简体   繁体   English

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

[英]Converting between 2 different libraries using the same COM interface in C#

I have a pair of libraries that both use the same COM interface. 我有一对使用相同COM接口的库。 In one library I have a class that implements that interface. 在一个库中,我有一个实现该接口的类。 The other library requires an object that implements the interface. 另一个库需要一个实现接口的对象。

However both libraries have their own definition of the interface. 但是两个库都有自己的接口定义。 Both are slightly different but essentially the same interface. 两者略有不同,但基本上是相同的界面。

So I try to case between them as follows: 所以我尝试将它们放在如下:

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

but this raises as an exception. 但这引起了例外。 If I do the following: 如果我执行以下操作:

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

Then it casts without problem but I am no longer able to pass the class to Library2. 然后它没有问题,但我不能再将类传递给Library2。

I naively assumed that both interfaces having the same GUID would prevent this being a problem but I appear to be wrong on that. 我天真地假设两个具有相同GUID的接口都可以防止这是一个问题,但我似乎错了。 Does anyone have any idea how I can convert between the 2 libraries? 有谁知道如何在两个库之间进行转换? Perhaps via a Marshal of some sort? 也许是通过某种元帅?

This is a very interesting problem, and I think I may have an interesting solution for it. 这是一个非常有趣的问题,我想我可能会有一个有趣的解决方案。 So, although Library1.Interface and Library2.Interface are the two binary compatible ComImport interfaces, they're still two different .NET interfaces and cannot be cast to each other. 因此,尽管Library1.InterfaceLibrary2.Interface是两个二进制兼容的ComImport接口,但它们仍然是两个不同的.NET接口,并且不能相互转换。

To make the casting possible, we need to somehow hide the identity of the managed Library1.Interface .NET object behind a COM-callable wrapper (CCW), then make sure this CCW doesn't get marshaled to the same .NET object. 为了使转换成为可能,我们需要以某种方式隐藏COM可调用包装器(CCW)后面的托管Library1.Interface .NET对象的标识,然后确保此CCW不会被封送到同一个.NET对象。 So that the .NET marshaller would create a separate RCW proxy which then could be cast to Library2.Interface as a plain vanilla COM object. 这样.NET编组器就会创建一个单独的RCW代理,然后可以将其作为普通的COM对象Library2.InterfaceLibrary2.Interface

Besides using separate COM apartments for Library1.Interface and Library2.Interface objects, I can only think of one other way of doing this: COM aggregation . 除了为Library1.InterfaceLibrary2.Interface对象使用单独的COM公寓之外,我只能想到另一种方法: COM聚合 Any .NET object can be aggregated via Marshal.CreateAggregatedObject as an inner object. 任何.NET对象都可以通过Marshal.CreateAggregatedObject聚合为内部对象。 The trick is to construct the unmanaged IUnknown COM identity object to serve as an outer (parent) object for aggregation. 诀窍是构造非托管IUnknown COM标识对象以用作聚合的外部(父)对象。 Such outer object will be given a separate RCW proxy when accessed from .NET. 当从.NET访问时,这样的外部对象将被赋予单独的RCW代理。

Below is my take on this: 以下是我对此的看法:

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

The whole logic as a console app (certain knowledge of COM binary protocols is required to understand this code): 作为控制台应用程序的整个逻辑(需要了解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);
            }
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM