From 9d270f2634240b285e5171539e346e66ad297a61 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Thu, 30 Apr 2020 16:58:03 -0700 Subject: [PATCH 1/3] ComWrappers example. --- .../comwrappers/IDispatch/AnyObjectProxy.cs | 172 +++++++++++++++ .../IDispatch/ComWrappersIDispatch.csproj | 10 + .../comwrappers/IDispatch/ComWrappersImpl.cs | 205 ++++++++++++++++++ .../comwrappers/IDispatch/Form1.Designer.cs | 81 +++++++ core/interop/comwrappers/IDispatch/Form1.cs | 58 +++++ core/interop/comwrappers/IDispatch/Form1.resx | 60 +++++ core/interop/comwrappers/IDispatch/Program.cs | 20 ++ core/interop/comwrappers/IDispatch/README.md | 27 +++ 8 files changed, 633 insertions(+) create mode 100644 core/interop/comwrappers/IDispatch/AnyObjectProxy.cs create mode 100644 core/interop/comwrappers/IDispatch/ComWrappersIDispatch.csproj create mode 100644 core/interop/comwrappers/IDispatch/ComWrappersImpl.cs create mode 100644 core/interop/comwrappers/IDispatch/Form1.Designer.cs create mode 100644 core/interop/comwrappers/IDispatch/Form1.cs create mode 100644 core/interop/comwrappers/IDispatch/Form1.resx create mode 100644 core/interop/comwrappers/IDispatch/Program.cs create mode 100644 core/interop/comwrappers/IDispatch/README.md diff --git a/core/interop/comwrappers/IDispatch/AnyObjectProxy.cs b/core/interop/comwrappers/IDispatch/AnyObjectProxy.cs new file mode 100644 index 00000000000..044e165963e --- /dev/null +++ b/core/interop/comwrappers/IDispatch/AnyObjectProxy.cs @@ -0,0 +1,172 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; + +public class AnyObjectProxy : ComWrappersImpl.IDispatch, + ICustomQueryInterface /* WORKAROUND for WinForms WebBrowser control API */ +{ + private static Lazy g_ComWrappers = new Lazy(() => new ComWrappersImpl(), true); + + private const int DISP_E_UNKNOWNNAME = unchecked((int)0x80020006); + + private readonly Type objType; + private readonly object obj; + private readonly IntPtr wrapperPtr; + private readonly Dictionary nameToDispId = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary dispIdToMemberInfo = new Dictionary(); + public AnyObjectProxy(object obj) + { + this.obj = obj; + this.objType = this.obj.GetType(); + + // The cached string to int is the function name to desired DispID. + // The DispIDs can be stored but need to consistent. The Invoke() call will be passed the + // DispID and will use that to Invoke whatever member is desired. + // + // The below caching could instead be done using MethodInfo and PropertyInfo instances. + // This would enable the Invoke() implementation to be more efficient. + // + int dispIdNext = 100; // Starting from 100 is typical convention. + foreach (MemberInfo mi in this.objType.GetMembers()) + { + this.dispIdToMemberInfo[dispIdNext] = mi; + this.nameToDispId[mi.Name] = dispIdNext++; + } + + this.wrapperPtr = g_ComWrappers.Value.GetOrCreateComInterfaceForObject(this, CreateComInterfaceFlags.None); + } + + ~AnyObjectProxy() + { + if (this.wrapperPtr != IntPtr.Zero) + { + Marshal.Release(this.wrapperPtr); + } + } + + void ComWrappersImpl.IDispatch.GetTypeInfoCount(out int pctinfo) + { + // Will always be called. + // Returning 0 is a completely acceptable return value. + pctinfo = 0; + } + + void ComWrappersImpl.IDispatch.GetTypeInfo(int iTInfo, int lcid, out IntPtr info) + { + // If GetTypeInfoCount() returns 0, this function will not be called. + throw new NotImplementedException(); + } + + void ComWrappersImpl.IDispatch.GetIDsOfNames(ref Guid iid, string[] names, int cNames, int lcid, int[] rgDispId) + { + Debug.Assert(iid == Guid.Empty); + for (int i = 0; i < cNames; ++i) + { + int dispId = 0; + if (!this.nameToDispId.TryGetValue(names[i], out dispId)) + { + throw new COMException(null, DISP_E_UNKNOWNNAME); + } + + rgDispId[i] = dispId; + } + } + + void ComWrappersImpl.IDispatch.Invoke( + int dispIdMember, + ref Guid riid, + int lcid, + INVOKEKIND wFlags, + ref DISPPARAMS pDispParams, + IntPtr VarResult, + IntPtr pExcepInfo, + IntPtr puArgErr) + { + MemberInfo? memberInfo; + if (!this.dispIdToMemberInfo.TryGetValue(dispIdMember, out memberInfo)) + { + throw new COMException(null, DISP_E_UNKNOWNNAME); + } + + BindingFlags invokeFlags = BindingFlags.Public | BindingFlags.Instance; + if (wFlags.HasFlag(INVOKEKIND.INVOKE_FUNC) + && memberInfo.MemberType == MemberTypes.Method) + { + invokeFlags |= BindingFlags.InvokeMethod; + } + else + { + throw new NotImplementedException("Operation not implemented."); + } + + // Use reflection to dispatch to the indicated function. + // Note that this is exactly what the internal implementation of IDispatch does so there + // isn't a lot of difference in cost. + var result = this.objType.InvokeMember( + memberInfo.Name, + invokeFlags, + null, + this.obj, + MarshalArguments(ref pDispParams)); + + if (result != null) + { + // Lots of special cases need should be addressed here. + // * Arrays, IEnumerable + // * IDispatch/IUnknown instances + // * .NET object should be wrapped by ComWrappers + // * etc + Marshal.GetNativeVariantForObject(result, VarResult); + } + } + + private static object[] MarshalArguments(ref DISPPARAMS pDispParams) + { + if (pDispParams.cArgs == 0) + { + return Array.Empty(); + } + + // Lots of special cases need should be addressed here. + // * Arrays + // * IDispatch/IUnknown instances + // * .NET objects passed back as arguments + // * etc + return Marshal.GetObjectsForNativeVariants(pDispParams.rgvarg, pDispParams.cArgs)!; + } + + /* WORKAROUND for WinForms WebBrowser control API */ + [ThreadStatic] bool inGetInterface = false; // Needed since the ComWrappers API calls this prior + // to callling user defined interfaces. + CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out IntPtr ppv) + { + Debug.Assert(this.wrapperPtr != IntPtr.Zero); + ppv = IntPtr.Zero; + + // Return the ComWrappers IDispatch implementation instead + // of the one provided by the runtime. + if (!this.inGetInterface + && iid == typeof(ComWrappersImpl.IDispatch).GUID) + { + try + { + this.inGetInterface = true; + int hr = Marshal.QueryInterface(this.wrapperPtr, ref iid, out ppv); + Debug.Assert(hr == 0); + } + finally + { + this.inGetInterface = false; + } + } + + return (ppv == IntPtr.Zero) + ? CustomQueryInterfaceResult.NotHandled + : CustomQueryInterfaceResult.Handled; + } +} diff --git a/core/interop/comwrappers/IDispatch/ComWrappersIDispatch.csproj b/core/interop/comwrappers/IDispatch/ComWrappersIDispatch.csproj new file mode 100644 index 00000000000..4c68202193e --- /dev/null +++ b/core/interop/comwrappers/IDispatch/ComWrappersIDispatch.csproj @@ -0,0 +1,10 @@ + + + + WinExe + netcoreapp5.0 + true + true + + + \ No newline at end of file diff --git a/core/interop/comwrappers/IDispatch/ComWrappersImpl.cs b/core/interop/comwrappers/IDispatch/ComWrappersImpl.cs new file mode 100644 index 00000000000..3c7231b49b6 --- /dev/null +++ b/core/interop/comwrappers/IDispatch/ComWrappersImpl.cs @@ -0,0 +1,205 @@ +#nullable enable + +using System; +using System.Collections; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using ComTypes = System.Runtime.InteropServices.ComTypes; + +internal unsafe class ComWrappersImpl : ComWrappers +{ + private static readonly ComInterfaceEntry* wrapperEntry; + + // This class only exposed IDispatch and the vtable is always the same. + // The below isn't the most efficient but it is reasonable for prototyping. + // If additional interfaces want to be exposed, add them here. + static ComWrappersImpl() + { + GetIUnknownImpl(out IntPtr fpQueryInteface, out IntPtr fpAddRef, out IntPtr fpRelease); + + var vtbl = new IDispatchVtbl() + { + IUnknownImpl = new IUnknownVtbl() + { + QueryInterface = fpQueryInteface, + AddRef = fpAddRef, + Release = fpRelease + }, + GetTypeInfoCount = Marshal.GetFunctionPointerForDelegate(IDispatchVtbl.pGetTypeInfoCount), + GetTypeInfo = Marshal.GetFunctionPointerForDelegate(IDispatchVtbl.pGetTypeInfo), + GetIDsOfNames = Marshal.GetFunctionPointerForDelegate(IDispatchVtbl.pGetIDsOfNames), + Invoke = Marshal.GetFunctionPointerForDelegate(IDispatchVtbl.pInvoke) + }; + var vtblRaw = RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(IDispatchVtbl), sizeof(IDispatchVtbl)); + Marshal.StructureToPtr(vtbl, vtblRaw, false); + + wrapperEntry = (ComInterfaceEntry*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(IDispatchVtbl), sizeof(ComInterfaceEntry)); + wrapperEntry->IID = typeof(IDispatch).GUID; + wrapperEntry->Vtable = vtblRaw; + } + + protected override unsafe ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count) + { + Debug.Assert(obj is IDispatch); + Debug.Assert(wrapperEntry != null); + + // Always return the same table mappings. + count = 1; + return wrapperEntry; + } + + protected override object CreateObject(IntPtr externalComObject, CreateObjectFlags flags) + { + throw new NotImplementedException(); + } + + protected override void ReleaseObjects(IEnumerable objects) + { + throw new NotImplementedException(); + } + + [Guid("00020400-0000-0000-C000-000000000046")] + public interface IDispatch + { + void GetTypeInfoCount(out int pctinfo); + + void GetTypeInfo(int iTInfo, int lcid, out IntPtr info); + + void GetIDsOfNames( + ref Guid iid, + string[] names, + int cNames, + int lcid, + int[] rgDispId); + + void Invoke( + int dispIdMember, + ref Guid riid, + int lcid, + ComTypes.INVOKEKIND wFlags, + ref ComTypes.DISPPARAMS pDispParams, + IntPtr VarResult, + IntPtr pExcepInfo, + IntPtr puArgErr); + } + + public struct IUnknownVtbl + { + public IntPtr QueryInterface; + public IntPtr AddRef; + public IntPtr Release; + } + + public struct IDispatchVtbl + { + public IUnknownVtbl IUnknownImpl; + public IntPtr GetTypeInfoCount; + public IntPtr GetTypeInfo; + public IntPtr GetIDsOfNames; + public IntPtr Invoke; + + public delegate int _GetTypeInfoCount(IntPtr thisPtr, out int i); + public delegate int _GetTypeInfo(IntPtr thisPtr, int itinfo, int lcid, out IntPtr i); + public delegate int _GetIDsOfNames( + IntPtr thisPtr, + ref Guid iid, + [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 3)] + string[] names, + int namesCount, + int lcid, + [Out][MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.I4, SizeParamIndex = 3)] + int[] dispIds); + public delegate int _Invoke( + IntPtr thisPtr, + int dispIdMember, + ref Guid riid, + int lcid, + ComTypes.INVOKEKIND wFlags, + ref ComTypes.DISPPARAMS pDispParams, + IntPtr VarResult, + IntPtr pExcepInfo, + IntPtr puArgErr); + + public static _GetTypeInfoCount pGetTypeInfoCount = new _GetTypeInfoCount(GetTypeInfoCountInternal); + public static _GetTypeInfo pGetTypeInfo = new _GetTypeInfo(GetTypeInfoInternal); + public static _GetIDsOfNames pGetIDsOfNames = new _GetIDsOfNames(GetIDsOfNamesInternal); + public static _Invoke pInvoke = new _Invoke(InvokeInternal); + + public static int GetTypeInfoCountInternal(IntPtr thisPtr, out int i) + { + i = 0; + try + { + var inst = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); + inst.GetTypeInfoCount(out i); + } + catch (Exception e) + { + return e.HResult; + } + return 0; // S_OK; + } + + public static int GetTypeInfoInternal(IntPtr thisPtr, int itinfo, int lcid, out IntPtr i) + { + i = IntPtr.Zero; + try + { + var inst = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); + inst.GetTypeInfo(itinfo, lcid, out i); + } + catch (Exception e) + { + return e.HResult; + } + return 0; // S_OK; + } + + public static int GetIDsOfNamesInternal( + IntPtr thisPtr, + ref Guid iid, + [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 3)] + string[] names, + int namesCount, + int lcid, + [Out][MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.I4, SizeParamIndex = 3)] + int[] dispIds) + { + try + { + var inst = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); + inst.GetIDsOfNames(ref iid, names, namesCount, lcid, dispIds); + } + catch (Exception e) + { + return e.HResult; + } + return 0; // S_OK; + } + + public static int InvokeInternal( + IntPtr thisPtr, + int dispIdMember, + ref Guid riid, + int lcid, + ComTypes.INVOKEKIND wFlags, + ref ComTypes.DISPPARAMS pDispParams, + IntPtr VarResult, + IntPtr pExcepInfo, + IntPtr puArgErr) + { + try + { + var inst = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); + inst.Invoke(dispIdMember, ref riid, lcid, wFlags, ref pDispParams, VarResult, pExcepInfo, puArgErr); + } + catch (Exception e) + { + return e.HResult; + } + return 0; // S_OK; + } + } +} diff --git a/core/interop/comwrappers/IDispatch/Form1.Designer.cs b/core/interop/comwrappers/IDispatch/Form1.Designer.cs new file mode 100644 index 00000000000..e432cc0b96d --- /dev/null +++ b/core/interop/comwrappers/IDispatch/Form1.Designer.cs @@ -0,0 +1,81 @@ +namespace ComWrappersIDispatch +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.webBrowser1 = new System.Windows.Forms.WebBrowser(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.label1 = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // webBrowser1 + // + this.webBrowser1.Location = new System.Drawing.Point(0, 0); + this.webBrowser1.Name = "webBrowser1"; + this.webBrowser1.ScrollBarsEnabled = false; + this.webBrowser1.Size = new System.Drawing.Size(205, 100); + this.webBrowser1.TabIndex = 0; + // + // textBox1 + // + this.textBox1.Location = new System.Drawing.Point(0, 128); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(205, 23); + this.textBox1.TabIndex = 0; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(0, 110); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(106, 15); + this.label1.TabIndex = 1; + this.label1.Text = "From Web control:"; + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(300, 400); + this.Controls.Add(this.label1); + this.Controls.Add(this.textBox1); + this.Name = "Form1"; + this.Text = "Form1"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.WebBrowser webBrowser1; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Label label1; + } +} + diff --git a/core/interop/comwrappers/IDispatch/Form1.cs b/core/interop/comwrappers/IDispatch/Form1.cs new file mode 100644 index 00000000000..a181de2040b --- /dev/null +++ b/core/interop/comwrappers/IDispatch/Form1.cs @@ -0,0 +1,58 @@ +using System.Diagnostics; +using System.Windows.Forms; + +namespace ComWrappersIDispatch +{ + public partial class Form1 : Form + { + public Form1() + { + InitializeComponent(); + + this.webBrowser1.DocumentText = +$@" + + +"; + + this.Controls.Add(this.webBrowser1); + + this.AttachObject(new ExposedObject(this)); + } + + private void AttachObject(object obj) + { + var proxy = new AnyObjectProxy(obj); + this.webBrowser1.ObjectForScripting = proxy; + } + + private class ExposedObject + { + private readonly Form1 form; + public ExposedObject(Form1 form) + { + this.form = form; + } + public void Func1() + { + Debug.WriteLine($"{nameof(Func1)}"); + } + public void Func2(int a) + { + Debug.WriteLine($"{nameof(Func2)}({a})"); + } + public void Func3(string msg) + { + Debug.WriteLine($"{nameof(Func3)}({msg})"); + this.form.textBox1.Text = msg; + + } + } + } +} diff --git a/core/interop/comwrappers/IDispatch/Form1.resx b/core/interop/comwrappers/IDispatch/Form1.resx new file mode 100644 index 00000000000..f298a7be809 --- /dev/null +++ b/core/interop/comwrappers/IDispatch/Form1.resx @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/core/interop/comwrappers/IDispatch/Program.cs b/core/interop/comwrappers/IDispatch/Program.cs new file mode 100644 index 00000000000..2da60056973 --- /dev/null +++ b/core/interop/comwrappers/IDispatch/Program.cs @@ -0,0 +1,20 @@ +using System; +using System.Windows.Forms; + +namespace ComWrappersIDispatch +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.SetHighDpiMode(HighDpiMode.SystemAware); + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + } +} diff --git a/core/interop/comwrappers/IDispatch/README.md b/core/interop/comwrappers/IDispatch/README.md new file mode 100644 index 00000000000..136a9bbbbf5 --- /dev/null +++ b/core/interop/comwrappers/IDispatch/README.md @@ -0,0 +1,27 @@ +`ComWrappers` API Demo +================ + +The [`ComWrappers` API](https://github.com/dotnet/runtime/issues/1845) was introduced in .NET 5.0 Preview 4 to help users build custom COM interop scenarios. + +Key Features +------------ + +Demonstrates how to implement and utilize the `ComWrappers` API by manually implementing a subset of [`IDispatch`](https://docs.microsoft.com/windows/win32/api/oaidl/nn-oaidl-idispatch) for .NET objects. The consumer of the `IDispatch` instance is the [WinForms `WebBrowser`](https://docs.microsoft.com/dotnet/framework/winforms/controls/webbrowser-control-windows-forms) API for [exposing an object to the JavaScript engine](https://docs.microsoft.com/dotnet/api/system.windows.forms.webbrowser.objectforscripting). + +This is already fully supported by the built-in COM interop system, but this demonstrates a way for users to provide their own implementation. + +**Note** There are two sections of code commented as `WORKAROUND`. These sections are needed to trick the existing `WebBrowser.ObjectForScripting` API into accepting a .NET object but receiving a `ComWrappers` generated `IDispatch` implementation. + +Build and Run +------------- + +1) Install .NET Core 5.0 Preview 4 or later. + +1) Load `ComWrappersIDispatch.csproj` in Visual Studio 2019 or build from the command line. + - Double click on `ComWrappersIDispatch.csproj` in File Explorer. + + or + + - Open a Command prompt with `dotnet` on the path and build `dotnet build ComWrappersIDispatch.csproj`. + +1) Press F5 to build and debug the project or `dotnet run ComWrappersIDispatch.csproj` From 8bd6ea4de85966c2df5fbf892ead417ba037fea1 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Thu, 30 Apr 2020 17:03:26 -0700 Subject: [PATCH 2/3] Update target framework. --- core/interop/comwrappers/IDispatch/ComWrappersIDispatch.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/interop/comwrappers/IDispatch/ComWrappersIDispatch.csproj b/core/interop/comwrappers/IDispatch/ComWrappersIDispatch.csproj index 4c68202193e..7b26aecb6e0 100644 --- a/core/interop/comwrappers/IDispatch/ComWrappersIDispatch.csproj +++ b/core/interop/comwrappers/IDispatch/ComWrappersIDispatch.csproj @@ -2,7 +2,7 @@ WinExe - netcoreapp5.0 + net5.0 true true From bd301beb8f21f0d0a5d170880948d335a09af6c1 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Thu, 30 Apr 2020 17:22:42 -0700 Subject: [PATCH 3/3] Clarify some comments. --- .../interop/comwrappers/IDispatch/AnyObjectProxy.cs | 13 +++++++------ .../comwrappers/IDispatch/ComWrappersImpl.cs | 2 +- core/interop/comwrappers/IDispatch/Form1.cs | 1 - core/interop/comwrappers/IDispatch/README.md | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/interop/comwrappers/IDispatch/AnyObjectProxy.cs b/core/interop/comwrappers/IDispatch/AnyObjectProxy.cs index 044e165963e..0ac896e27f3 100644 --- a/core/interop/comwrappers/IDispatch/AnyObjectProxy.cs +++ b/core/interop/comwrappers/IDispatch/AnyObjectProxy.cs @@ -116,10 +116,11 @@ void ComWrappersImpl.IDispatch.Invoke( if (result != null) { - // Lots of special cases need should be addressed here. + // Lots of special cases should be addressed here. // * Arrays, IEnumerable // * IDispatch/IUnknown instances - // * .NET object should be wrapped by ComWrappers + // * .NET object could be wrapped by ComWrappers + // * .NET objects that are already COM objects can be safely passed on // * etc Marshal.GetNativeVariantForObject(result, VarResult); } @@ -132,7 +133,7 @@ private static object[] MarshalArguments(ref DISPPARAMS pDispParams) return Array.Empty(); } - // Lots of special cases need should be addressed here. + // Lots of special cases should be addressed here. // * Arrays // * IDispatch/IUnknown instances // * .NET objects passed back as arguments @@ -142,14 +143,14 @@ private static object[] MarshalArguments(ref DISPPARAMS pDispParams) /* WORKAROUND for WinForms WebBrowser control API */ [ThreadStatic] bool inGetInterface = false; // Needed since the ComWrappers API calls this prior - // to callling user defined interfaces. + // to calling user defined interfaces. CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out IntPtr ppv) { Debug.Assert(this.wrapperPtr != IntPtr.Zero); ppv = IntPtr.Zero; - // Return the ComWrappers IDispatch implementation instead - // of the one provided by the runtime. + // Return the ComWrappers IDispatch implementation + // instead of the one provided by the runtime. if (!this.inGetInterface && iid == typeof(ComWrappersImpl.IDispatch).GUID) { diff --git a/core/interop/comwrappers/IDispatch/ComWrappersImpl.cs b/core/interop/comwrappers/IDispatch/ComWrappersImpl.cs index 3c7231b49b6..6620a16c15b 100644 --- a/core/interop/comwrappers/IDispatch/ComWrappersImpl.cs +++ b/core/interop/comwrappers/IDispatch/ComWrappersImpl.cs @@ -12,7 +12,7 @@ internal unsafe class ComWrappersImpl : ComWrappers { private static readonly ComInterfaceEntry* wrapperEntry; - // This class only exposed IDispatch and the vtable is always the same. + // This class only exposes IDispatch and the vtable is always the same. // The below isn't the most efficient but it is reasonable for prototyping. // If additional interfaces want to be exposed, add them here. static ComWrappersImpl() diff --git a/core/interop/comwrappers/IDispatch/Form1.cs b/core/interop/comwrappers/IDispatch/Form1.cs index a181de2040b..f3fd455f965 100644 --- a/core/interop/comwrappers/IDispatch/Form1.cs +++ b/core/interop/comwrappers/IDispatch/Form1.cs @@ -51,7 +51,6 @@ public void Func3(string msg) { Debug.WriteLine($"{nameof(Func3)}({msg})"); this.form.textBox1.Text = msg; - } } } diff --git a/core/interop/comwrappers/IDispatch/README.md b/core/interop/comwrappers/IDispatch/README.md index 136a9bbbbf5..b60743c68c9 100644 --- a/core/interop/comwrappers/IDispatch/README.md +++ b/core/interop/comwrappers/IDispatch/README.md @@ -10,7 +10,7 @@ Demonstrates how to implement and utilize the `ComWrappers` API by manually impl This is already fully supported by the built-in COM interop system, but this demonstrates a way for users to provide their own implementation. -**Note** There are two sections of code commented as `WORKAROUND`. These sections are needed to trick the existing `WebBrowser.ObjectForScripting` API into accepting a .NET object but receiving a `ComWrappers` generated `IDispatch` implementation. +**Note** There are two sections of code commented as `WORKAROUND`. These sections are needed to trick the `WebBrowser.ObjectForScripting` API into accepting a .NET object but receiving a `ComWrappers` generated `IDispatch` implementation instead. Build and Run -------------