在 C# 中使用相同的 COM 接口在 2 个不同的库之间进行转换 [英] Converting between 2 different libraries using the same COM interface in C#

查看:14
本文介绍了在 C# 中使用相同的 COM 接口在 2 个不同的库之间进行转换的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个库都使用相同的 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 的两个界面可以防止这成为问题,但我似乎错了.有谁知道如何在这两个库之间进行转换?也许通过某种元帅?

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

为了使转换成为可能,我们需要以某种方式将托管 Library1.Interface .NET 对象的身份隐藏在 COM 可调用包装器 (CCW) 后面,然后确保该 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.

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

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 二进制协议知识):

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

这篇关于在 C# 中使用相同的 COM 接口在 2 个不同的库之间进行转换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