Skip to content
qiannian edited this page Jun 13, 2026 · 1 revision

Rust 调用示例

Rust 可以通过 windows crate 创建 op.opsoft COM 对象。下面示例同时包含已注册 COM 调用和免注册调用方式。

准备依赖

[package]
name = "op-rust-demo"
version = "0.1.0"
edition = "2021"

[dependencies]
windows = { version = "0.62.2", features = [
    "Win32_Foundation",
    "Win32_System_Com",
    "Win32_System_Com_StructuredStorage",
    "Win32_System_LibraryLoader",
    "Win32_System_Ole",
    "Win32_System_Variant",
] }

已注册 COM 示例

适用于已经通过 regsvr32 或安装脚本注册过 op 插件的环境。

Code

use std::ptr::null_mut;

use windows::{
    core::{BSTR, GUID, PCWSTR},
    Win32::System::{
        Com::{
            CLSIDFromProgID, CoCreateInstance, CoInitializeEx, CoUninitialize, DISPATCH_METHOD,
            DISPATCH_PROPERTYGET, DISPPARAMS, IDispatch, CLSCTX_INPROC_SERVER, CLSCTX_LOCAL_SERVER,
            COINIT_APARTMENTTHREADED,
        },
        Variant::VARIANT,
    },
};

type AppResult<T> = Result<T, Box<dyn std::error::Error>>;

struct ComRuntime;

impl ComRuntime {
    fn init() -> AppResult<Self> {
        // 初始化当前线程的 COM 环境
        unsafe {
            CoInitializeEx(None, COINIT_APARTMENTTHREADED).ok()?;
        }
        Ok(Self)
    }
}

impl Drop for ComRuntime {
    fn drop(&mut self) {
        // 当前线程结束 COM 使用
        unsafe {
            CoUninitialize();
        }
    }
}

fn to_wide(value: &str) -> Vec<u16> {
    value.encode_utf16().chain(Some(0)).collect()
}

fn create_op() -> AppResult<IDispatch> {
    // 通过 ProgID 查找 CLSID,并创建 IDispatch 对象
    unsafe {
        let prog_id = to_wide("op.opsoft");
        let clsid = CLSIDFromProgID(PCWSTR(prog_id.as_ptr()))?;
        let op = CoCreateInstance(
            &clsid,
            None,
            CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
        )?;
        Ok(op)
    }
}

fn invoke_string(dispatch: &IDispatch, name: &str) -> AppResult<String> {
    unsafe {
        let iid_null = GUID::from_u128(0);
        let method_name = to_wide(name);
        let names = [PCWSTR(method_name.as_ptr())];
        let mut dispid = 0;

        // 把方法名转换为 DISPID
        dispatch.GetIDsOfNames(&iid_null, names.as_ptr(), 1, 0, &mut dispid)?;

        let params = DISPPARAMS {
            rgvarg: null_mut(),
            rgdispidNamedArgs: null_mut(),
            cArgs: 0,
            cNamedArgs: 0,
        };
        let mut result = VARIANT::default();

        // 调用无参数方法,例如 Ver()
        dispatch.Invoke(
            dispid,
            &iid_null,
            0,
            DISPATCH_METHOD | DISPATCH_PROPERTYGET,
            &params,
            Some(&mut result as *mut VARIANT),
            None,
            None,
        )?;

        let text = BSTR::try_from(&result)?;
        Ok(text.to_string())
    }
}

fn main() -> AppResult<()> {
    let _com = ComRuntime::init()?;
    let op = create_op()?;

    // 调用 op.Ver()
    let version = invoke_string(&op, "Ver")?;
    println!("op version: {}", version);

    Ok(())
}

免注册示例

通过 tools.dllsetupW 加载 op_x86.dllop_x64.dll,不需要使用 regsvr32 注册插件。

示例假设程序运行目录下有如下结构:

app.exe
op/
  x86/
    tools.dll
    op_x86.dll
  x64/
    tools.dll
    op_x64.dll

Code

use std::{
    os::windows::ffi::OsStrExt,
    path::{Path, PathBuf},
    ptr::null_mut,
};

use windows::{
    core::{BSTR, GUID, PCSTR, PCWSTR},
    Win32::System::{
        Com::{
            CLSIDFromProgID, CoCreateInstance, CoInitializeEx, CoUninitialize, DISPATCH_METHOD,
            DISPATCH_PROPERTYGET, DISPPARAMS, IDispatch, CLSCTX_INPROC_SERVER, CLSCTX_LOCAL_SERVER,
            COINIT_APARTMENTTHREADED,
        },
        LibraryLoader::{GetProcAddress, LoadLibraryW},
        Variant::VARIANT,
    },
};

type AppResult<T> = Result<T, Box<dyn std::error::Error>>;
type SetupW = unsafe extern "C" fn(PCWSTR) -> i32;

