繁体   English   中英

如何调用在基类中定义的专用COM接口的方法?

[英]How to invoke a method of a private COM interfaces, defined in a base class?

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

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

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

这是OldLibrary程序集的C#类BaseClass ,它像这样实现IComInterface (请注意,接口被声明为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

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

    }
}

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

我以为,因为BaseClass.IComInterfaceImprovedClass.IComInterface都是具有相同GUID和相同方法签名的COM接口,并且.NET 4.0+中存在COM类型等效项 ,因此必须有一种方法来执行我要执行的操作没有反思。

另一个要求是, ImprovedClass必须从BaseClass派生,因为C#客户端代码需要BaseClass的实例,并将其传递给COM客户端代码。 因此,不能将BaseClassImprovedClass中。

[编辑] 此处描述涉及从WebBrowserWebBrowserSite派生的真实场景。

我已经习惯了在C ++中执行此操作,因此在这里我会从心理上将C ++转换为C#。 (即,您可能需要进行一些调整。)

COM身份规则要求对象上的接口集必须是静态的。 因此,如果您可以获得某些肯定由BaseClass实现的接口,则可以关闭该接口的QI,以获取BaseClassIComInterface实现。

因此,如下所示:

type typeBaseIComInterface = typeof(OldLibrary.BaseClass).GetInterfaces().First((t) => t.GUID == typeof(IComInterface).GUID); 
IntPtr unkBaseIComInterface = Marshal.GetComInterfaceForObject(this, typeBaseIComInterface, CustomQueryInterfaceMode.Ignore);
dynamic baseptr = Marshal.GetTypedObjectForIUnknown(unkBaseIComInterface, typeof(OldLibrary.BaseClass);
baseptr.ComMethod(/* args go here */);

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

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

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

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.

这是我的解决方案。 好的,它使用反射,但是我看不出问题出在哪里,因为它要简单得多,最终的用法实际上只是一行代码,如下所示:

// IComInterface
void IComInterface.ComMethod(object arg)
{
    InvokeBaseMethod(this, "ComMethod", typeof(OldLibrary.BaseClass), typeof(IComInterface), arg);
}

实用程序方法(可用于任何类)是这样的:

public static object InvokeBaseMethod(object obj, string methodName, Type baseType, Type equivalentBaseInterface, params object[] arguments)
{
    Type baseInterface = baseType.GetInterfaces().First((t) => t.GUID == equivalentBaseInterface.GUID);
    ComMemberType type = ComMemberType.Method;
    int methodSlotNumber = Marshal.GetComSlotForMethodInfo(equivalentBaseInterface.GetMethod(methodName));
    MethodInfo baseMethod = (MethodInfo)Marshal.GetMethodInfoForComSlot(baseInterface, methodSlotNumber, ref type);
    return baseMethod.Invoke(obj, arguments);
}

您需要使用ICustomMarshaler 我只是想出了这个解决方案,它比您拥有的解决方案复杂得多,并且没有任何思考。 据我所知, ICustomMarshaler是显式控制托管对象的神奇功能(如RCW代理)的唯一方法,在这种情况下,可以将它们动态地转换为它们不提供的托管接口指针似乎明确实施。

对于我将演示的完整场景,黑体字项引用了示例的相关部分。

情境

您是通过接收非托管接口指针( 朋克 )到您的托管代码COM interop功能(如MFCreateMediaSession),也许是以前使用的出色互操作属性([MarshalAs(UnmanagedType.Interface)] out IMFMediaSession pSess, ...以接收一个托管接口( IMFMediaSession )。您想通过提供自己的托管类( session )在这种情况下获得的__COM对象的“改进”(如您所说):

  1. 可能添加一些其他接口(例如IMFAsyncCallback );
  2. 不需要您转发或重新实现您已经拥有的接口;
  3. 在单个RCW中将非托管接口的寿命与托管接口合并
  4. 不存储任何无关的接口指针...

关键是更改获取非托管对象的函数上的封送处理指令,以使其使用自定义封送处理器 如果p/Invoke定义不在您控制的外部库中,则可以制作自己的本地副本。 这就是我在这里所做的,在这里我用新属性替换了[Out, MarshalAs(UnmanagedType.Interface)]

    [DllImport("mf.dll", ExactSpelling = true), SuppressUnmanagedCodeSecurity]
    static extern HResult MFCreateMediaSession(
        [In] IMFAttributes pConfiguration,
        [Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MFSessionMarshaler))] out IMFMediaSession ppMediaSession
        );

