使用C#相同的COM接口,2个不同的库之间的转换接口、不同、COM

2023-09-03 00:15:49 作者:早知回不去为何还要分开i

我有一对都使用相同的COM接口库。在一个图书馆中,我有一个实现该接口的类。其他库需要一个对象来实现该接口。

I have a pair of libraries that both use the same COM interface. 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;

然后蒙上没有问题,但我不再能够类传递给Library2。

Then it casts without problem but I am no longer able to pass the class to Library2.

我天真地认为这两个接口具有相同的GUID将prevent这是一个问题,但我似乎是错误的这一点。没有人有任何想法我怎么能在2库之间的转换?或许通过某种形式的元帅?

I naively assumed that both interfaces having the same GUID would prevent this being a problem but I appear to be wrong on that. Does anyone have any idea how I can convert between the 2 libraries? Perhaps via a Marshal of some sort?

推荐答案

这是一个很有趣的问题,我想我可能有它的一个有趣的解决方案。所以,尽管 Library1.Interface Library2.Interface 是两个二进制兼容的 ComImport 接口,它们仍然是两个不同的.NET接口,不能投给对方。

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.

为了使铸件可能,我们需要以某种方式掩盖管理的后面的COM可调用包装(CCW)Library1.Interface .NET对象的身份,然后进行相信这CCW不会被封送到同一个.NET对象。从而使.NET编组将创建一个单独的RCW代理,然后可以转换为 Library2.Interface 作为一个普通的COM对象。

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. 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.

除了使用不同的COM公寓 Library1.Interface Library2.Interface 的对象,我只能想到一个这样做的另一种方式: COM聚集。任何.NET对象可以通过 Marshal.CreateAggregatedObject 作为内部对象。关键是要构建非托管的IUnknown COM对象的身份,以作为聚集外(父)对象。这样的外部对象会给出一个单独的RCW代理从.NET访问时。

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. Any .NET object can be aggregated via Marshal.CreateAggregatedObject as an inner object. The trick is to construct the unmanaged IUnknown COM identity object to serve as an outer (parent) object for aggregation. Such outer object will be given a separate RCW proxy when accessed from .NET.

下面是我拿到这个:

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

整个逻辑作为一个控制台应用程序(COM的二进制协议一定的知识才能理解这个code):

The whole logic as a console app (certain knowledge of COM binary protocols is required to understand this code):

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);
            }
        }
    }
}