问题描述
如何从派生类调用在基类中定义的私有 COM 接口的方法?
例如这里是COM接口,IComInterface
(IDL):
[uuid(9AD16CCE-7588-486C-BC56-F3161FF92EF2),ole自动化]接口 IComInterface: IUnknown{HRESULT ComMethod([in] IUnknown* arg);}
这是来自 OldLibrary
程序集的 C# 类 BaseClass
,它像这样实现 IComInterface
(注意接口被声明为私有):
//程序集OldLibrary"公共静态类 OldLibrary{[ComImport(), Guid("9AD16CCE-7588-486C-BC56-F3161FF92EF2")][接口类型(ComInterfaceType.InterfaceIsIUnknown)]私有接口 IComInterface{void ComMethod([In, MarshalAs(UnmanagedType.Interface)] 对象 arg);}[ComVisible(真)][ClassInterface(ClassInterfaceType.None)]公共类 BaseClass : IComInterface{无效 IComInterface.ComMethod(对象 arg){Console.WriteLine("BaseClass.IComInterface.ComMethod");}}}
最后,这是一个改进的版本,ImprovedClass
,它派生自 BaseClass
,但声明并实现了自己的 IComInterface
版本,因为base 的 OldLibrary.IComInterface
无法访问:
//程序集NewLibrary"公共静态类 NewLibrary{[ComImport(), Guid("9AD16CCE-7588-486C-BC56-F3161FF92EF2")][接口类型(ComInterfaceType.InterfaceIsIUnknown)]私有接口 IComInterface{void ComMethod([In, MarshalAs(UnmanagedType.Interface)] 对象 arg);}[ComVisible(真)][ClassInterface(ClassInterfaceType.None)]公共类改进类:OldLibrary.BaseClass,ICom接口,自定义查询接口{//ICom接口无效 IComInterface.ComMethod(对象 arg){Console.WriteLine("ImprovedClass.IComInterface.ComMethod");//我如何在这里调用 base.ComMethod,//除了通过反射?}//ICustomQuery接口公共 CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv){if (iid == typeof(IComInterface).GUID){ppv = Marshal.GetComInterfaceForObject(this, typeof(IComInterface), CustomQueryInterfaceMode.Ignore);返回 CustomQueryInterfaceResult.Handled;}ppv = IntPtr. 零;返回 CustomQueryInterfaceResult.NotHandled;}}}
我如何在没有反射的情况下从 ImprovedClass.ComMethod
调用 BaseClass.ComMethod
?
我可以使用反射,但实际上是用例 IComInterface
是一个复杂的 OLE 接口,具有许多复杂签名的成员.
我认为是因为 BaseClass.IComInterface
和 ImprovedClass.IComInterface
都是 COM 接口,具有相同的 GUID 和相同的方法签名,并且有 COM 类型等效,所以必须有一种方法来做我想做的事没有反思.
另一个要求是 ImprovedClass
必须从 BaseClass
派生,因为 C# 客户端代码需要 BaseClass
的实例,它通过到 COM 客户端代码.因此,在 ImprovedClass
中包含 BaseClass
不是一种选择.
[已编辑] 涉及从 WebBrowser
和 WebBrowserSite
描述 这里.
我想通了,通过使用一个 helper 包含的对象 (BaseClassComProxy
) 和一个聚合的 COM 代理对象,用 .这种方法为我提供了一个具有单独标识的非托管对象,我可以对其进行转换(使用 Marshal.GetTypedObjectForIUnknown
) 到我自己的 BaseClass.IComInterface
接口的等效版本,即否则无法访问.它适用于任何其他私有 COM 接口,由 BaseClass
实现.
@EricBrown 关于 COM 身份规则的观点对这项研究有很大帮助.谢谢埃里克!
这是一个独立的控制台测试应用.WebBrowserSite
解决原始问题的代码发布在此处.
使用系统;使用 System.Diagnostics;使用 System.Linq;使用 System.Runtime.InteropServices;命名空间 ManagedServer{/*//IComInterface IDL 定义[uuid(9AD16CCE-7588-486C-BC56-F3161FF92EF2),ole自动化]接口 IComInterface: IUnknown{HRESULT ComMethod(IUnknown* arg);}*///旧库公共静态类 OldLibrary{//私有 COM 接口 IComInterface[ComImport(), Guid("9AD16CCE-7588-486C-BC56-F3161FF92EF2")][接口类型(ComInterfaceType.InterfaceIsIUnknown)]私有接口 IComInterface{void ComMethod([In, MarshalAs(UnmanagedType.Interface)] 对象 arg);}[ComVisible(真)][ClassInterface(ClassInterfaceType.None)]公共类 BaseClass : IComInterface{无效 IComInterface.ComMethod(对象 arg){Console.WriteLine("BaseClass.IComInterface.ComMethod");}}}//新建库公共静态类 NewLibrary{//OldLibrary.IComInterface 在这里不可访问,//定义一个新的等效版本[ComImport(), Guid("9AD16CCE-7588-486C-BC56-F3161FF92EF2")][接口类型(ComInterfaceType.InterfaceIsIUnknown)]私有接口 IComInterface{void ComMethod([In, MarshalAs(UnmanagedType.Interface)] 对象 arg);}[ComVisible(真)][ClassInterface(ClassInterfaceType.None)]公共类改进类:OldLibrary.BaseClass,NewLibrary.IComInterface,ICustomQuery接口,一次性{NewLibrary.IComInterface _baseIComInterface;BaseClassComProxy _baseClassComProxy;//ICom接口//我们要调用只能通过 COM 访问的 BaseClass.IComInterface.ComMethod无效 IComInterface.ComMethod(对象 arg){_baseIComInterface.ComMethod(arg);Console.WriteLine("ImprovedClass.IComInterface.ComMethod");}//ICustomQuery接口公共 CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv){if (iid == typeof(NewLibrary.IComInterface).GUID){//CustomQueryInterfaceMode.Ignore 是为了避免 QI 期间的无限循环.ppv = Marshal.GetComInterfaceForObject(this, typeof(NewLibrary.IComInterface), CustomQueryInterfaceMode.Ignore);返回 CustomQueryInterfaceResult.Handled;}ppv = IntPtr. 零;返回 CustomQueryInterfaceResult.NotHandled;}//构造函数公共改进类(){//将 CCW 对象与辅助 Inner 对象聚合_baseClassComProxy = new BaseClassComProxy(this);_baseIComInterface = _baseClassComProxy.GetComInterface<IComInterface>();}〜改进类(){处置();Console.WriteLine("ImprovedClass finalized.");}//IDispose公共无效处置(){//我们可能有对自身的周期性 COM 引用//例如,通过 _baseIComInterface//确保释放所有引用if (_baseIComInterface != null){Marshal.ReleaseComObject(_baseIComInterface);_baseIComInterface = null;}if (_baseClassComProxy != null){_baseClassComProxy.Dispose();_baseClassComProxy = null;}}//供测试用公共无效 InvokeComMethod(){((NewLibrary.IComInterface)this).ComMethod(null);}}#region BaseClassComProxy//内部作为聚合对象类 BaseClassComProxy :ICustomQuery接口,一次性{弱引用_outer;//避免外部和内部对象之间的循环引用类型[] _interfaces;//base 的私有 COM 接口在这里IntPtr _unkAggregated;//聚合代理公共 BaseClassComProxy(对象外部){_outer = new WeakReference(outer);_interfaces = outer.GetType().BaseType.GetInterfaces();var unkOuter = Marshal.GetIUnknownForObject(outer);尝试{//CreateAggregatedObject 对此进行 AddRef//我们为正确关闭提供了 IDispose_unkAggregated = Marshal.CreateAggregatedObject(unkOuter, this);}最后{Marshal.Release(unkOuter);}}public T GetComInterface<T>() 其中 T : 类{//将外部的基本接口转换为等效的外部接口返回 (T)Marshal.GetTypedObjectForIUnknown(_unkAggregated, typeof(T));}public void GetComInterface<T>(out T baseInterface) where T : class{baseInterface = GetComInterface<T>();}~BaseClassComProxy(){处置();Console.WriteLine("BaseClassComProxy 对象完成.");}//IDispose公共无效处置(){if (_outer != null){_outer = null;_interfaces = null;if (_unkAggregated != IntPtr.Zero){Marshal.Release(_unkAggregated);_unkAggregated = IntPtr.Zero;}}}//ICustomQuery接口公共 CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv){//访问外部的基本私有 COM 接口if (_outer != null){var ifaceGuid = iid;var iface = _interfaces.FirstOrDefault((i) => i.GUID == ifaceGuid);if (iface != null && iface.IsImport){//必须是具有 ComImport 属性的 COM 接口var unk = Marshal.GetComInterfaceForObject(_outer.Target, iface, CustomQueryInterfaceMode.Ignore);if (unk != IntPtr.Zero){ppv = unk;返回 CustomQueryInterfaceResult.Handled;}}}ppv = IntPtr. 零;返回 CustomQueryInterfaceResult.Failed;}}#endregion}课堂节目{静态无效主要(字符串 [] 参数){//测试var 改进 = 新的 NewLibrary.ImprovedClass();改进的.InvokeComMethod();////COM 客户端//var unmanagedObject = (ISimpleUnmanagedObject)Activator.CreateInstance(Type.GetTypeFromProgID("Noseratio.SimpleUnmanagedObject"));//unmanagedObject.InvokeComMethod(改进);改进的.Dispose();改进=空;//测试引用计数GC.Collect(生成:GC.MaxGeneration,模式:GCCollectionMode.Forced,阻塞:false);Console.WriteLine("按 Enter 退出.");Console.ReadLine();}//COM 测试客户端接口[ComImport(), Guid("2EA68065-8890-4F69-A02F-2BC3F0418561")][接口类型(ComInterfaceType.InterfaceIsDual)]内部接口 ISimpleUnmanagedObject{void InvokeComMethod([In, MarshalAs(UnmanagedType.Interface)] 对象 arg);void InvokeComMethodDirect([In] IntPtr comInterface);}}}
输出:
BaseClass.IComInterface.ComMethod改进的Class.IComInterface.ComMethod按 Enter 退出.BaseClassComProxy 对象最终确定.改进类最终确定.How can I invoke a method of a private COM interface, defined in a base class, from a derived class?
For example, here is the COM interface, IComInterface
(IDL):
[
uuid(9AD16CCE-7588-486C-BC56-F3161FF92EF2),
oleautomation
]
interface IComInterface: IUnknown
{
HRESULT ComMethod([in] IUnknown* arg);
}
Here's the C# class BaseClass
from OldLibrary
assembly, which implements IComInterface
like this (note the interface is declared as private):
// Assembly "OldLibrary"
public static class OldLibrary
{
[ComImport(), Guid("9AD16CCE-7588-486C-BC56-F3161FF92EF2")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IComInterface
{
void ComMethod([In, MarshalAs(UnmanagedType.Interface)] object arg);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class BaseClass : IComInterface
{
void IComInterface.ComMethod(object arg)
{
Console.WriteLine("BaseClass.IComInterface.ComMethod");
}
}
}
Finally, here's an improved version, ImprovedClass
, which derives from BaseClass
, but declares and implement its own version of IComInterface
, because the base's OldLibrary.IComInterface
is inaccessible:
// Assembly "NewLibrary"
public static class NewLibrary
{
[ComImport(), Guid("9AD16CCE-7588-486C-BC56-F3161FF92EF2")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IComInterface
{
void ComMethod([In, MarshalAs(UnmanagedType.Interface)] object arg);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class ImprovedClass :
OldLibrary.BaseClass,
IComInterface,
ICustomQueryInterface
{
// IComInterface
void IComInterface.ComMethod(object arg)
{
Console.WriteLine("ImprovedClass.IComInterface.ComMethod");
// How do I call base.ComMethod here,
// otherwise than via reflection?
}
// ICustomQueryInterface
public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
{
if (iid == typeof(IComInterface).GUID)
{
ppv = Marshal.GetComInterfaceForObject(this, typeof(IComInterface), CustomQueryInterfaceMode.Ignore);
return CustomQueryInterfaceResult.Handled;
}
ppv = IntPtr.Zero;
return CustomQueryInterfaceResult.NotHandled;
}
}
}
How do I call BaseClass.ComMethod
from ImprovedClass.ComMethod
without reflection?
I could use reflection, but in the real use case IComInterface
is a complex OLE interface with a number of members of complex signatures.
I thought that because both BaseClass.IComInterface
and ImprovedClass.IComInterface
are both COM interfaces with the same GUID and identical method signatures, and there's COM Type Equivalence in .NET 4.0+, so there has to be a way to do what I'm after without reflection.
Another requirement is that ImprovedClass
has to be derived from BaseClass
, because the C# client code expects an instance of BaseClass
, which it passes to the COM client code. Thus, containment of BaseClass
inside ImprovedClass
is not an option.
[EDITED] A real-life scenario which involves deriving from WebBrowser
and WebBrowserSite
is described here.
I figured it out, by using a helper contained object (BaseClassComProxy
) and an aggregated COM proxy object, created with Marshal.CreateAggregatedObject
. This approach gives me an unmanaged object with separate identity, which I can cast (with Marshal.GetTypedObjectForIUnknown
) to my own equivalent version of BaseClass.IComInterface
interface, which is not otherwise accessible. It works for any other private COM interfaces, implemented by BaseClass
.
@EricBrown's points about COM identity rules have helped a lot with this research. Thanks Eric!
Here's a standalone console test app. The code solving the original problem with WebBrowserSite
is posted here.
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
namespace ManagedServer
{
/*
// IComInterface IDL definition
[
uuid(9AD16CCE-7588-486C-BC56-F3161FF92EF2),
oleautomation
]
interface IComInterface: IUnknown
{
HRESULT ComMethod(IUnknown* arg);
}
*/
// OldLibrary
public static class OldLibrary
{
// private COM interface IComInterface
[ComImport(), Guid("9AD16CCE-7588-486C-BC56-F3161FF92EF2")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IComInterface
{
void ComMethod([In, MarshalAs(UnmanagedType.Interface)] object arg);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class BaseClass : IComInterface
{
void IComInterface.ComMethod(object arg)
{
Console.WriteLine("BaseClass.IComInterface.ComMethod");
}
}
}
// NewLibrary
public static class NewLibrary
{
// OldLibrary.IComInterface is inaccessible here,
// define a new equivalent version
[ComImport(), Guid("9AD16CCE-7588-486C-BC56-F3161FF92EF2")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IComInterface
{
void ComMethod([In, MarshalAs(UnmanagedType.Interface)] object arg);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class ImprovedClass :
OldLibrary.BaseClass,
NewLibrary.IComInterface,
ICustomQueryInterface,
IDisposable
{
NewLibrary.IComInterface _baseIComInterface;
BaseClassComProxy _baseClassComProxy;
// IComInterface
// we want to call BaseClass.IComInterface.ComMethod which is only accessible via COM
void IComInterface.ComMethod(object arg)
{
_baseIComInterface.ComMethod(arg);
Console.WriteLine("ImprovedClass.IComInterface.ComMethod");
}
// ICustomQueryInterface
public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
{
if (iid == typeof(NewLibrary.IComInterface).GUID)
{
// CustomQueryInterfaceMode.Ignore is to avoid infinite loop during QI.
ppv = Marshal.GetComInterfaceForObject(this, typeof(NewLibrary.IComInterface), CustomQueryInterfaceMode.Ignore);
return CustomQueryInterfaceResult.Handled;
}
ppv = IntPtr.Zero;
return CustomQueryInterfaceResult.NotHandled;
}
// constructor
public ImprovedClass()
{
// aggregate the CCW object with the helper Inner object
_baseClassComProxy = new BaseClassComProxy(this);
_baseIComInterface = _baseClassComProxy.GetComInterface<IComInterface>();
}
~ImprovedClass()
{
Dispose();
Console.WriteLine("ImprovedClass finalized.");
}
// IDispose
public void Dispose()
{
// we may have recicular COM references to itself
// e.g., via _baseIComInterface
// make sure to release all references
if (_baseIComInterface != null)
{
Marshal.ReleaseComObject(_baseIComInterface);
_baseIComInterface = null;
}
if (_baseClassComProxy != null)
{
_baseClassComProxy.Dispose();
_baseClassComProxy = null;
}
}
// for testing
public void InvokeComMethod()
{
((NewLibrary.IComInterface)this).ComMethod(null);
}
}
#region BaseClassComProxy
// Inner as aggregated object
class BaseClassComProxy :
ICustomQueryInterface,
IDisposable
{
WeakReference _outer; // avoid circular refs between outer and inner object
Type[] _interfaces; // the base's private COM interfaces are here
IntPtr _unkAggregated; // aggregated proxy
public BaseClassComProxy(object outer)
{
_outer = new WeakReference(outer);
_interfaces = outer.GetType().BaseType.GetInterfaces();
var unkOuter = Marshal.GetIUnknownForObject(outer);
try
{
// CreateAggregatedObject does AddRef on this
// se we provide IDispose for proper shutdown
_unkAggregated = Marshal.CreateAggregatedObject(unkOuter, this);
}
finally
{
Marshal.Release(unkOuter);
}
}
public T GetComInterface<T>() where T : class
{
// cast an outer's base interface to an equivalent outer's interface
return (T)Marshal.GetTypedObjectForIUnknown(_unkAggregated, typeof(T));
}
public void GetComInterface<T>(out T baseInterface) where T : class
{
baseInterface = GetComInterface<T>();
}
~BaseClassComProxy()
{
Dispose();
Console.WriteLine("BaseClassComProxy object finalized.");
}
// IDispose
public void Dispose()
{
if (_outer != null)
{
_outer = null;
_interfaces = null;
if (_unkAggregated != IntPtr.Zero)
{
Marshal.Release(_unkAggregated);
_unkAggregated = IntPtr.Zero;
}
}
}
// ICustomQueryInterface
public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
{
// access to the outer's base private COM interfaces
if (_outer != null)
{
var ifaceGuid = iid;
var iface = _interfaces.FirstOrDefault((i) => i.GUID == ifaceGuid);
if (iface != null && iface.IsImport)
{
// must be a COM interface with ComImport attribute
var unk = Marshal.GetComInterfaceForObject(_outer.Target, iface, CustomQueryInterfaceMode.Ignore);
if (unk != IntPtr.Zero)
{
ppv = unk;
return CustomQueryInterfaceResult.Handled;
}
}
}
ppv = IntPtr.Zero;
return CustomQueryInterfaceResult.Failed;
}
}
#endregion
}
class Program
{
static void Main(string[] args)
{
// test
var improved = new NewLibrary.ImprovedClass();
improved.InvokeComMethod();
//// COM client
//var unmanagedObject = (ISimpleUnmanagedObject)Activator.CreateInstance(Type.GetTypeFromProgID("Noseratio.SimpleUnmanagedObject"));
//unmanagedObject.InvokeComMethod(improved);
improved.Dispose();
improved = null;
// test ref counting
GC.Collect(generation: GC.MaxGeneration, mode: GCCollectionMode.Forced, blocking: false);
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
}
// COM test client interfaces
[ComImport(), Guid("2EA68065-8890-4F69-A02F-2BC3F0418561")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
internal interface ISimpleUnmanagedObject
{
void InvokeComMethod([In, MarshalAs(UnmanagedType.Interface)] object arg);
void InvokeComMethodDirect([In] IntPtr comInterface);
}
}
}
Output:
BaseClass.IComInterface.ComMethod ImprovedClass.IComInterface.ComMethod Press Enter to exit. BaseClassComProxy object finalized. ImprovedClass finalized.
这篇关于如何调用在基类中定义的私有 COM 接口的方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!