WebBrowserSite:如何在派生类中调用私有 COM 接口方法?

WebBrowserSite: how to call a private COM interface method in a derived class?(WebBrowserSite:如何在派生类中调用私有 COM 接口方法?)

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

问题描述

这是挑战.我来自框架的 WebBrowserSite 类.我的派生类的实例 ImprovedWebBrowserSite 通过 WebBrowser.CreateWebBrowserSiteBase,我在派生版本的 WebBrowser 类- 专门提供一个自定义站点对象.框架的 WebBrowser 实现进一步将其传递给底层的非托管 WebBrowser ActiveX 控件.

Here is the challenge. I'm deriving from the Framework's WebBrowserSite class. An instance of my derived class, ImprovedWebBrowserSite, is returned via WebBrowser.CreateWebBrowserSiteBase, which I override in my derived version of the WebBrowser class - specifically to provide a custom site object. The Framework's WebBrowser implementation further passes it to down to the underlying unmanaged WebBrowser ActiveX control.

到目前为止,我已经设法在 ImprovedWebBrowserSite 实现中覆盖了 IDocHostUIHandler(例如 这个).我现在正在寻找更多的核心 COM 接口,例如 IOleClientSite,我想将其传递给 WebBrowserSite.它们都通过 ComImport 向 COM 公开,但由框架的 WebBrowserSite 实现声明为 privateinternal/UnsafeNativeMethods.因此,我不能明确地重新实现 他们在派生类中.我必须定义自己的版本,就像我对 IDocHostUIHandler 所做的那样.

So far, I've managed to override IDocHostUIHandler in my ImprovedWebBrowserSite implementation (like this). I'm now looking for more core COM interfaces, like IOleClientSite, which I want to pass-through to WebBrowserSite. All of them are exposed to COM with ComImport, but declared as private or internal by the Framework's implementation of WebBrowserSite/UnsafeNativeMethods. Thus, I cannot explicitly re-implement them in the derived class. I have to define my own versions, like I did with IDocHostUIHandler.

那么,问题是,如何从我的派生类中调用 WebBrowserSite 中定义的私有或内部 COM 接口的方法? 例如,我想调用 IOleClientSite.GetContainer.我可以使用反射(如 this),但这是最后的手段,仅次于重新实现 WebBrowser 从头开始​​.

So, the question is, how do I call a method of a private or internal COM interface defined in WebBrowserSite, from my derived class? For example, I want to call IOleClientSite.GetContainer. I can use reflection (like this), but that would be the last resort, second to re-implementing WebBrowser from scratch.

我的想法是,因为框架的私有 UnsafeNativeMethods.IOleClientSite 和我自己的 ImprovedWebBrowserSite.IOleClientSite 都是 COM 接口,用 ComImport 属性,相同的 GUID 和相同的方法签名..NET 4.0+ 中有 COM Type Equivalence,所以必须成为一种无需反思的方式.

My thinking is, because the Framework's private UnsafeNativeMethods.IOleClientSite and my own ImprovedWebBrowserSite.IOleClientSite are both COM interfaces, declared with the ComImport attribute, the same GUID and identical method signatures. There's COM Type Equivalence in .NET 4.0+, so there has to be a way to do it without reflection.

[UPDATE] 现在我有了解决方案,我相信它会打开自定义 WinForms 版本WebBrowser 控件的一个>.

[UPDATE] Now that I've got a solution, I believe it opens some new and interesting possibilities in customizing the WinForms version of WebBrowser control.

这个版本的问题是在我最初尝试以更抽象的形式表述问题之后创建的,被称为误导由评论员.该评论后来被删除,但我决定保留这两个版本.

This version of the question was created after my initial attempt to formulate the problem in a more abstract form was dubbed misleading by a commentator. The comment has been removed later, but I decided to keep both versions.

为什么我不想使用反射来解决这个问题?有几个原因:

  • 依赖于内部或私有方法的实际符号名称,由 WebBrowserSite 的实现者给出,与 COM 接口不同,它是关于二进制 v-table 合约的.

  • Dependency on the actual symbolic names of the internal or private methods, as given by the implementers of WebBrowserSite, unlike with a COM interface, which is about the binary v-table contract.

庞大的反射代码.例如,考虑调用基础的私有 TranslateAccelerator 通过 Type.InvokeMember,我有大约 20 个这样的方法可以调用.

Bulky reflection code. E.g., consider calling the base's private TranslateAccelerator via Type.InvokeMember, and I have ~20 methods like that to call.

虽然不太重要,但效率:通过反射的后期绑定调用总是比通过 v-table 直接调用 COM 接口方法的效率低.

Although less important, efficiency: a late-bound call via reflection is always less efficient than a direct call to a COM interface method via v-table.

推荐答案

最后,我相信我已经解决了问题 使用 Marshal.CreateAggregatedObject,在@EricBrown 的帮助下.

Finally, I believe I've solved the problem using Marshal.CreateAggregatedObject, with some help from @EricBrown.

以下代码可以自定义 WebBrowserSite OLE 接口,以 IOleClientSite 为例,调用 WebBrowserSite 的私有 COM 可见实现.它可以扩展到其他接口,例如IDocHostUIHandler.

Here's the code that makes possible customizing WebBrowserSite OLE interfaces, using IOleClientSite as an example, calling the private COM-visible implementation of WebBrowserSite. It can be extended to other interfaces, e.g. IDocHostUIHandler.

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

namespace CustomWebBrowser
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            var wb = new ImprovedWebBrowser();
            wb.Dock = DockStyle.Fill;
            this.Controls.Add(wb);
            wb.Visible = true;
            wb.DocumentText = "<b>Hello from ImprovedWebBrowser!</b>";
        }
    }

    // ImprovedWebBrowser with custom pass-through IOleClientSite 
    public class ImprovedWebBrowser: WebBrowser
    {
        // provide custom WebBrowserSite,
        // where we override IOleClientSite and call the base implementation
        protected override WebBrowserSiteBase CreateWebBrowserSiteBase()
        {
            return new ImprovedWebBrowserSite(this);
        }

        // IOleClientSite
        [ComImport(), Guid("00000118-0000-0000-C000-000000000046")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        public interface IOleClientSite
        {
            void SaveObject();

            [return: MarshalAs(UnmanagedType.Interface)]
            object GetMoniker(
                [In, MarshalAs(UnmanagedType.U4)] int dwAssign,
                [In, MarshalAs(UnmanagedType.U4)] int dwWhichMoniker);

            [PreserveSig]
            int GetContainer([Out] out IntPtr ppContainer);

            void ShowObject();

            void OnShowWindow([In, MarshalAs(UnmanagedType.I4)] int fShow);

            void RequestNewObjectLayout();
        }

        // ImprovedWebBrowserSite
        protected class ImprovedWebBrowserSite :
            WebBrowserSite,
            IOleClientSite,
            ICustomQueryInterface,
            IDisposable
        {
            IOleClientSite _baseIOleClientSite;
            IntPtr _unkOuter;
            IntPtr _unkInnerAggregated;
            Inner _inner;

            #region Inner
            // Inner as aggregated object
            class Inner :
                ICustomQueryInterface,
                IDisposable
            {
                object _outer;
                Type[] _interfaces;

                public Inner(object outer)
                {
                    _outer = outer;
                    // the base's private COM interfaces are here
                    _interfaces = _outer.GetType().BaseType.GetInterfaces(); 
                }

                public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
                {
                    if (_outer != null)
                    {
                        var ifaceGuid = iid;
                        var iface = _interfaces.FirstOrDefault((t) => t.GUID == ifaceGuid);
                        if (iface != null)
                        {
                            var unk = Marshal.GetComInterfaceForObject(_outer, iface, CustomQueryInterfaceMode.Ignore);
                            if (unk != IntPtr.Zero)
                            {
                                ppv = unk;
                                return CustomQueryInterfaceResult.Handled;
                            }
                        }
                    }
                    ppv = IntPtr.Zero;
                    return CustomQueryInterfaceResult.Failed;
                }

                ~Inner()
                {
                    // need to work out the reference counting for GC to work correctly
                    Debug.Print("Inner object finalized.");
                }

                public void Dispose()
                {
                    _outer = null;
                    _interfaces = null;
                }
            }
            #endregion

            // constructor
            public ImprovedWebBrowserSite(WebBrowser host):
                base(host)
            {
                // get the CCW object for this
                _unkOuter = Marshal.GetIUnknownForObject(this);
                Marshal.AddRef(_unkOuter);
                try
                {
                    // aggregate the CCW object with the helper Inner object
                    _inner = new Inner(this);
                    _unkInnerAggregated = Marshal.CreateAggregatedObject(_unkOuter, _inner);

                    // turn private WebBrowserSiteBase.IOleClientSite into our own IOleClientSite
                    _baseIOleClientSite = (IOleClientSite)Marshal.GetTypedObjectForIUnknown(_unkInnerAggregated, typeof(IOleClientSite));
                }
                finally
                {
                    Marshal.Release(_unkOuter);
                }
            }

            ~ImprovedWebBrowserSite()
            {
                // need to work out the reference counting for GC to work correctly
                Debug.Print("ImprovedClass finalized.");
            }

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

            void IDisposable.Dispose()
            {
                base.Dispose();

                // we may have recicular references to itself
                _baseIOleClientSite = null;

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

                if (_unkInnerAggregated != IntPtr.Zero)
                {
                    Marshal.Release(_unkInnerAggregated);
                    _unkInnerAggregated = IntPtr.Zero;
                }

                if (_unkOuter != IntPtr.Zero)
                {
                    Marshal.Release(_unkOuter);
                    _unkOuter = IntPtr.Zero;
                }
            }

            #region IOleClientSite
            // IOleClientSite
            public void SaveObject()
            {
                Debug.Print("IOleClientSite.SaveObject");
                _baseIOleClientSite.SaveObject();
            }

            public object GetMoniker(int dwAssign, int dwWhichMoniker)
            {
                Debug.Print("IOleClientSite.GetMoniker");
                return _baseIOleClientSite.GetMoniker(dwAssign, dwWhichMoniker);
            }

            public int GetContainer(out IntPtr ppContainer)
            {
                Debug.Print("IOleClientSite.GetContainer");
                return _baseIOleClientSite.GetContainer(out ppContainer);
            }

            public void ShowObject()
            {
                Debug.Print("IOleClientSite.ShowObject");
                _baseIOleClientSite.ShowObject();
            }

            public void OnShowWindow(int fShow)
            {
                Debug.Print("IOleClientSite.OnShowWindow");
                _baseIOleClientSite.OnShowWindow(fShow);
            }

            public void RequestNewObjectLayout()
            {
                Debug.Print("IOleClientSite.RequestNewObjectLayout");
                _baseIOleClientSite.RequestNewObjectLayout();
            }
            #endregion
        }
    }
}

这篇关于WebBrowserSite:如何在派生类中调用私有 COM 接口方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本文标题为:WebBrowserSite:如何在派生类中调用私有 COM 接口方法?