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

这是挑战。 我是从Framework的WebBrowserSite类派生出来的。 我的派生类ImprovedWebBrowserSite一个实例是通过WebBrowser.CreateWebBrowserSiteBase返回的,我在WebBrowser类的派生版本中覆盖了它 – 专门用于提供自定义站点对象。 Framework的WebBrowser实现进一步将其传递给底层的非托管WebBrowser ActiveX控件。

到目前为止,我已经设法在我的ImprovedWebBrowserSite实现中覆盖IDocHostUIHandler (像这样 )。 我现在正在寻找更多的核心COM接口,比如IOleClientSite ,我想将它传递给WebBrowserSite 。 所有这些都通过ComImport暴露给COM,但是通过Framework的WebBrowserSite / UnsafeNativeMethods实现声明为privateinternal 。 因此,我无法在派生类中明确地重新实现它们。 我必须定义自己的版本,就像我使用IDocHostUIHandler

所以,问题是,如何从我的派生类中调用WebBrowserSite定义的私有或内部COM接口的方法? 例如,我想调用IOleClientSite.GetContainer 。 我可以使用reflection(像这样 ),但这将是最后的手段,其次是从头开始重新实现WebBrowser

我的想法是,因为Framework的私有UnsafeNativeMethods.IOleClientSite和我自己的ImprovedWebBrowserSite.IOleClientSite都是COM接口,使用ComImport属性声明,相同的GUID和相同的方法签名。 .NET 4.0+中有COM类型等价 ,因此必须有一种方法可以在没有reflection的情况下完成。

[更新]现在我已经有了解决方案 ,我相信它为自定义WebBrowser控件的WinForms版本打开了一些新的和有趣的可能性。

这个版本的问题是在我最初尝试以更抽象的forms提出问题之后创建的,被评论员称为误导。 评论已被删除,但我决定保留这两个版本。

为什么我不想用reflection来解决这个问题? 原因如下:

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

  • 庞大的reflection代码。 例如,考虑通过Type.InvokeMember调用base的私有TranslateAccelerator ,我有大约20个这样的方法来调用。

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

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

下面是使用IOleClientSite作为示例自定义WebBrowserSite OLE接口的代码,调用WebBrowserSite的私有COM可见实现。 它可以扩展到其他接口,例如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 = "Hello from ImprovedWebBrowser!"; } } // 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 } } } 

只是一个想法,但也许你可以使用这里的一些源代码。 你会重新实现,但它可能会给你你想要的东西。