要部署您自己的具有上述“神奇”界面行为的类,您需要两个类:一个抽象基类,该基类必须标记为[ComImport] (即使实际上不是),以提供RCW管道,加上我显示的其他属性(创建自己的GUID),然后是派生类,您可以在其中放置所需的任何增强功能。

这里要注意的是, 基类 (在我的示例中为_session和派生类session )都不会显式列出您希望其从非托管IUnknown代理的接口。 任何复制QueryInterface版本的“适当”接口定义都将具有优先权,并破坏您通过强制转换轻松调用非托管“基本”方法的能力。 您将回到COM插槽并_vtbl登陆。

这也意味着,在派生类的实例上,您将只能通过强制转换来访问导入的接口。 派生类可以按通常方式实现其他“额外”接口。 顺便说一下,那些也可以导入COM接口。

这是我刚刚描述的两个类,它们说明了您的应用程序内容的去向。 请注意,与必须通过一个或多个成员变量(必须进行初始化和清理等)转发一个巨大的接口相比,它们的整洁程度与之相比。

[ComImport, SuppressUnmanagedCodeSecurity, Guid("c6646f0a-3d96-4ac2-9e3f-8ae2a11145ce")]
[ClassInterface(ClassInterfaceType.None)]
public abstract class _session
{
}

public class session : _session, IMFAsyncCallback
{
    HResult IMFAsyncCallback.GetParameters(out MFASync pdwFlags, out MFAsyncCallbackQueue pdwQueue)
    {
        /// add-on interfaces can use explicit implementation...
    }

    public HResult Invoke([In, MarshalAs(UnmanagedType.Interface)] IMFAsyncResult pAsyncResult)
    {
        /// ...or public.
    }
}

接下来是ICustomMarshaler实现。 因为我们标记为使用此参数的参数是out参数,所以永远不会调用此类的托管本机函数。 要实现的主要功能是MarshalNativeToManaged ,在这里我使用GetTypedObjectForIUnknown来指定我定义的派生类( 会话 )。 即使该类未实现IMFMediaSession ,您也可以通过强制转换获得该非托管接口。

目前,我最好的猜测是调用CleanUpNativeData调用中的Release (如果错了,我会再来编辑这篇文章)。

class MFSessionMarshaler : ICustomMarshaler
{
    static ICustomMarshaler GetInstance(String _) => new MFSessionMarshaler();

    public Object MarshalNativeToManaged(IntPtr pUnk) => Marshal.GetTypedObjectForIUnknown(pUnk, typeof(session));

    public void CleanUpNativeData(IntPtr pNativeData) => Marshal.Release(pNativeData);

    public int GetNativeDataSize() => -1;
    IntPtr ICustomMarshaler.MarshalManagedToNative(Object _) => IntPtr.Zero;
    void ICustomMarshaler.CleanUpManagedData(Object ManagedObj) { } }

在这里,我们看到了.NET中为数不多的地方之一,我知道您可以(暂时)违反类型安全。 因为请注意ppMediaSession作为out IMFMediaSession ppMediaSession的成熟的,强类型的参数out IMFMediaSession ppMediaSession封送处理程序中弹出到代码中,但是在自定义封送处理代码中,它肯定不是立即起作用(即强制转换)。

现在您可以开始了。 以下是一些示例,展示了如何使用它,并演示了按预期方式运行:

IMFMediaSession pI;
MFCreateMediaSession(null, out pI);  // get magical RCW

var rcw = (session)pI;   // we happen to know what it really is

pI.ClearTopologies();    // you can call IMFMediaSession members...

((IMFAsyncCallback)pI).Invoke(null);  // and also IMFAsyncCallback.
rcw.Invoke(null);        // same thing, via the backing object

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM