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

问题描述

我如何可以调用私人COM接口的方法,在基类中定义,从派生类?

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

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

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

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

下面是C#类的BaseClass OldLibrary 组件,它实现 IComInterface 这样的(注意接口被声明为private):

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

最后,这里是一个改进版本, ImprovedClass ,这源于的BaseClass ,但声明并实现它自己版本对 IComInterface ,因为基本的 OldLibrary.IComInterface 不可访问:

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

    }
}

我怎么叫 BaseClass.ComMethod ImprovedClass.ComMethod 没有反映?
我可以使用反射,但在实际使用的情况下 IComInterface 是一些复杂的签名成员的复杂的OLE接口。

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.

我想,因为这两个 BaseClass.IComInterface ImprovedClass.IComInterface 都具有相同GUID的两个COM接口和相同的方法签名,并有 COM类型等效在.NET 4.0+,所以必须有一种方法做什么,我以后是没有反映。

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.

另一个要求是, ImprovedClass 已经从的BaseClass 导出,因为C#的客户端code预计, 的BaseClass ,它传递给COM客户端code的一个实例。因此,的BaseClass 壳内 ImprovedClass 不是一个选项。

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.

一个真实的场景这涉及到从的和 WebBrowserSite 这里描述

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

推荐答案

我想通了,通过使用一个包含辅助对象( BaseClassComProxy )和聚合COM代理对象与 Marshal.CreateAggregatedObject 。这种做法使我有独立的身份非托管的对象,我可以投(有 Marshal.GetTypedObjectForIUnknown ),以 BaseClass.IComInterface 接口,这是不以其他方式可访问的。它适用于任何其他私人COM接口,通过的BaseClass 实施。

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的有关COM身份规则点已经帮助了很多与此研究。感谢埃里克!

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

下面是一个独立的控制台测试应用程序。在code解决与 WebBrowserSite 张贴这里。

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

    }
}

输出:


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

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

查看更多