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

时间:2014-11-05 13:22:51

标签: c# .net casting com com-interop

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

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

所以我试着在它们之间进行如下处理:

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

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

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

然后它没有问题但是我无法再将类传递给Library2。

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

1 个答案:

答案 0 :(得分:7)

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

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

除了为Library1.InterfaceLibrary2.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);
            }
        }
    }
}