struct ComRuntime;

impl ComRuntime {
    fn init() -> AppResult<Self> {
        // 初始化当前线程的 COM 环境
        unsafe {
            CoInitializeEx(None, COINIT_APARTMENTTHREADED).ok()?;
        }
        Ok(Self)
    }
}

impl Drop for ComRuntime {
    fn drop(&mut self) {
        // 当前线程结束 COM 使用
        unsafe {
            CoUninitialize();
        }
    }
}

fn err(message: impl Into<String>) -> Box<dyn std::error::Error> {
    std::io::Error::new(std::io::ErrorKind::Other, message.into()).into()
}

fn path_to_wide(path: &Path) -> Vec<u16> {
    path.as_os_str().encode_wide().chain(Some(0)).collect()
}

fn str_to_wide(value: &str) -> Vec<u16> {
    value.encode_utf16().chain(Some(0)).collect()
}

fn setup_op_regfree(plugin_root: &Path) -> AppResult<()> {
    // 根据当前 Rust 进程位数选择 x86 或 x64
    let (arch, op_name) = if cfg!(target_pointer_width = "64") {
        ("x64", "op_x64.dll")
    } else {
        ("x86", "op_x86.dll")
    };

    let runtime_dir = plugin_root.join(arch);
    let tools_dll = runtime_dir.join("tools.dll");
    let op_dll = runtime_dir.join(op_name);

    if !tools_dll.exists() {
        return Err(err(format!("找不到免注册工具: {}", tools_dll.display())));
    }
    if !op_dll.exists() {
        return Err(err(format!("找不到 op 插件: {}", op_dll.display())));
    }

    unsafe {
        // 加载 tools.dll,并获取 setupW 函数地址
        let tools_path = path_to_wide(&tools_dll);
        let module = LoadLibraryW(PCWSTR(tools_path.as_ptr()))?;
        let proc = GetProcAddress(module, PCSTR(b"setupW\0".as_ptr()))
            .ok_or_else(|| err("tools.dll 未导出 setupW"))?;

        // setupW 是 cdecl 调用约定
        let setup_w: SetupW = std::mem::transmute(proc);

        // setupW 必须在 CoCreateInstance 之前调用
        let op_path = path_to_wide(&op_dll);
        if setup_w(PCWSTR(op_path.as_ptr())) != 1 {
            return Err(err(format!("免注册加载失败: {}", op_dll.display())));
        }
    }

    Ok(())
}

fn create_op() -> AppResult<IDispatch> {
    // 通过 ProgID 查找 CLSID,并创建 IDispatch 对象
    unsafe {
        let prog_id = str_to_wide("op.opsoft");
        let clsid = CLSIDFromProgID(PCWSTR(prog_id.as_ptr()))?;
        let op = CoCreateInstance(
            &clsid,
            None,
            CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
        )?;
        Ok(op)
    }
}

fn invoke_string(dispatch: &IDispatch, name: &str) -> AppResult<String> {
    unsafe {
        let iid_null = GUID::from_u128(0);
        let method_name = str_to_wide(name);
        let names = [PCWSTR(method_name.as_ptr())];
        let mut dispid = 0;

        // 把方法名转换为 DISPID
        dispatch.GetIDsOfNames(&iid_null, names.as_ptr(), 1, 0, &mut dispid)?;

        let params = DISPPARAMS {
            rgvarg: null_mut(),
            rgdispidNamedArgs: null_mut(),
            cArgs: 0,
            cNamedArgs: 0,
        };
        let mut result = VARIANT::default();

        // 调用无参数方法,例如 Ver()
        dispatch.Invoke(
            dispid,
            &iid_null,
            0,
            DISPATCH_METHOD | DISPATCH_PROPERTYGET,
            &params,
            Some(&mut result as *mut VARIANT),
            None,
            None,
        )?;

        let text = BSTR::try_from(&result)?;
        Ok(text.to_string())
    }
}

fn main() -> AppResult<()> {
    // 程序运行目录下的 op 目录,也可以改成自己的插件释放目录
    let plugin_root = PathBuf::from("./op");
    setup_op_regfree(&plugin_root)?;

    let _com = ComRuntime::init()?;
    let op = create_op()?;

    // 调用 op.Ver()
    let version = invoke_string(&op, "Ver")?;
    println!("op version: {}", version);

    Ok(())
}

说明

  • windows crate 需要启用 COM、Variant、LibraryLoader 相关 feature。
  • setupW 只在当前进程内生效,不会写入系统注册表。
  • setupWcdecl 调用约定,Rust 中使用 unsafe extern "C" 声明。
  • tools.dll 安装了当前进程内的 COM Hook,加载成功后保持到进程退出即可。
  • 当前 Rust 进程、tools.dllop_x86.dll/op_x64.dll 的位数必须一致。

Clone this wiki locally