将 Windows COM 自动化与 Rust 结合使用

问题描述 投票:0回答:1

我们通过名为“bPac”的编程接口/SDK 使用 Brother 标签打印机(QL 系列)。该工具(标签打印功能是该工具的一部分)目前正在从 Ruby 重写为 Rust。在这个过程中,我陷入了 Rust 中的 Win32/COM/OLE 问题。 Ruby 中的最小工作示例非常简单:

doc = WIN32OLE.new "bpac.Document"
doc.open 'some_label.lbx'
doc.SetPrinter "Brother QL-810W", true

print_opts = 0
doc.StartPrint("", print_opts)
doc.PrintOut(1, print_opts)
doc.EndPrint

我想从 Rust 中有一个类似的简单工作示例开始。由于我不熟悉 Windows API,因此

windows-rs
箱子相当庞大。我想,我可能需要其中的
System::Com
部分。这就是我开始的地方:

use windows::Win32::System::{Com, Ole};
use ::windows::core::Result;

pub fn print() {
    unsafe { Com::CoInitializeEx(std::ptr::null(), Com::COINIT_APARTMENTTHREADED) }.unwrap();

    let clsid = unsafe { Com::CLSIDFromProgID("bpac.Document") };
    println!("We've got a CLSID: {:?}", clsid);

    let obj: Result<Com::IDispatch> = unsafe { Com::CoCreateInstance(&clsid.unwrap(), None, Com::CLSCTX_ALL) };
    println!("IDispatch: {:?}", obj);
}

这样我就可以获得一个

IDispatch
对象,我应该能够查询可用的方法和属性。调用这个低级(非常接近 C-metal)API 时遇到问题。我找到了 win-idispatch 板条箱,但这似乎与
windows-rs
不起作用...:-/

windows rust com ole
1个回答
1
投票

我想做类似的事情:使用 Rust 的 COM Office/Excel 自动化。
简而言之,我围绕

IDispatch::GetIDsOfNames()
IDispatch::Invoke()
构建了一个包装器,对于参数,可以使用
VARIANT

以下资源帮助我构建了解决方案:
https://stuncloud.wordpress.com/2021/08/17/rust_com/
https://qiita.com/benki/items/42099c58e07b16293609
https://learn.microsoft.com/en-us/previous-versions/office/troubleshoot/office-developer/automate-excel-from-c

编辑 2024-03-19:添加一个快速示例:

货物.toml

[package]
name = "office_interop"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.81"

[dependencies.windows]
version = "0.54.0"
features = [
    "Win32_Foundation",
    "Win32_System_Com",
    "Win32_System_Ole",
]

main.rs

use anyhow::{anyhow, Context};
use std::time::Duration;
use std::{env, thread};
use windows::core::*;
use windows::Win32::System::Com::*;
use windows::Win32::System::Ole::*;

const LOCALE_USER_DEFAULT: u32 = 0x0400;
const LOCALE_SYSTEM_DEFAULT: u32 = 0x0800;

//cargo run -- FILE

fn main() -> anyhow::Result<()> {
    let mut args = env::args();
    let _ = args.next();
    let file = args.next();
    let file = file.ok_or(anyhow!("arg1 missing"))?;
    unsafe {
        let res = CoInitialize(None);
        if res.is_err() {
            return Err(anyhow!("error: {}", res.message()));
        }
        let _com = DeferCoUninitialize;

        let clsid = CLSIDFromProgID(PCWSTR::from_raw(
            HSTRING::from("Excel.Application").as_ptr(),
        ))
        .with_context(|| "CLSIDFromProgID")?;

        println!("{:?}", clsid);

        let excel = CoCreateInstance(&clsid, None, CLSCTX_LOCAL_SERVER)
            .with_context(|| "CoCreateInstance")?;
        let excel = IDispatchWrapper(excel);
        let _excel = DeferExcelQuit(&excel);

        excel
            .put("Visible", vec![false.into()])
            .with_context(|| "Visible false")?;

        excel
            .put("DisplayAlerts", vec![false.into()])
            .with_context(|| "DisplayAlerts false")?;

        let result = excel.get("Workbooks").with_context(|| "get Workbooks")?;
        let workbooks = result.idispatch().with_context(|| "idispatch Workbooks")?;

        let result = workbooks
            .call("Open", vec![(&file).into()])
            .with_context(|| format!("Failed to open file \"{}\"!", file))?;

        let workbook = result.idispatch().with_context(|| "idispatch Workbook")?;

        thread::sleep(Duration::from_millis(3000));

        let result = excel.get("Caption").with_context(|| "get Caption")?;
        println!("{}", result.string().with_context(|| "Caption string")?);

        workbook
            .call("Close", vec![false.into()])
            .with_context(|| "call Close")?;

        thread::sleep(Duration::from_millis(3000));

        let result = excel.get("Caption").with_context(|| "get Caption")?;
        println!("{}", result.string().with_context(|| "Caption string")?);

        thread::sleep(Duration::from_millis(3000));

        Ok(())
    }
}

