【问题标题】:Converting between 2 different libraries using the same COM interface in C#在 C# 中使用相同的 COM 接口在 2 个不同的库之间进行转换
【发布时间】:2015-01-01 17:03:39
【问题描述】:

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

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

所以我尝试在它们之间进行如下区分:

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

但这会引发异常。如果我执行以下操作:

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

然后它可以毫无问题地转换,但我不再能够将类传递给 Library2。

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

【问题讨论】:

  • Library1.InterfaceLibrary2.Interface 被 CLR 认为是同一个接口(无论它们的定义看起来是否相同,或者它们是否以某种方式映射到同一个 COM界面)。
  • 您能否扩展“两者略有不同但基本相同”。如果它们不同,它们如何兼容?接口声明该类实现必须支持。如果它们不同,那将如何工作?
  • 为什么不创建一个通用库并从两个库中引用相同的接口?
  • @Belogix:一个接口使用 IntPtr,而另一个接口使用实际的接口定义。
  • @MatiCicero:因为这两个库都不在我的控制之下,很遗憾......

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


【解决方案1】:

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

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

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

【讨论】:

  • 我喜欢这个解决方案 :) 我会尝试一下!
猜你喜欢
  • 2011-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多