本文介绍了如何调用在基类中定义的私有 COM 接口的方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何从派生类调用在基类中定义的私有 COM 接口的方法?

例如这里是COM接口,IComInterface(IDL):

[uuid(9AD16CCE-7588-486C-BC56-F3161FF92EF2),ole自动化]接口 IComInterface: IUnknown{HRESULT ComMethod([in] IUnknown* arg);}

这是来自 OldLibrary 程序集的 C# 类 BaseClass,它像这样实现 IComInterface(注意接口被声明为私有):

//程序集OldLibrary"公共静态类 OldLibrary{[ComImport(), Guid("9AD16CCE-7588-486C-BC56-F3161FF92EF2")][接口类型(ComInterfaceType.InterfaceIsIUnknown)]私有接口 IComInterface{void ComMethod([In, MarshalAs(UnmanagedType.Interface)] 对象 arg);}[ComVisible(真)][ClassInterface(ClassInterfaceType.None)]公共类 BaseClass : IComInterface{无效 IComInterface.ComMethod(对象 arg){Console.WriteLine("BaseClass.IComInterface.ComMethod");}}}

最后,这是一个改进的版本,ImprovedClass,它派生自 BaseClass,但声明并实现了自己的 IComInterface 版本,因为base 的 OldLibrary.IComInterface 无法访问:

//程序集NewLibrary"公共静态类 NewLibrary{[ComImport(), Guid("9AD16CCE-7588-486C-BC56-F3161FF92EF2")][接口类型(ComInterfaceType.InterfaceIsIUnknown)]私有接口 IComInterface{void ComMethod([In, MarshalAs(UnmanagedType.Interface)] 对象 arg);}[ComVisible(真)][ClassInterface(ClassInterfaceType.None)]公共类改进类:OldLibrary.BaseClass,ICom接口,自定义查询接口{//ICom接口无效 IComInterface.ComMethod(对象 arg){Console.WriteLine("ImprovedClass.IComInterface.ComMethod");//我如何在这里调用 base.ComMethod,//除了通过反射?}//ICustomQuery接口公共 CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv){if (iid == typeof(IComInterface).GUID){ppv = Marshal.GetComInterfaceForObject(this, typeof(IComInterface), CustomQueryInterfaceMode.Ignore);返回 CustomQueryInterfaceResult.Handled;}ppv = IntPtr. 零;返回 CustomQueryInterfaceResult.NotHandled;}}}

我如何在没有反射的情况下从 ImprovedClass.ComMethod 调用 BaseClass.ComMethod?
我可以使用反射,但实际上是用例 IComInterface 是一个复杂的 OLE 接口,具有许多复杂签名的成员.

我认为是因为 BaseClass.IComInterfaceImprovedClass.IComInterface 都是 COM 接口,具有相同的 GUID 和相同的方法签名,并且有 COM 类型等效,所以必须有一种方法来做我想做的事没有反思.

另一个要求是 ImprovedClass 必须从 BaseClass 派生,因为 C# 客户端代码需要 BaseClass 的实例,它通过到 COM 客户端代码.因此,在 ImprovedClass 中包含 BaseClass 不是一种选择.

[已编辑] 涉及从 WebBrowserWebBrowserSite 描述 这里.

解决方案

我想通了,通过使用一个 helper 包含的对象 (BaseClassComProxy) 和一个聚合的 COM 代理对象,用 .这种方法为我提供了一个具有单独标识的非托管对象,我可以对其进行转换(使用 Marshal.GetTypedObjectForIUnknown) 到我自己的 BaseClass.IComInterface 接口的等效版本,即否则无法访问.它适用于任何其他私有 COM 接口,由 BaseClass 实现.

@EricBrown 关于 COM 身份规则的观点对这项研究有很大帮助.谢谢埃里克!

这是一个独立的控制台测试应用.WebBrowserSite 解决原始问题的代码发布在此处.

