-
Notifications
You must be signed in to change notification settings - Fork 251
rust
qiannian edited this page Jun 13, 2026
·
1 revision
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",
] }适用于已经通过 regsvr32 或安装脚本注册过 op 插件的环境。
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,
¶ms,
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.dll 的 setupW 加载 op_x86.dll 或 op_x64.dll,不需要使用 regsvr32 注册插件。
示例假设程序运行目录下有如下结构:
app.exe
op/
x86/
tools.dll
op_x86.dll
x64/
tools.dll
op_x64.dll
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,
¶ms,
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(())
}-
windowscrate 需要启用 COM、Variant、LibraryLoader 相关 feature。 -
setupW只在当前进程内生效,不会写入系统注册表。 -
setupW是cdecl调用约定,Rust 中使用unsafe extern "C"声明。 -
tools.dll安装了当前进程内的 COM Hook,加载成功后保持到进程退出即可。 - 当前 Rust 进程、
tools.dll、op_x86.dll/op_x64.dll的位数必须一致。