-
Notifications
You must be signed in to change notification settings - Fork 1k
Autocomplete using ComWrappers #6685
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Runtime.InteropServices; | ||
using System.Runtime.InteropServices.ComTypes; | ||
using System.Windows.Forms; | ||
|
||
internal partial class Interop | ||
{ | ||
internal unsafe partial class WinFormsComWrappers | ||
{ | ||
internal class AutoCompleteWrapper | ||
{ | ||
private IntPtr _wrappedInstance; | ||
|
||
public AutoCompleteWrapper(IntPtr wrappedInstance) | ||
{ | ||
_wrappedInstance = wrappedInstance.OrThrowIfZero(); | ||
} | ||
|
||
internal IntPtr Instance => _wrappedInstance; | ||
|
||
public void Dispose() | ||
{ | ||
Marshal.Release(_wrappedInstance); | ||
_wrappedInstance = IntPtr.Zero; | ||
} | ||
|
||
public HRESULT Init(IntPtr hwndEdit, IEnumString punkACL, IntPtr pwszRegKeyPath, IntPtr pwszQuickComplete) | ||
{ | ||
var punkACLPtr = WinFormsComWrappers.Instance.GetOrCreateComInterfaceForObject(punkACL, CreateComInterfaceFlags.None); | ||
return ((delegate* unmanaged<IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, HRESULT>)(*(*(void***)_wrappedInstance + 3))) | ||
(_wrappedInstance, hwndEdit, punkACLPtr, pwszRegKeyPath, pwszQuickComplete); | ||
} | ||
|
||
public HRESULT SetOptions(Shell32.AUTOCOMPLETEOPTIONS dwFlag) | ||
{ | ||
return ((delegate* unmanaged<IntPtr, Shell32.AUTOCOMPLETEOPTIONS, HRESULT>)(*(*(void***)_wrappedInstance + 5))) | ||
(_wrappedInstance, dwFlag); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Runtime.CompilerServices; | ||
using System.Runtime.InteropServices; | ||
using System.Runtime.InteropServices.ComTypes; | ||
|
||
internal partial class Interop | ||
{ | ||
internal unsafe partial class WinFormsComWrappers | ||
{ | ||
internal static class IEnumStringVtbl | ||
{ | ||
public static IntPtr Create(IntPtr fpQueryInterface, IntPtr fpAddRef, IntPtr fpRelease) | ||
{ | ||
IntPtr* vtblRaw = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(IEnumStringVtbl), IntPtr.Size * 7); | ||
vtblRaw[0] = fpQueryInterface; | ||
vtblRaw[1] = fpAddRef; | ||
vtblRaw[2] = fpRelease; | ||
vtblRaw[3] = (IntPtr)(delegate* unmanaged<IntPtr, int, IntPtr*, int*, int>)&Next; | ||
vtblRaw[4] = (IntPtr)(delegate* unmanaged<IntPtr, int, int>)&Skip; | ||
vtblRaw[5] = (IntPtr)(delegate* unmanaged<IntPtr, int>)&Reset; | ||
vtblRaw[6] = (IntPtr)(delegate* unmanaged<IntPtr, IntPtr*, int>)&Clone; | ||
|
||
return (IntPtr)vtblRaw; | ||
} | ||
|
||
[UnmanagedCallersOnly] | ||
private static int Next(IntPtr thisPtr, int celt, IntPtr* rgelt, int* pceltFetched) | ||
{ | ||
IEnumString inst = ComInterfaceDispatch.GetInstance<IEnumString>((ComInterfaceDispatch*)thisPtr); | ||
string[] elt = new string[celt]; | ||
var result = inst.Next(celt, elt, (IntPtr)pceltFetched); | ||
for (var i = 0; i < *pceltFetched; i++) | ||
{ | ||
rgelt[i] = Marshal.StringToCoTaskMemUni(elt[i]); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
[UnmanagedCallersOnly] | ||
private static int Skip(IntPtr thisPtr, int celt) | ||
{ | ||
IEnumString inst = ComInterfaceDispatch.GetInstance<IEnumString>((ComInterfaceDispatch*)thisPtr); | ||
return inst.Skip(celt); | ||
} | ||
|
||
[UnmanagedCallersOnly] | ||
private static int Reset(IntPtr thisPtr) | ||
{ | ||
try | ||
{ | ||
IEnumString inst = ComInterfaceDispatch.GetInstance<IEnumString>((ComInterfaceDispatch*)thisPtr); | ||
inst.Reset(); | ||
} | ||
catch (Exception ex) | ||
{ | ||
return ex.HResult; | ||
} | ||
|
||
return S_OK; | ||
} | ||
|
||
[UnmanagedCallersOnly] | ||
private static int Clone(IntPtr thisPtr, IntPtr* ppenum) | ||
{ | ||
try | ||
{ | ||
IEnumString inst = ComInterfaceDispatch.GetInstance<IEnumString>((ComInterfaceDispatch*)thisPtr); | ||
inst.Clone(out var cloned); | ||
*ppenum = WinFormsComWrappers.Instance.GetOrCreateComInterfaceForObject(cloned, CreateComInterfaceFlags.None); | ||
} | ||
catch (Exception ex) | ||
{ | ||
return ex.HResult; | ||
} | ||
|
||
return S_OK; | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,16 +2,14 @@ | |
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
#nullable disable | ||
|
||
using System.Runtime.InteropServices; | ||
using System.Runtime.InteropServices.ComTypes; | ||
using static Interop; | ||
|
||
namespace System.Windows.Forms | ||
{ | ||
/// <summary> | ||
/// Represents an internal class that is used bu ComboBox and TextBox AutoCompleteCustomSource property. | ||
/// Represents an internal class that is used by ComboBox and TextBox AutoCompleteCustomSource property. | ||
/// This class is responsible for initializing the SHAutoComplete COM object and setting options in it. | ||
/// The StringSource contains an array of Strings which is passed to the COM object as the custom source. | ||
/// </summary> | ||
|
@@ -20,41 +18,30 @@ internal class StringSource : IEnumString | |
private string[] strings; | ||
private int current; | ||
private int size; | ||
private Shell32.IAutoComplete2 _autoCompleteObject2; | ||
|
||
/// <summary> | ||
/// SHAutoComplete COM object CLSID. | ||
/// </summary> | ||
private static Guid autoCompleteClsid = new Guid("{00BB2763-6A77-11D0-A535-00C04FD7D062}"); | ||
private WinFormsComWrappers.AutoCompleteWrapper? _autoCompleteObject2; | ||
|
||
/// <summary> | ||
/// Constructor. | ||
/// </summary> | ||
public StringSource(string[] strings) | ||
{ | ||
Array.Clear(strings, 0, size); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Based on the usage this will never ever happens. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or better say, this is no-op. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While it is true that there are no callers that pass in a null array, not clearing the passed in array is a change in behavior. I wouldn't remove the clear in this change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
if (strings is not null) | ||
{ | ||
this.strings = strings; | ||
} | ||
this.strings = strings; | ||
|
||
current = 0; | ||
size = (strings is null) ? 0 : strings.Length; | ||
size = strings.Length; | ||
|
||
Guid iid_iunknown = typeof(Shell32.IAutoComplete2).GUID; | ||
HRESULT hr = Ole32.CoCreateInstance( | ||
ref autoCompleteClsid, | ||
var autoCompleteIID = IID.IAutoComplete2; | ||
Ole32.CoCreateInstance( | ||
in CLSID.AutoComplete, | ||
IntPtr.Zero, | ||
Ole32.CLSCTX.INPROC_SERVER, | ||
ref iid_iunknown, | ||
out object obj); | ||
if (!hr.Succeeded()) | ||
{ | ||
throw Marshal.GetExceptionForHR((int)hr); | ||
} | ||
in autoCompleteIID, | ||
out IntPtr autoComplete2Ptr).ThrowIfFailed(); | ||
|
||
_autoCompleteObject2 = (Shell32.IAutoComplete2)obj; | ||
var obj = WinFormsComWrappers.Instance | ||
.GetOrCreateObjectForComInstance(autoComplete2Ptr, CreateObjectFlags.None); | ||
_autoCompleteObject2 = (WinFormsComWrappers.AutoCompleteWrapper)obj; | ||
} | ||
|
||
/// <summary> | ||
|
@@ -73,7 +60,7 @@ public bool Bind(HandleRef edit, Shell32.AUTOCOMPLETEOPTIONS options) | |
return false; | ||
} | ||
|
||
HRESULT hr = _autoCompleteObject2.Init(edit.Handle, (IEnumString)this, null, null); | ||
HRESULT hr = _autoCompleteObject2.Init(edit.Handle, this, IntPtr.Zero, IntPtr.Zero); | ||
GC.KeepAlive(edit.Wrapper); | ||
return hr.Succeeded(); | ||
} | ||
|
@@ -82,22 +69,17 @@ public void ReleaseAutoComplete() | |
{ | ||
if (_autoCompleteObject2 is not null) | ||
{ | ||
Marshal.ReleaseComObject(_autoCompleteObject2); | ||
_autoCompleteObject2.Dispose(); | ||
_autoCompleteObject2 = null; | ||
} | ||
} | ||
|
||
public void RefreshList(string[] newSource) | ||
{ | ||
Array.Clear(strings, 0, size); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also based on the usage this will never happens. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or better say, this is no-op. |
||
|
||
if (strings is not null) | ||
{ | ||
strings = newSource; | ||
} | ||
|
||
strings = newSource; | ||
current = 0; | ||
size = (strings is null) ? 0 : strings.Length; | ||
size = strings.Length; | ||
} | ||
|
||
#region IEnumString Members | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IAutoComplete2 has methods Init, Enable, SetOptions, and GetOptions. Here for AutoCompleteWrapper I only see Init and SetOptions. Is that intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. This is intentional. There no need for declaring methods in RCW if they would not be used in the application. WinForms use just 2 methods, so I leave only these 2 method. That mentality was shown for me in the dotnet/runtime repo, and based on my experience working with WinForms team, same applied here as well. Additional cost (albeit very small) is paid for all WinForms consumers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is good information to know for ComWrappers, I will keep this in mind. Thank you for the insight.