使用系统;使用 System.Diagnostics;使用 System.Linq;使用 System.Runtime.InteropServices;命名空间 ManagedServer{/*//IComInterface IDL 定义[uuid(9AD16CCE-7588-486C-BC56-F3161FF92EF2),ole自动化]接口 IComInterface: IUnknown{HRESULT ComMethod(IUnknown* arg);}*///旧库公共静态类 OldLibrary{//私有 COM 接口 IComInterface[ComImport(), Guid("9AD16CCE-7588-486C-BC56-F3161FF92EF2")][接口类型(ComInterfaceType.InterfaceIsIUnknown)]私有接口 IComInterface{void ComMethod([In, MarshalAs(UnmanagedType.Interface)] 对象 arg);}[ComVisible(真)][ClassInterface(ClassInterfaceType.None)]公共类 BaseClass : IComInterface{无效 IComInterface.ComMethod(对象 arg){Console.WriteLine("BaseClass.IComInterface.ComMethod");}}}//新建库公共静态类 NewLibrary{//OldLibrary.IComInterface 在这里不可访问,//定义一个新的等效版本[ComImport(), Guid("9AD16CCE-7588-486C-BC56-F3161FF92EF2")][接口类型(ComInterfaceType.InterfaceIsIUnknown)]私有接口 IComInterface{void ComMethod([In, MarshalAs(UnmanagedType.Interface)] 对象 arg);}[ComVisible(真)][ClassInterface(ClassInterfaceType.None)]公共类改进类:OldLibrary.BaseClass,NewLibrary.IComInterface,ICustomQuery接口,一次性{NewLibrary.IComInterface _baseIComInterface;BaseClassComProxy _baseClassComProxy;//ICom接口//我们要调用只能通过 COM 访问的 BaseClass.IComInterface.ComMethod无效 IComInterface.ComMethod(对象 arg){_baseIComInterface.ComMethod(arg);Console.WriteLine("ImprovedClass.IComInterface.ComMethod");}//ICustomQuery接口公共 CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv){if (iid == typeof(NewLibrary.IComInterface).GUID){//CustomQueryInterfaceMode.Ignore 是为了避免 QI 期间的无限循环.ppv = Marshal.GetComInterfaceForObject(this, typeof(NewLibrary.IComInterface), CustomQueryInterfaceMode.Ignore);返回 CustomQueryInterfaceResult.Handled;}ppv = IntPtr. 零;返回 CustomQueryInterfaceResult.NotHandled;}//构造函数公共改进类(){//将 CCW 对象与辅助 Inner 对象聚合_baseClassComProxy = new BaseClassComProxy(this);_baseIComInterface = _baseClassComProxy.GetComInterface<IComInterface>();}〜改进类(){处置();Console.WriteLine("ImprovedClass finalized.");}//IDispose公共无效处置(){//我们可能有对自身的周期性 COM 引用//例如,通过 _baseIComInterface//确保释放所有引用if (_baseIComInterface != null){Marshal.ReleaseComObject(_baseIComInterface);_baseIComInterface = null;}if (_baseClassComProxy != null){_baseClassComProxy.Dispose();_baseClassComProxy = null;}}//供测试用公共无效 InvokeComMethod(){((NewLibrary.IComInterface)this).ComMethod(null);}}#region BaseClassComProxy//内部作为聚合对象类 BaseClassComProxy :ICustomQuery接口,一次性{弱引用_outer;//避免外部和内部对象之间的循环引用类型[] _interfaces;//base 的私有 COM 接口在这里IntPtr _unkAggregated;//聚合代理公共 BaseClassComProxy(对象外部){_outer = new WeakReference(outer);_interfaces = outer.GetType().BaseType.GetInterfaces();var unkOuter = Marshal.GetIUnknownForObject(outer);尝试{//CreateAggregatedObject 对此进行 AddRef//我们为正确关闭提供了 IDispose_unkAggregated = Marshal.CreateAggregatedObject(unkOuter, this);}最后{Marshal.Release(unkOuter);}}public T GetComInterface<T>() 其中 T : 类{//将外部的基本接口转换为等效的外部接口返回 (T)Marshal.GetTypedObjectForIUnknown(_unkAggregated, typeof(T));}public void GetComInterface<T>(out T baseInterface) where T : class{baseInterface = GetComInterface<T>();}~BaseClassComProxy(){处置();Console.WriteLine("BaseClassComProxy 对象完成.");}//IDispose公共无效处置(){if (_outer != null){_outer = null;_interfaces = null;if (_unkAggregated != IntPtr.Zero){Marshal.Release(_unkAggregated);_unkAggregated = IntPtr.Zero;}}}//ICustomQuery接口公共 CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv){//访问外部的基本私有 COM 接口if (_outer != null){var ifaceGuid = iid;var iface = _interfaces.FirstOrDefault((i) => i.GUID == ifaceGuid);if (iface != null && iface.IsImport){//必须是具有 ComImport 属性的 COM 接口var unk = Marshal.GetComInterfaceForObject(_outer.Target, iface, CustomQueryInterfaceMode.Ignore);if (unk != IntPtr.Zero){ppv = unk;返回 CustomQueryInterfaceResult.Handled;}}}ppv = IntPtr. 零;返回 CustomQueryInterfaceResult.Failed;}}#endregion}课堂节目{静态无效主要(字符串 [] 参数){//测试var 改进 = 新的 NewLibrary.ImprovedClass();改进的.InvokeComMethod();////COM 客户端//var unmanagedObject = (ISimpleUnmanagedObject)Activator.CreateInstance(Type.GetTypeFromProgID("Noseratio.SimpleUnmanagedObject"));//unmanagedObject.InvokeComMethod(改进);改进的.Dispose();改进=空;//测试引用计数GC.Collect(生成:GC.MaxGeneration,模式:GCCollectionMode.Forced,阻塞:false);Console.WriteLine("按 Enter 退出.");Console.ReadLine();}//COM 测试客户端接口[ComImport(), Guid("2EA68065-8890-4F69-A02F-2BC3F0418561")][接口类型(ComInterfaceType.InterfaceIsDual)]内部接口 ISimpleUnmanagedObject{void InvokeComMethod([In, MarshalAs(UnmanagedType.Interface)] 对象 arg);void InvokeComMethodDirect([In] IntPtr comInterface);}}}

输出:

BaseClass.IComInterface.ComMethod改进的Class.IComInterface.ComMethod按 Enter 退出.BaseClassComProxy 对象最终确定.改进类最终确定.

How can I invoke a method of a private COM interface, defined in a base class, from a derived class?

For example, here is the COM interface, IComInterface (IDL):

[
    uuid(9AD16CCE-7588-486C-BC56-F3161FF92EF2),
    oleautomation
]
interface IComInterface: IUnknown
{
    HRESULT ComMethod([in] IUnknown* arg);
}

Here's the C# class BaseClass from OldLibrary assembly, which implements IComInterface like this (note the interface is declared as private):

// Assembly "OldLibrary"
public static class OldLibrary
{
    [ComImport(), Guid("9AD16CCE-7588-486C-BC56-F3161FF92EF2")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IComInterface
    {
        void ComMethod([In, MarshalAs(UnmanagedType.Interface)] object arg);
    }

    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    public class BaseClass : IComInterface
    {
        void IComInterface.ComMethod(object arg)
        {
            Console.WriteLine("BaseClass.IComInterface.ComMethod");
        }
    }
}

Finally, here's an improved version, ImprovedClass, which derives from BaseClass, but declares and implement its own version of IComInterface, because the base's OldLibrary.IComInterface is inaccessible:

// Assembly "NewLibrary"
public static class NewLibrary
{
    [ComImport(), Guid("9AD16CCE-7588-486C-BC56-F3161FF92EF2")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IComInterface
    {
        void ComMethod([In, MarshalAs(UnmanagedType.Interface)] object arg);
    }

    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    public class ImprovedClass :
        OldLibrary.BaseClass,
        IComInterface,
        ICustomQueryInterface
    {
        // IComInterface
        void IComInterface.ComMethod(object arg)
        {
            Console.WriteLine("ImprovedClass.IComInterface.ComMethod");
            // How do I call base.ComMethod here,
            // otherwise than via reflection?
        }

        // ICustomQueryInterface
        public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
        {
            if (iid == typeof(IComInterface).GUID)
            {
                ppv = Marshal.GetComInterfaceForObject(this, typeof(IComInterface), CustomQueryInterfaceMode.Ignore);
                return CustomQueryInterfaceResult.Handled;
            }
            ppv = IntPtr.Zero;
            return CustomQueryInterfaceResult.NotHandled;
        }

    }
}

How do I call BaseClass.ComMethod from ImprovedClass.ComMethod without reflection?
I could use reflection, but in the real use case IComInterface is a complex OLE interface with a number of members of complex signatures.

I thought that because both BaseClass.IComInterface and ImprovedClass.IComInterface are both COM interfaces with the same GUID and identical method signatures, and there's COM Type Equivalence in .NET 4.0+, so there has to be a way to do what I'm after without reflection.

Another requirement is that ImprovedClass has to be derived from BaseClass, because the C# client code expects an instance of BaseClass, which it passes to the COM client code. Thus, containment of BaseClass inside ImprovedClass is not an option.

[EDITED] A real-life scenario which involves deriving from WebBrowser and WebBrowserSite is described here.

解决方案

I figured it out, by using a helper contained object (BaseClassComProxy) and an aggregated COM proxy object, created with Marshal.CreateAggregatedObject. This approach gives me an unmanaged object with separate identity, which I can cast (with Marshal.GetTypedObjectForIUnknown) to my own equivalent version of BaseClass.IComInterface interface, which is not otherwise accessible. It works for any other private COM interfaces, implemented by BaseClass.

@EricBrown's points about COM identity rules have helped a lot with this research. Thanks Eric!

Here's a standalone console test app. The code solving the original problem with WebBrowserSite is posted here.

using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;

namespace ManagedServer
{
    /*
    // IComInterface IDL definition
    [
        uuid(9AD16CCE-7588-486C-BC56-F3161FF92EF2),
        oleautomation
    ]
    interface IComInterface: IUnknown
    {
        HRESULT ComMethod(IUnknown* arg);
    }
    */

    // OldLibrary
    public static class OldLibrary
    {
        // private COM interface IComInterface
        [ComImport(), Guid("9AD16CCE-7588-486C-BC56-F3161FF92EF2")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IComInterface
        {
            void ComMethod([In, MarshalAs(UnmanagedType.Interface)] object arg);
        }

        [ComVisible(true)]
        [ClassInterface(ClassInterfaceType.None)]
        public class BaseClass : IComInterface
        {
            void IComInterface.ComMethod(object arg)
            {
                Console.WriteLine("BaseClass.IComInterface.ComMethod");
            }
        }
    }

    // NewLibrary
    public static class NewLibrary
    {
        // OldLibrary.IComInterface is inaccessible here,
        // define a new equivalent version
        [ComImport(), Guid("9AD16CCE-7588-486C-BC56-F3161FF92EF2")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IComInterface
        {
            void ComMethod([In, MarshalAs(UnmanagedType.Interface)] object arg);
        }

        [ComVisible(true)]
        [ClassInterface(ClassInterfaceType.None)]
        public class ImprovedClass :
            OldLibrary.BaseClass,
            NewLibrary.IComInterface,
            ICustomQueryInterface,
            IDisposable
        {
            NewLibrary.IComInterface _baseIComInterface;
            BaseClassComProxy _baseClassComProxy;

            // IComInterface
            // we want to call BaseClass.IComInterface.ComMethod which is only accessible via COM
            void IComInterface.ComMethod(object arg)
            {
                _baseIComInterface.ComMethod(arg);
                Console.WriteLine("ImprovedClass.IComInterface.ComMethod");
            }

            // ICustomQueryInterface
            public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
            {
                if (iid == typeof(NewLibrary.IComInterface).GUID)
                {
                    // CustomQueryInterfaceMode.Ignore is to avoid infinite loop during QI.
                    ppv = Marshal.GetComInterfaceForObject(this, typeof(NewLibrary.IComInterface), CustomQueryInterfaceMode.Ignore);
                    return CustomQueryInterfaceResult.Handled;
                }
                ppv = IntPtr.Zero;
                return CustomQueryInterfaceResult.NotHandled;
            }

            // constructor
            public ImprovedClass()
            {
                // aggregate the CCW object with the helper Inner object
                _baseClassComProxy = new BaseClassComProxy(this);
                _baseIComInterface = _baseClassComProxy.GetComInterface<IComInterface>();
            }

            ~ImprovedClass()
            {
                Dispose();
                Console.WriteLine("ImprovedClass finalized.");
            }

            // IDispose
            public void Dispose()
            {
                // we may have recicular COM references to itself
                // e.g., via _baseIComInterface
                // make sure to release all references

                if (_baseIComInterface != null)
                {
                    Marshal.ReleaseComObject(_baseIComInterface);
                    _baseIComInterface = null;
                }

                if (_baseClassComProxy != null)
                {
                    _baseClassComProxy.Dispose();
                    _baseClassComProxy = null;
                }
            }

            // for testing
            public void InvokeComMethod()
            {
                ((NewLibrary.IComInterface)this).ComMethod(null);
            }
        }

        #region BaseClassComProxy
        // Inner as aggregated object
        class BaseClassComProxy :
            ICustomQueryInterface,
            IDisposable
        {
            WeakReference _outer; // avoid circular refs between outer and inner object
            Type[] _interfaces; // the base's private COM interfaces are here
            IntPtr _unkAggregated; // aggregated proxy

            public BaseClassComProxy(object outer)
            {
                _outer = new WeakReference(outer);
                _interfaces = outer.GetType().BaseType.GetInterfaces();
                var unkOuter = Marshal.GetIUnknownForObject(outer);
                try
                {
                    // CreateAggregatedObject does AddRef on this
                    // se we provide IDispose for proper shutdown
                    _unkAggregated = Marshal.CreateAggregatedObject(unkOuter, this);
                }
                finally
                {
                    Marshal.Release(unkOuter);
                }
            }

            public T GetComInterface<T>() where T : class
            {
                // cast an outer's base interface to an equivalent outer's interface
                return (T)Marshal.GetTypedObjectForIUnknown(_unkAggregated, typeof(T));
            }

            public void GetComInterface<T>(out T baseInterface) where T : class
            {
                baseInterface = GetComInterface<T>();
            }

            ~BaseClassComProxy()
            {
                Dispose();
                Console.WriteLine("BaseClassComProxy object finalized.");
            }

            // IDispose
            public void Dispose()
            {
                if (_outer != null)
                {
                    _outer = null;
                    _interfaces = null;
                    if (_unkAggregated != IntPtr.Zero)
                    {
                        Marshal.Release(_unkAggregated);
                        _unkAggregated = IntPtr.Zero;
                    }
                }
            }

            // ICustomQueryInterface
            public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
            {
                // access to the outer's base private COM interfaces
                if (_outer != null)
                {
                    var ifaceGuid = iid;
                    var iface = _interfaces.FirstOrDefault((i) => i.GUID == ifaceGuid);
                    if (iface != null && iface.IsImport)
                    {
                        // must be a COM interface with ComImport attribute
                        var unk = Marshal.GetComInterfaceForObject(_outer.Target, iface, CustomQueryInterfaceMode.Ignore);
                        if (unk != IntPtr.Zero)
                        {
                            ppv = unk;
                            return CustomQueryInterfaceResult.Handled;
                        }
                    }
                }
                ppv = IntPtr.Zero;
                return CustomQueryInterfaceResult.Failed;
            }
        }
        #endregion

    }

    class Program
    {
        static void Main(string[] args)
        {
            // test
            var improved = new NewLibrary.ImprovedClass();
            improved.InvokeComMethod();

            //// COM client
            //var unmanagedObject = (ISimpleUnmanagedObject)Activator.CreateInstance(Type.GetTypeFromProgID("Noseratio.SimpleUnmanagedObject"));
            //unmanagedObject.InvokeComMethod(improved);

            improved.Dispose();
            improved = null;

            // test ref counting
            GC.Collect(generation: GC.MaxGeneration, mode: GCCollectionMode.Forced, blocking: false);
            Console.WriteLine("Press Enter to exit.");
            Console.ReadLine();
        }

        // COM test client interfaces
        [ComImport(), Guid("2EA68065-8890-4F69-A02F-2BC3F0418561")]
        [InterfaceType(ComInterfaceType.InterfaceIsDual)]
        internal interface ISimpleUnmanagedObject
        {
            void InvokeComMethod([In, MarshalAs(UnmanagedType.Interface)] object arg);
            void InvokeComMethodDirect([In] IntPtr comInterface);
        }

    }
}

Output:

BaseClass.IComInterface.ComMethod
ImprovedClass.IComInterface.ComMethod
Press Enter to exit.
BaseClassComProxy object finalized.
ImprovedClass finalized.

这篇关于如何调用在基类中定义的私有 COM 接口的方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-22 20:50
查看更多