我有一对使用相同COM接口的库。在一个库中,我有一个实现该接口的类。另一个库需要一个实现接口的对象。
但是两个库都有自己的接口定义。两者略有不同,但基本上是相同的界面。
所以我试着在它们之间进行如下处理:
Library2.Interface intf = (Library2.Interface)impl;
但这引起了例外。如果我执行以下操作:
Library1.Interface intf = (Library1.Interface)impl;
然后它没有问题但是我无法再将类传递给Library2。
我天真地假设两个具有相同GUID的接口都可以防止这是一个问题,但我似乎错了。有谁知道如何在两个库之间进行转换?也许是通过某种元帅?
答案 0 :(得分:7)
这是一个非常有趣的问题,我想我可能会有一个有趣的解决方案。因此,尽管Library1.Interface
和Library2.Interface
是两个二进制兼容的ComImport
接口,但它们仍然是两个不同的.NET接口,并且不能相互转换。
为了使转换成为可能,我们需要以某种方式隐藏COM可调用包装器(CCW)后面的托管Library1.Interface
.NET对象的标识,然后确保此CCW不会被编组到同一个.NET对象。因此.NET编组器会创建一个单独的RCW代理,然后可以将其作为普通的COM对象强制转换为Library2.Interface
。
除了为Library1.Interface
和Library2.Interface
对象使用单独的COM公寓之外,我只能想到另一种方法:COM aggregation。任何.NET对象都可以通过Marshal.CreateAggregatedObject
作为内部对象进行聚合。诀窍是构造非托管IUnknown
COM标识对象以用作聚合的外(父)对象。当从.NET访问时,这样的外部对象将被赋予单独的RCW代理。
以下是对此的看法:
var server = ComWrapper.Create<Library2.Interface>(() => 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<Library2.Interface>(() => new Library1.Server());
var client = new Library2.Client();
client.CallMethod(server);
Marshal.ReleaseComObject(server);
Console.ReadLine();
}
}
/// <summary>
/// ComWrapper - http://stackoverflow.com/q/26758316/1768303
/// by Noseratio
/// </summary>
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<object> 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<T>(Func<object> 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);
}
}
}
}