You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

非IUnknown继承的COM接口实现与IDL转换及远程调用疑问

关于Word PDF导出COM接口实现的疑问

我正在实现用于Word文档导出PDF的COM接口,遇到两类特殊接口:

  1. 部分接口未继承IUnknown,比如IMsoServerFileManagerSite:
#undef  INTERFACE
#define INTERFACE  IMsoServerFileManagerSite
DECLARE_INTERFACE(IMsoServerFileManagerSite)
{
  STDMETHOD_(BOOL, FGetHandle) (const WCHAR *pwzFileName, HANDLE *phFile, BOOL fRead, BOOL fWrite) PURE;
  STDMETHOD_(BOOL, FCloseHandle) (HANDLE hFile) PURE;
};

我发现用[local]属性可以把这类接口转成IDL:

[
    local,
    uuid(f8e47685-e402-4119-aa07-4ea4ff1f1123)
]
interface IMsoServerFileManagerSite
{
    BOOL FGetHandle(const WCHAR *pwzFileName, HANDLE *phFile, BOOL fRead, BOOL fWrite);
    BOOL FCloseHandle(HANDLE hFile);
}
  1. 另一类IMsoDocExporter接口虽继承IUnknown,但部分方法返回void而非HRESULT。将实现该接口的对象传入Word API后,调试发现我的DLL被combase.dll!CRemoteUnknown::RemQueryInterface调用。

现提出以下疑问:

  • combase.dll!CRemoteUnknown::RemQueryInterface是否意味着对象被远程查询?即是否需要代理/存根,无法使用[local]属性?
  • 使用HANDLE等非自动化类型且不返回HRESULT的接口能否被封送?
  • 如何将这类接口转换为IDL以让MIDL生成代理/存根实现?(比如MIDL文档提到可使用call_as映射为远程可调用方法,但不清楚HANDLE的处理方式)

问题解答

1. 关于CRemoteUnknown::RemQueryInterface的含义

是的,CRemoteUnknown::RemQueryInterface的调用确实表明对象正在被跨进程/远程上下文查询。这意味着COM运行时正在尝试为你的对象创建代理/存根,此时[local]属性不再适用——[local]标记的接口只能在同一进程内调用,不支持跨进程封送。

如果你的对象需要被Word进程(或其他外部进程)访问,就必须提供有效的代理/存根实现,不能依赖[local]。

2. 非自动化类型+非HRESULT返回值的接口能否封送

默认情况下不行:

  • COM标准封送要求接口方法必须返回HRESULT,这是跨进程错误处理的基础;
  • HANDLE是进程相关的内核对象引用,本身不具备跨进程可移植性——不同进程中的HANDLE值指向的是完全不同的内核对象实例,直接封送HANDLE会导致无效引用。

不过可以通过自定义封送或类型转换来解决这个问题,不能直接依赖MIDL的默认封送逻辑。

3. 转换为可生成代理/存根的IDL方案

针对这类特殊接口,需要分步骤处理:

步骤1:修正方法返回值为HRESULT

首先,必须把原接口中返回BOOL/void的方法,在IDL中映射为返回HRESULT的方法——这是MIDL生成代理/存根的硬性要求。可以用call_as属性来做本地方法和远程方法的映射:

[
    uuid(xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx),
    object
]
interface IMsoDocExporter : IUnknown
{
    // 远程方法:符合COM标准,返回HRESULT
    HRESULT Remote_Method([in] PARAM_TYPE param);
    // 本地方法:对应原接口的void/BOOL返回方法
    void Method([in] PARAM_TYPE param) call_as(Remote_Method);
}

你的实现只需要编写本地方法Method,MIDL会自动生成Remote_Method的代理/存根逻辑,负责把本地调用转换为远程可封送的调用。

步骤2:处理HANDLE类型

HANDLE不能直接封送,需要替换为跨进程可识别的对象标识,比如:

  • 如果是文件句柄:可以传递文件路径,在远程进程中重新打开;
  • 如果是其他内核对象:可以使用DuplicateHandle在进程间复制句柄,然后传递复制后的句柄值(注意需要确保目标进程有足够权限);
  • 自定义对象:可以把对象封装为COM接口,传递接口指针(COM会自动处理代理/存根)。

对于IMsoServerFileManagerSite这类接口,你可以在IDL中重新定义方法,用文件路径替代直接返回HANDLE,或者在远程方法中返回可跨进程的句柄副本:

[
    uuid(f8e47685-e402-4119-aa07-4ea4ff1f1123),
    object
]
interface IMsoServerFileManagerSite : IUnknown
{
    HRESULT Remote_FGetHandle([in] const WCHAR *pwzFileName, [out] HANDLE *phFile, [in] BOOL fRead, [in] BOOL fWrite);
    BOOL FGetHandle([in] const WCHAR *pwzFileName, [out] HANDLE *phFile, [in] BOOL fRead, [in] BOOL fWrite) call_as(Remote_FGetHandle);

    HRESULT Remote_FCloseHandle([in] HANDLE hFile);
    BOOL FCloseHandle([in] HANDLE hFile) call_as(Remote_FCloseHandle);
}

然后在Remote_FGetHandle的实现中,完成跨进程句柄复制的逻辑,确保远程进程拿到的是有效的句柄。

步骤3:生成代理/存根

确保IDL中所有接口都继承IUnknown(或IDispatch),并且方法都符合COM封送要求(返回HRESULT,使用可封送类型),然后用MIDL编译IDL文件,生成代理/存根DLL,注册后即可支持跨进程调用。


内容的提问来源于stack exchange,提问作者VersBersch

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 10:55:16