在C#中使用相同的COM接口在2个不同的库之间进行转换

我有一对使用相同COM接口的库。 在一个库中,我有一个实现该接口的类。 另一个库需要一个实现接口的对象。

但是两个库都有自己的接口定义。 两者略有不同,但基本上是相同的界面。

所以我试着将它们放在如下:

Library2.Interface intf = (Library2.Interface)impl; 

但这引起了例外。 如果我执行以下操作:

  Library1.Interface intf = (Library1.Interface)impl; 

然后它没有问题,但我不能再将类传递给Library2。

我天真地假设两个具有相同GUID的接口都可以防止这是一个问题,但我似乎错了。 有谁知道如何在两个库之间进行转换? 也许是通过某种元帅?

这是一个非常有趣的问题,我想我可能会有一个有趣的解决方案。 因此,尽管Library1.InterfaceLibrary2.Interface是两个二进制兼容的ComImport接口,但它们仍然是两个不同的.NET接口,并且不能相互转换。

为了使转换成为可能,我们需要以某种方式隐藏COM可调用包装器(CCW)后面的托管Library1.Interface .NET对象的标识,然后确保此CCW不会被封送到同一个.NET对象。 这样.NET编组器就会创建一个单独的RCW代理,然后可以将其作为普通的COM对象Library2.InterfaceLibrary2.Interface

除了为Library1.InterfaceLibrary2.Interface对象使用单独的COM公寓之外,我只能想到另一种方法: COM聚合 。 任何.NET对象都可以通过Marshal.CreateAggregatedObject聚合为内部对象。 诀窍是构造非托管IUnknown COM标识对象以用作聚合的外(父)对象。 当从.NET访问时,这样的外部对象将被赋予单独的RCW代理。

以下是我对此的看法:

 var server = ComWrapper.Create(() => new Library1.Server()); var client = new Library2.Client(); client.CallMethod(server); 

整个逻辑作为控制台应用程序(需要了解COM二进制协议的某些知识才能理解此代码):

 using System; using System.Runtime.InteropServices; namespace Library1 { [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("4C08A691-5D61-4E9A-B16D-75BAD2834BAE")] public interface Interface { void TestMethod(); } [ComVisible(true)] public class Server : Interface { public Server() { } public void TestMethod() { Console.WriteLine("TestMethod called"); } } } namespace Library2 { [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("4C08A691-5D61-4E9A-B16D-75BAD2834BAE")] public interface Interface { void TestMethod(); } public class Client { public void CallMethod(Library2.Interface server) { server.TestMethod(); } } } namespace TestApp { class Program { static void Main(string[] args) { // convert Library1.Server to Library2.Interface var server = ComWrapper.Create(() => new Library1.Server()); var client = new Library2.Client(); client.CallMethod(server); Marshal.ReleaseComObject(server); Console.ReadLine(); } } ///  /// ComWrapper - http://stackoverflow.com/q/26758316/1768303 /// by Noseratio ///  public class ComWrapper { readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046"); const int S_OK = 0; const int E_FAIL = unchecked((int)0x80004005); delegate int QueryInterfaceMethod(IntPtr pUnk, ref Guid iid, out IntPtr ppv); delegate int AddRefMethod(IntPtr pUnk); delegate int ReleaseMethod(IntPtr pUnk); [StructLayout(LayoutKind.Sequential)] struct UnkObject { public IntPtr pVtable; } [StructLayout(LayoutKind.Sequential)] struct UnkVtable { public IntPtr pQueryInterface; public IntPtr pAddRef; public IntPtr pRelease; } int _refCount = 0; IntPtr _pVtable; IntPtr _outerObject; IntPtr _aggregatedObject; GCHandle _gcHandle; QueryInterfaceMethod _queryInterfaceMethod; AddRefMethod _addRefMethod; ReleaseMethod _releaseMethod; private ComWrapper() { } ~ComWrapper() { Console.WriteLine("~ComWrapper"); Free(); } private IntPtr Initialize(Func createInnerObject) { try { // implement IUnknown methods _queryInterfaceMethod = delegate(IntPtr pUnk, ref Guid iid, out IntPtr ppv) { lock (this) { // delegate anything but IID_IUnknown to the aggregated object if (IID_IUnknown == iid) { ppv = _outerObject; Marshal.AddRef(_outerObject); return S_OK; } return Marshal.QueryInterface(_aggregatedObject, ref iid, out ppv); } }; _addRefMethod = delegate(IntPtr pUnk) { lock (this) { return ++_refCount; } }; _releaseMethod = delegate(IntPtr pUnk) { lock (this) { if (0 == --_refCount) { Free(); } return _refCount; } }; // create the IUnknown vtable var vtable = new UnkVtable(); vtable.pQueryInterface = Marshal.GetFunctionPointerForDelegate(_queryInterfaceMethod); vtable.pAddRef = Marshal.GetFunctionPointerForDelegate(_addRefMethod); vtable.pRelease = Marshal.GetFunctionPointerForDelegate(_releaseMethod); _pVtable = Marshal.AllocCoTaskMem(Marshal.SizeOf(vtable)); Marshal.StructureToPtr(vtable, _pVtable, false); // create the IUnknown object var unkObject = new UnkObject(); unkObject.pVtable = _pVtable; _outerObject = Marshal.AllocCoTaskMem(Marshal.SizeOf(unkObject)); Marshal.StructureToPtr(unkObject, _outerObject, false); // pin the managed ComWrapper instance _gcHandle = GCHandle.Alloc(this, GCHandleType.Normal); // create and aggregate the inner object _aggregatedObject = Marshal.CreateAggregatedObject(_outerObject, createInnerObject()); return _outerObject; } catch { Free(); throw; } } private void Free() { Console.WriteLine("Free"); if (_aggregatedObject != IntPtr.Zero) { Marshal.Release(_aggregatedObject); _aggregatedObject = IntPtr.Zero; } if (_pVtable != IntPtr.Zero) { Marshal.FreeCoTaskMem(_pVtable); _pVtable = IntPtr.Zero; } if (_outerObject != IntPtr.Zero) { Marshal.FreeCoTaskMem(_outerObject); _outerObject = IntPtr.Zero; } if (_gcHandle.IsAllocated) { _gcHandle.Free(); } } public static T Create(Func createInnerObject) { var wrapper = new ComWrapper(); var unk = wrapper.Initialize(createInnerObject); Marshal.AddRef(unk); try { var comObject = Marshal.GetObjectForIUnknown(unk); return (T)comObject; } finally { Marshal.Release(unk); } } } }