pub struct Variant(VARIANT);

impl From<bool> for Variant {
    fn from(value: bool) -> Self {
        Self(value.into())
    }
}

impl From<i32> for Variant {
    fn from(value: i32) -> Self {
        Self(value.into())
    }
}

impl From<&str> for Variant {
    fn from(value: &str) -> Self {
        Self(BSTR::from(value).into())
    }
}

impl From<&String> for Variant {
    fn from(value: &String) -> Self {
        Self(BSTR::from(value).into())
    }
}

impl Variant {
    pub fn bool(&self) -> anyhow::Result<bool> {
        Ok(bool::try_from(&self.0)?)
    }

    pub fn int(&self) -> anyhow::Result<i32> {
        Ok(i32::try_from(&self.0)?)
    }

    pub fn string(&self) -> anyhow::Result<String> {
        Ok(BSTR::try_from(&self.0)?.to_string())
    }

    pub fn idispatch(&self) -> anyhow::Result<IDispatchWrapper> {
        Ok(IDispatchWrapper(IDispatch::try_from(&self.0)?))
    }

    pub fn vt(&self) -> u16 {
        unsafe { self.0.as_raw().Anonymous.Anonymous.vt }
    }
}

pub struct IDispatchWrapper(pub IDispatch);

impl IDispatchWrapper {
    pub fn invoke(
        &self,
        flags: DISPATCH_FLAGS,
        name: &str,
        mut args: Vec<Variant>,
    ) -> anyhow::Result<Variant> {
        unsafe {
            let mut dispid = 0;
            self.0
                .GetIDsOfNames(
                    &GUID::default(),
                    &PCWSTR::from_raw(HSTRING::from(name).as_ptr()),
                    1,
                    LOCALE_USER_DEFAULT,
                    &mut dispid,
                )
                .with_context(|| "GetIDsOfNames")?;

            let mut dp = DISPPARAMS::default();
            let mut dispid_named = DISPID_PROPERTYPUT;

            if !args.is_empty() {
                args.reverse();
                dp.cArgs = args.len() as u32;
                dp.rgvarg = args.as_mut_ptr() as *mut VARIANT;

                // Handle special-case for property-puts!
                if (flags & DISPATCH_PROPERTYPUT) != DISPATCH_FLAGS(0) {
                    dp.cNamedArgs = 1;
                    dp.rgdispidNamedArgs = &mut dispid_named;
                }
            }

            let mut result = VARIANT::default();
            self.0
                .Invoke(
                    dispid,
                    &GUID::default(),
                    LOCALE_SYSTEM_DEFAULT,
                    flags,
                    &dp,
                    Some(&mut result),
                    None,
                    None,
                )
                .with_context(|| "Invoke")?;

            Ok(Variant(result))
        }
    }

    pub fn get(&self, name: &str) -> anyhow::Result<Variant> {
        self.invoke(DISPATCH_PROPERTYGET, name, vec![])
    }

    pub fn int(&self, name: &str) -> anyhow::Result<i32> {
        let result = self.get(name)?;
        result.int()
    }

    pub fn bool(&self, name: &str) -> anyhow::Result<bool> {
        let result = self.get(name)?;
        result.bool()
    }

    pub fn string(&self, name: &str) -> anyhow::Result<String> {
        let result = self.get(name)?;
        result.string()
    }

    pub fn put(&self, name: &str, args: Vec<Variant>) -> anyhow::Result<Variant> {
        self.invoke(DISPATCH_PROPERTYPUT, name, args)
    }

    pub fn call(&self, name: &str, args: Vec<Variant>) -> anyhow::Result<Variant> {
        self.invoke(DISPATCH_METHOD, name, args)
    }
}

pub struct DeferExcelQuit<'a>(pub &'a IDispatchWrapper);

impl Drop for DeferExcelQuit<'_> {
    fn drop(&mut self) {
        let result = self.0.get("Workbooks").unwrap();
        let workbooks = result.idispatch().unwrap();
        let count = workbooks.int("Count").unwrap();
        for i in 1..=count {
            let result = workbooks
                .invoke(DISPATCH_PROPERTYGET, "Item", vec![i.into()])
                .unwrap();
            let workbook = result.idispatch().unwrap();
            workbook.put("Saved", vec![true.into()]).unwrap();
        }
        self.0.call("Quit", vec![]).unwrap(); //todo: quit doesn't kill process after open and Visible=true
    }
}

pub struct DeferCoUninitialize;

impl Drop for DeferCoUninitialize {
    fn drop(&mut self) {
        unsafe {
            CoUninitialize();
        }
    }
}

© www.soinside.com 2019 - 2024. All rights reserved.