如何在C#中手动调用Marshal.QueryInterface()?
手动调用Marshal.QueryInterface()的细节解析
首先明确:虽然实际开发中直接强制转换COM接口类型(CLR会隐式调用QueryInterface)是更简洁安全的方式,但为了学习手动调用的原理,搞清楚这些细节完全有必要。
针对你的疑问逐一解答:
1. pUnk的来源:Marshal.GetIUnknownForObject(my_ISomething)完全可行
Marshal.GetIUnknownForObject()的作用就是将托管的COM接口实例转换为底层的IUnknown指针(以IntPtr形式返回),这完全符合QueryInterface第一个参数对IUnknown指针的要求。
需要注意:通过这个方法获取的IntPtr会增加COM对象的引用计数,所以**必须在使用完后调用Marshal.Release(pUnk)**来释放引用,避免内存泄漏。
3. 将ppv转换回ISomething2接口
QueryInterface成功返回的ppv是目标接口的原始指针,要转换为托管的接口实例,有两种常用方式:
- 方式一:使用
Marshal.GetObjectForIUnknown(ppv)获取通用对象,再强制转换为目标接口类型 - 方式二:使用
Marshal.CreateWrapperOfType(ppv, typeof(ISomething2))直接创建对应类型的包装对象
同样要注意:QueryInterface返回的ppv也会增加对象引用计数,所以转换为托管对象后,必须调用Marshal.Release(ppv)来释放这个原始指针(托管对象自身会在GC回收时自动释放对应的COM引用)。
完整代码示例
// 假设my_ISomething是已经获取到的有效COM接口实例 IntPtr pUnk = Marshal.GetIUnknownForObject(my_ISomething); try { // 获取目标接口ISomething2的GUID Guid iidISomething2 = typeof(ISomething2).GUID; // 调用QueryInterface,HRESULT返回0表示成功 if (Marshal.QueryInterface(pUnk, ref iidISomething2, out IntPtr ppv) == 0) { try { // 转换为托管接口实例 ISomething2 something2 = (ISomething2)Marshal.GetObjectForIUnknown(ppv); // 在这里使用something2接口的方法 something2.DoSomething(); } finally { // 释放QueryInterface返回的指针 Marshal.Release(ppv); } } } finally { // 释放GetIUnknownForObject获取的IUnknown指针 Marshal.Release(pUnk); }
额外注意事项
Marshal.QueryInterface的返回值是HRESULT,除了0(S_OK),还可能返回E_NOINTERFACE(找不到目标接口)等错误码,实际测试时可以根据返回值判断调用结果;- 手动调用时必须严格遵循COM的引用计数规则,每个
AddRef(包括GetIUnknownForObject和QueryInterface)都要对应一个Release; - 隐式强制转换的本质是CLR帮你完成了从获取IUnknown指针、调用QueryInterface、转换为托管对象到自动管理引用的全部流程,这也是为什么实际开发中更推荐这种方式。
内容的提问来源于stack exchange,提问作者Nick Gris
相关产品推荐
相关产品推荐

