From 6277e50b3005aee37a5eeaae318806d6ac5c7bed Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Mon, 5 Dec 2022 15:46:24 -0800 Subject: [PATCH] Convert IFont and IFontDisp to CSWin32 (#8322) Converts IFont / IFontDisp to CSWin32. IFontDisp wasn't properly defined (wrong DISPIDs). I attemped to run my VB tests against the correct code to see if that fixed the test run problem, but alas, it did not. Including the VB tests (Skipped) to allow manual testing and allow investigating and vetting against further fixes that come as part of the modernization effort here. This adds some tests for the other IFont usages. Additionally: - Adds the interface definition for IDispatch (which CsWin32 omits) and a CCW - Adds DynamicAxHost for testing AxHost with controls direct from assemblies - Adds ComClasses/ComClassFactory for creating COM classes direct from assemblies - Adds explicit converters to VARIANT and CY (currency format) - Adds ToStringAndFree to BSTR - Adds another overload for prop getting from IDispatch - Moves code from ControlPaint to it's only usage in COM2FontConverter --- .../src/Interop/Ole32/Interop.IFont.cs | 56 ------ .../src/Interop/Ole32/Interop.IFontDisp.cs | 33 ---- .../src/Interop/Ole32/Interop.QACONTAINER.cs | 2 +- .../src/Interop/OleAut32/Interop.FONTDESC.cs | 24 --- .../OleAut32/Interop.OleCreateFontIndirect.cs | 17 -- .../src/Interop/OleAut32/Interop.VARIANT.cs | 31 +++- .../src/NativeMethods.json | 1 + .../src/NativeMethods.txt | 17 +- .../src/Windows/Win32/Foundation/BSTR.cs | 10 ++ .../src/Windows/Win32/System/Com/CY.cs | 16 ++ .../Win32/System/Com/IDispatch.Interface.cs | 82 +++++++++ .../src/Windows/Win32/System/Com/IDispatch.cs | 12 ++ .../Win32/System/Com/ComClassFactory.cs | 71 ++++++++ .../Windows/Forms/AxHost.OleInterfaces.cs | 2 +- .../src/System/Windows/Forms/AxHost.cs | 78 +++++--- .../COM2Interop/COM2FontConverter.cs | 83 ++++++++- .../COM2Interop/COM2PropertyDescriptor.cs | 15 +- .../COM2Interop/ComNativeDescriptor.cs | 3 +- .../Forms/Control.ActiveXFontMarshaler.cs | 86 ++++----- .../Windows/Forms/Control.ActiveXImpl.cs | 2 +- .../src/System/Windows/Forms/ControlPaint.cs | 81 --------- .../TestUtilities/ThreadExceptionFixture.cs | 4 +- .../System.Windows.Forms.Tests.csproj | 6 + .../Windows/Forms/AxHost.VisualBasic6Tests.cs | 30 ++++ .../System/Windows/Forms/AxHostTests.cs | 63 +++---- .../Com2Interop/COM2FontConverterTests.cs | 166 ++++++++++++++++++ .../Com2Interop/ComNativeDescriptorTests.cs | 27 +++ .../Control.ActiveXFontMarshallerTests.cs | 36 ++++ .../System/Windows/Forms/DynamicAxHost.cs | 30 ++++ .../UnitTests/TestResources/ComClasses.cs | 21 +++ .../TestResources/VB6/SimpleControl.vb6 | Bin 0 -> 24576 bytes 31 files changed, 772 insertions(+), 333 deletions(-) delete mode 100644 src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.IFont.cs delete mode 100644 src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.IFontDisp.cs delete mode 100644 src/System.Windows.Forms.Primitives/src/Interop/OleAut32/Interop.FONTDESC.cs delete mode 100644 src/System.Windows.Forms.Primitives/src/Interop/OleAut32/Interop.OleCreateFontIndirect.cs create mode 100644 src/System.Windows.Forms.Primitives/src/Windows/Win32/System/Com/CY.cs create mode 100644 src/System.Windows.Forms.Primitives/src/Windows/Win32/System/Com/IDispatch.Interface.cs create mode 100644 src/System.Windows.Forms.Primitives/tests/TestUtilities/Windows/Win32/System/Com/ComClassFactory.cs create mode 100644 src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/AxHost.VisualBasic6Tests.cs create mode 100644 src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComponentModel/Com2Interop/COM2FontConverterTests.cs create mode 100644 src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/Control.ActiveXFontMarshallerTests.cs create mode 100644 src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DynamicAxHost.cs create mode 100644 src/System.Windows.Forms/tests/UnitTests/TestResources/ComClasses.cs create mode 100644 src/System.Windows.Forms/tests/UnitTests/TestResources/VB6/SimpleControl.vb6 diff --git a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.IFont.cs b/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.IFont.cs deleted file mode 100644 index 4855e7918a6..00000000000 --- a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.IFont.cs +++ /dev/null @@ -1,56 +0,0 @@ -// 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; - -internal static partial class Interop -{ - internal static partial class Ole32 - { - [ComImport] - [Guid("BEF6E002-A874-101A-8BBA-00AA00300CAB")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public unsafe interface IFont - { - string Name { get; set; } - long Size { get; set; } - BOOL Bold { get; set; } - BOOL Italic { get; set; } - BOOL Underline { get; set; } - BOOL Strikethrough { get; set; } - short Weight { get; set; } - short Charset { get; set; } - IntPtr hFont { get; } - - [PreserveSig] - HRESULT Clone( - out IFont ppfont); - - [PreserveSig] - HRESULT IsEqual( - IFont pfontOther); - - [PreserveSig] - HRESULT SetRatio( - int cyLogical, - int cyHimetric); - - [PreserveSig] - HRESULT QueryTextMetrics( - TEXTMETRICW* pTM); - - [PreserveSig] - HRESULT AddRefHfont( - IntPtr hFont); - - [PreserveSig] - HRESULT ReleaseHfont( - IntPtr hFont); - - [PreserveSig] - HRESULT SetHdc( - IntPtr hDC); - } - } -} diff --git a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.IFontDisp.cs b/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.IFontDisp.cs deleted file mode 100644 index f76fa5cec40..00000000000 --- a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.IFontDisp.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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; - -internal static partial class Interop -{ - internal static partial class Ole32 - { - [ComImport] - [Guid("BEF6E003-A874-101A-8BBA-00AA00300CAB")] - [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] - public interface IFontDisp - { - string Name { get; set; } - - long Size { get; set; } - - bool Bold { get; set; } - - bool Italic { get; set; } - - bool Underline { get; set; } - - bool Strikethrough { get; set; } - - short Weight { get; set; } - - short Charset { get; set; } - } - } -} diff --git a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.QACONTAINER.cs b/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.QACONTAINER.cs index 82f4b61775f..37b3e1bb027 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.QACONTAINER.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.QACONTAINER.cs @@ -22,7 +22,7 @@ public sealed class QACONTAINER public QACONTAINERFLAGS dwAmbientFlags; public uint colorFore; public uint colorBack; - public IFont? pFont; + public IFont.Interface? pFont; public IntPtr pUndoMgr; public uint dwAppearance; public PInvoke.LCID lcid; diff --git a/src/System.Windows.Forms.Primitives/src/Interop/OleAut32/Interop.FONTDESC.cs b/src/System.Windows.Forms.Primitives/src/Interop/OleAut32/Interop.FONTDESC.cs deleted file mode 100644 index bfeca7927ef..00000000000 --- a/src/System.Windows.Forms.Primitives/src/Interop/OleAut32/Interop.FONTDESC.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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; - -internal partial class Interop -{ - internal static partial class Oleaut32 - { - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - public struct FONTDESC - { - public uint cbSizeOfStruct; - public string? lpstrName; - public long cySize; - public short sWeight; - public short sCharset; - public BOOL fItalic; - public BOOL fUnderline; - public BOOL fStrikethrough; - } - } -} diff --git a/src/System.Windows.Forms.Primitives/src/Interop/OleAut32/Interop.OleCreateFontIndirect.cs b/src/System.Windows.Forms.Primitives/src/Interop/OleAut32/Interop.OleCreateFontIndirect.cs deleted file mode 100644 index a6efa15a60d..00000000000 --- a/src/System.Windows.Forms.Primitives/src/Interop/OleAut32/Interop.OleCreateFontIndirect.cs +++ /dev/null @@ -1,17 +0,0 @@ -// 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; - -internal partial class Interop -{ - internal static partial class Oleaut32 - { - [DllImport(Libraries.Oleaut32, ExactSpelling = true, PreserveSig = false)] - public static extern Ole32.IFont OleCreateFontIndirect(ref FONTDESC lpFontDesc, in Guid riid); - - [DllImport(Libraries.Oleaut32, ExactSpelling = true, EntryPoint = "OleCreateFontIndirect", PreserveSig = false)] - public static extern Ole32.IFontDisp OleCreateIFontDispIndirect(ref FONTDESC lpFontDesc, in Guid riid); - } -} diff --git a/src/System.Windows.Forms.Primitives/src/Interop/OleAut32/Interop.VARIANT.cs b/src/System.Windows.Forms.Primitives/src/Interop/OleAut32/Interop.VARIANT.cs index cd178933cd9..0de7d640e0b 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/OleAut32/Interop.VARIANT.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/OleAut32/Interop.VARIANT.cs @@ -39,8 +39,11 @@ public void Clear() // // If the VARTYPE is a VT_VECTOR, the contents are cleared as above and CoTaskMemFree is also called on // cabstr.pElems. - + // // https://learn.microsoft.com/windows/win32/api/oleauto/nf-oleauto-variantclear#remarks + // + // - VT_BSTR (SysFreeString) + // - VT_DISPATCH / VT_UNKOWN (->Release(), if not VT_BYREF) fixed (void* t = &this) { @@ -841,6 +844,32 @@ private static object ToVector(in CA ca, VARENUM vectorType) private static Span GetSpan(Array arr) => MemoryMarshal.CreateSpan(ref Unsafe.AsRef(Marshal.UnsafeAddrOfPinnedArrayElement(arr, 0).ToPointer()), arr.Length); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator bool(VARIANT variant) + => variant.vt == VT_BOOL ? variant.data.boolVal != VARIANT_BOOL.VARIANT_FALSE : ThrowInvalidCast(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator short(VARIANT variant) + => variant.vt == VT_I2 ? variant.data.iVal : ThrowInvalidCast(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator BSTR(VARIANT variant) + => variant.vt == VT_BSTR ? variant.data.bstrVal : ThrowInvalidCast(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator CY(VARIANT variant) + => variant.vt == VT_CY ? variant.data.cyVal : ThrowInvalidCast(); + + public static explicit operator decimal(VARIANT variant) => variant.vt switch + { + VT_DECIMAL => variant.Anonymous.decVal.ToDecimal(), + VT_CY => decimal.FromOACurrency(variant.data.cyVal.int64), + _ => ThrowInvalidCast(), + }; + + [MethodImpl(MethodImplOptions.NoInlining)] + private static T ThrowInvalidCast() => throw new InvalidCastException(); + internal partial struct _Anonymous_e__Union { internal partial struct _Anonymous_e__Struct diff --git a/src/System.Windows.Forms.Primitives/src/NativeMethods.json b/src/System.Windows.Forms.Primitives/src/NativeMethods.json index 5c41408ad65..b92a8016b26 100644 --- a/src/System.Windows.Forms.Primitives/src/NativeMethods.json +++ b/src/System.Windows.Forms.Primitives/src/NativeMethods.json @@ -5,6 +5,7 @@ "comInterop": { "preserveSigMethods": [ "IAutoComplete2.SetOptions", + "IClassFactory.CreateInstance", "IDispatch.Invoke", "IFileDialog.Show", "IFileDialog.GetResult", diff --git a/src/System.Windows.Forms.Primitives/src/NativeMethods.txt b/src/System.Windows.Forms.Primitives/src/NativeMethods.txt index 16dccab75ff..c0e542b95a6 100644 --- a/src/System.Windows.Forms.Primitives/src/NativeMethods.txt +++ b/src/System.Windows.Forms.Primitives/src/NativeMethods.txt @@ -69,11 +69,23 @@ DestroyCursor DestroyIcon DestroyMenu DestroyWindow -DestroyWindow +DISP_E_ARRAYISLOCKED +DISP_E_BADINDEX +DISP_E_BADPARAMCOUNT +DISP_E_BADVARTYPE +DISP_E_BADCALLEE +DISP_E_BUFFERTOOSMALL DISP_E_DIVBYZERO DISP_E_EXCEPTION DISP_E_MEMBERNOTFOUND +DISP_E_NONAMEDARGS +DISP_E_NOTACOLLECTION +DISP_E_OVERFLOW DISP_E_PARAMNOTFOUND +DISP_E_PARAMNOTOPTIONAL +DISP_E_TYPEMISMATCH +DISP_E_UNKNOWNINTERFACE +DISP_E_UNKNOWNLCID DISP_E_UNKNOWNNAME DISPATCH_FLAGS DispatchMessage @@ -128,6 +140,7 @@ ExtTextOut FDAP FILETIME FillRect +FONTDESC FormatMessage FUNCDESC FUNCFLAGS @@ -267,6 +280,7 @@ HitTestThemeBackground HWND_* IAccessible IAutoComplete2 +IClassFactory IClassFactory2 ICM_MODE ICON_* @@ -416,6 +430,7 @@ OLE_E_ADVISENOTSUPPORTED OLE_E_INVALIDRECT OLE_E_NOCONNECTION OLE_E_PROMPTSAVECANCELLED +OleCreateFontIndirect OleCreatePictureIndirect OleFlushClipboard OleInitialize diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/Foundation/BSTR.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/Foundation/BSTR.cs index 7b91083c359..a3043093915 100644 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/Foundation/BSTR.cs +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/Foundation/BSTR.cs @@ -21,5 +21,15 @@ public void Dispose() } } + /// + /// Converts the to string and frees it. + /// + public readonly string ToStringAndFree() + { + string result = ToString(); + Dispose(); + return result; + } + public bool IsNull => Value is null; } diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/System/Com/CY.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/System/Com/CY.cs new file mode 100644 index 00000000000..0c45ecf521c --- /dev/null +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/System/Com/CY.cs @@ -0,0 +1,16 @@ +// 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. + +namespace Windows.Win32.System.Com; + +internal partial struct CY +{ + // https://learn.microsoft.com/openspecs/windows_protocols/ms-oaut/5a2b34c4-d109-438e-9ec8-84816d8de40d + + public static explicit operator decimal(CY value) => decimal.FromOACurrency(value.int64); + public static explicit operator CY(decimal value) => new() { int64 = decimal.ToOACurrency(value) }; + + public static explicit operator float(CY value) => (float)(value.int64 / 10000f); + public static explicit operator CY(float value) => new() { int64 = (long)(value * 10000) }; +} diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/System/Com/IDispatch.Interface.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/System/Com/IDispatch.Interface.cs new file mode 100644 index 00000000000..dec0243ed3d --- /dev/null +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/System/Com/IDispatch.Interface.cs @@ -0,0 +1,82 @@ +// 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 ComWrappers = Interop.WinFormsComWrappers; + +namespace Windows.Win32.System.Com; + +internal unsafe partial struct IDispatch : IVTable +{ + static void IVTable.PopulateComInterfaceVTable(Vtbl* vtable) + { + vtable->GetTypeInfoCount_4 = &GetTypeInfoCount; + vtable->GetTypeInfo_5 = &GetTypeInfo; + vtable->GetIDsOfNames_6 = &GetIDsOfNames; + vtable->Invoke_7 = &Invoke; + } + + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] + private static HRESULT GetTypeInfoCount(IDispatch* @this, uint* pctinfo) + => ComWrappers.UnwrapAndInvoke(@this, o => o.GetTypeInfoCount(pctinfo)); + + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] + private static HRESULT GetTypeInfo(IDispatch* @this, uint iTInfo, uint lcid, ITypeInfo** ppTInfo) + => ComWrappers.UnwrapAndInvoke(@this, o => o.GetTypeInfo(iTInfo, lcid, ppTInfo)); + + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] + private static HRESULT GetIDsOfNames(IDispatch* @this, Guid* riid, PWSTR* rgszNames, uint cNames, uint lcid, int* rgDispId) + => ComWrappers.UnwrapAndInvoke(@this, o => o.GetIDsOfNames(riid, rgszNames, cNames, lcid, rgDispId)); + + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] + private static HRESULT Invoke( + IDispatch* @this, + int dispIdMember, + Guid* riid, + uint lcid, + DISPATCH_FLAGS dwFlags, + DISPPARAMS* pDispParams, + VARIANT* pVarResult, + EXCEPINFO* pExcepInfo, + uint* pArgErr) + => ComWrappers.UnwrapAndInvoke( + @this, + o => o.Invoke(dispIdMember, riid, lcid, dwFlags, pDispParams, pVarResult, pExcepInfo, pArgErr)); + + [ComImport] + [Guid("00020400-0000-0000-C000-000000000046")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public unsafe interface Interface + { + [PreserveSig] + HRESULT GetTypeInfoCount( + uint* pctinfo); + + [PreserveSig] + HRESULT GetTypeInfo( + uint iTInfo, + uint lcid, + ITypeInfo** ppTInfo); + + [PreserveSig] + HRESULT GetIDsOfNames( + Guid* riid, + PWSTR* rgszNames, + uint cNames, + uint lcid, + int* rgDispId); + + [PreserveSig] + HRESULT Invoke( + int dispIdMember, + Guid* riid, + uint lcid, + DISPATCH_FLAGS dwFlags, + DISPPARAMS* pDispParams, + VARIANT* pVarResult, + EXCEPINFO* pExcepInfo, + uint* pArgErr); + } +} diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/System/Com/IDispatch.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/System/Com/IDispatch.cs index 9dd8d96c8ed..467c73b3458 100644 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/System/Com/IDispatch.cs +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/System/Com/IDispatch.cs @@ -29,5 +29,17 @@ internal HRESULT GetProperty( puArgErr: null); } } + + /// + /// Get the specified property. + /// + internal VARIANT GetProperty( + uint dispId, + uint lcid = 0) + { + VARIANT variant = default; + GetProperty(dispId, &variant, lcid).ThrowOnFailure(); + return variant; + } } } diff --git a/src/System.Windows.Forms.Primitives/tests/TestUtilities/Windows/Win32/System/Com/ComClassFactory.cs b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Windows/Win32/System/Com/ComClassFactory.cs new file mode 100644 index 00000000000..f44b43da39f --- /dev/null +++ b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Windows/Win32/System/Com/ComClassFactory.cs @@ -0,0 +1,71 @@ +// 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.ComponentModel; +using System.Runtime.InteropServices; + +namespace Windows.Win32.System.Com; + +/// +/// Wraps an from a dynamically loaded assembly. +/// +internal unsafe class ComClassFactory : IDisposable +{ + private readonly string _filePath; + public Guid ClassId { get; } + private readonly HINSTANCE _instance; + private readonly IClassFactory* _classFactory; + + private const string ExportMethodName = "DllGetClassObject"; + + public ComClassFactory( + string filePath, + Guid classId) + { + _filePath = filePath; + ClassId = classId; + _instance = PInvoke.LoadLibraryEx(filePath, HANDLE.Null, default); + if (_instance.IsNull) + { + throw new Win32Exception(); + } + + // Dynamically get the class factory method. + + // HRESULT DllGetClassObject( + // [in] REFCLSID rclsid, + // [in] REFIID riid, + // [out] LPVOID* ppv + // ); + + FARPROC proc = PInvoke.GetProcAddress(_instance, ExportMethodName); + IClassFactory* classFactory; + ((delegate* unmanaged)proc.Value)( + &classId, IID.Get(), + (void**)&classFactory).ThrowOnFailure(); + _classFactory = classFactory; + } + + internal HRESULT CreateInstance(out IUnknown* unknown) + { + unknown = default; + fixed (IUnknown** u = &unknown) + { + return _classFactory->CreateInstance(null, IID.Get(), (void**)u); + } + } + + internal HRESULT CreateInstance(out object unknown) + { + HRESULT result = CreateInstance(out IUnknown* punk); + unknown = punk is null ? null : Marshal.GetObjectForIUnknown((nint)punk); + return result; + } + + public void Dispose() + { + _classFactory->Release(); + PInvoke.FreeLibrary(_instance); + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/AxHost.OleInterfaces.cs b/src/System.Windows.Forms/src/System/Windows/Forms/AxHost.OleInterfaces.cs index d3708762cfc..d4c2c831d9a 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/AxHost.OleInterfaces.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/AxHost.OleInterfaces.cs @@ -86,7 +86,7 @@ internal void StartEvents() try { - _connectionPoint = new ConnectionPointCookie(nativeObject, this, typeof(IPropertyNotifySink)); + _connectionPoint = new ConnectionPointCookie(nativeObject, this, typeof(IPropertyNotifySink), throwException: false); } catch { diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/AxHost.cs b/src/System.Windows.Forms/src/System/Windows/Forms/AxHost.cs index 1dd17770cb6..66d2877e7e1 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/AxHost.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/AxHost.cs @@ -103,7 +103,7 @@ internal static class AxFlags private static readonly Guid s_maskEdit_Clsid = new("{c932ba85-4374-101b-a56c-00aa003668dc}"); // Static state for perf optimization - private static Dictionary s_fontTable; + private static Dictionary s_fontTable; // BitVector32 masks for various internal state flags. private static readonly int s_ocxStateSet = BitVector32.CreateMask(); @@ -3573,7 +3573,7 @@ private unsafe bool QuickActivate() qaContainer.pClientSite = _oleSite; qaContainer.pPropertyNotifySink = _oleSite; - qaContainer.pFont = (Ole32.IFont)GetIFontFromFont(GetParentContainer()._parent.Font); + qaContainer.pFont = (IFont.Interface)GetIFontFromFont(GetParentContainer()._parent.Font); qaContainer.dwAppearance = 0; qaContainer.lcid = PInvoke.GetThreadLocale(); @@ -3930,29 +3930,34 @@ protected static unsafe Image GetPictureFromIPictureDisp(object picture) } } - private static Oleaut32.FONTDESC GetFONTDESCFromFont(Font font) + /// + /// Gets a cached for a given . The returned + /// must have it's populated with + /// a newly pinned string before usage. + /// + private static FONTDESC GetFONTDESCFromFont(Font font) { if (s_fontTable is null) { - s_fontTable = new Dictionary(); + s_fontTable = new Dictionary(); } - else if (s_fontTable.TryGetValue(font, out Oleaut32.FONTDESC cachedFDesc)) + else if (s_fontTable.TryGetValue(font, out FONTDESC cachedFDesc)) { return cachedFDesc; } LOGFONTW logfont = LOGFONTW.FromFont(font); - var fdesc = new Oleaut32.FONTDESC + var fdesc = new FONTDESC { - cbSizeOfStruct = (uint)Marshal.SizeOf(), - lpstrName = font.Name, - cySize = (long)(font.SizeInPoints * 10000), + cbSizeofstruct = (uint)sizeof(FONTDESC), + cySize = (CY)font.SizeInPoints, sWeight = (short)logfont.lfWeight, sCharset = (short)logfont.lfCharSet, fItalic = font.Italic, fUnderline = font.Underline, fStrikethrough = font.Strikeout }; + s_fontTable[font] = fdesc; return fdesc; } @@ -3995,14 +4000,23 @@ protected static object GetIFontFromFont(Font font) try { - Oleaut32.FONTDESC fontDesc = GetFONTDESCFromFont(font); - return Oleaut32.OleCreateFontIndirect(ref fontDesc, in IID.GetRef()); + FONTDESC fontDesc = GetFONTDESCFromFont(font); + fixed (char* n = font.Name) + { + fontDesc.lpstrName = n; + HRESULT hr = PInvoke.OleCreateFontIndirect(in fontDesc, in IID.GetRef(), out void* lplpvObj); + if (hr.Succeeded) + { + return Marshal.GetObjectForIUnknown((nint)lplpvObj); + } + } } catch { - s_axHTraceSwitch.TraceVerbose($"Failed to create IFrom from font: {font}"); - return null; } + + s_axHTraceSwitch.TraceVerbose($"Failed to create IFrom from font: {font}"); + return null; } /// @@ -4016,7 +4030,7 @@ protected static Font GetFontFromIFont(object font) return null; } - Ole32.IFont oleFont = (Ole32.IFont)font; + IFont.Interface oleFont = (IFont.Interface)font; try { Font f = Font.FromHfont(oleFont.hFont); @@ -4047,8 +4061,13 @@ protected static object GetIFontDispFromFont(Font font) throw new ArgumentException(SR.AXFontUnitNotPoint, nameof(font)); } - Oleaut32.FONTDESC fontdesc = GetFONTDESCFromFont(font); - return Oleaut32.OleCreateIFontDispIndirect(ref fontdesc, in IID.GetRef()); + fixed (char* n = font.Name) + { + FONTDESC fontdesc = GetFONTDESCFromFont(font); + fontdesc.lpstrName = n; + PInvoke.OleCreateFontIndirect(in fontdesc, in IID.GetRef(), out void* lplpvObj).ThrowOnFailure(); + return Marshal.GetObjectForIUnknown((nint)lplpvObj); + } } /// @@ -4062,46 +4081,55 @@ protected static Font GetFontFromIFontDisp(object font) return null; } - if (font is Ole32.IFont ifont) + if (font is IFont.Interface ifont) { return GetFontFromIFont(ifont); } - Ole32.IFontDisp oleFont = (Ole32.IFontDisp)font; + IFontDisp.Interface oleFont = (IFontDisp.Interface)font; + using ComScope dispatch = new((IDispatch*)Marshal.GetIDispatchForObject(oleFont)); + FontStyle style = FontStyle.Regular; try { - if (oleFont.Bold) + if ((bool)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_BOLD)) { style |= FontStyle.Bold; } - if (oleFont.Italic) + if ((bool)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_ITALIC)) { style |= FontStyle.Italic; } - if (oleFont.Underline) + if ((bool)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_UNDER)) { style |= FontStyle.Underline; } - if (oleFont.Strikethrough) + if ((bool)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_STRIKE)) { style |= FontStyle.Strikeout; } - if (oleFont.Weight >= 700) + if ((short)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_WEIGHT) >= 700) { style |= FontStyle.Bold; } - return new Font(oleFont.Name, (float)oleFont.Size / 10000, style, GraphicsUnit.Point, (byte)oleFont.Charset); + using BSTR name = (BSTR)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_NAME); + + return new Font( + name.ToString(), + (float)(CY)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_SIZE), + style, + GraphicsUnit.Point, + (byte)(short)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_CHARSET)); } catch (Exception e) { - s_axHTraceSwitch.TraceVerbose($"Could not create font from: {oleFont.Name}. {e.Message}"); + s_axHTraceSwitch.TraceVerbose($"Could not create font from IFontDisp: {e.Message}"); return DefaultFont; } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ComponentModel/COM2Interop/COM2FontConverter.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ComponentModel/COM2Interop/COM2FontConverter.cs index 99dc9d2ff9d..0a0a357230e 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ComponentModel/COM2Interop/COM2FontConverter.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ComponentModel/COM2Interop/COM2FontConverter.cs @@ -3,16 +3,17 @@ // See the LICENSE file in the project root for more information. using System.Drawing; -using static Interop.Ole32; +using Windows.Win32.System.Com; +using Windows.Win32.System.Ole; namespace System.Windows.Forms.ComponentModel.Com2Interop { /// - /// This class maps an OLE_COLOR to a managed Color editor. + /// This class maps an IFont to a managed Font. /// internal class Com2FontConverter : Com2DataTypeToManagedDataTypeConverter { - private IntPtr _lastHandle = IntPtr.Zero; + private HFONT _lastHandle = HFONT.Null; private Font? _lastFont; public override bool AllowExpand => true; @@ -21,14 +22,14 @@ internal class Com2FontConverter : Com2DataTypeToManagedDataTypeConverter public override object ConvertNativeToManaged(object? nativeValue, Com2PropertyDescriptor pd) { - if (nativeValue is not IFont nativeFont) + if (nativeValue is not IFont.Interface nativeFont) { - _lastHandle = IntPtr.Zero; + _lastHandle = HFONT.Null; _lastFont = Control.DefaultFont; return _lastFont; } - IntPtr fontHandle = nativeFont.hFont; + HFONT fontHandle = nativeFont.hFont; // Do we have this cached? if (fontHandle == _lastHandle && _lastFont is not null) @@ -59,6 +60,7 @@ public override object ConvertNativeToManaged(object? nativeValue, Com2PropertyD { managedValue ??= Control.DefaultFont; + // We never set the object back as we have a modifiable IFont handle. cancelSet = true; if (_lastFont is not null && _lastFont.Equals(managedValue)) @@ -68,11 +70,12 @@ public override object ConvertNativeToManaged(object? nativeValue, Com2PropertyD _lastFont = (Font)managedValue; - if (pd.GetNativeValue(pd.TargetObject) is IFont nativeFont) + if (pd.GetNativeValue(pd.TargetObject) is IFont.Interface nativeFont) { - bool changed = ControlPaint.FontToIFont(_lastFont, nativeFont); + // Apply any differences back to the IFont handle + ApplyFontSettings(_lastFont, nativeFont, out bool targetChanged); - if (changed) + if (targetChanged) { _lastFont = null; ConvertNativeToManaged(nativeFont, pd); @@ -81,5 +84,67 @@ public override object ConvertNativeToManaged(object? nativeValue, Com2PropertyD return null; } + + private static void ApplyFontSettings(Font source, IFont.Interface target, out bool targetChanged) + { + targetChanged = false; + + // We need to go through all the pain of the diff here because it looks like setting them all has different + // results based on the order and each individual IFont implementor. + + if (!source.Name.Equals(target.Name.ToStringAndFree())) + { + target.Name = new(source.Name); + targetChanged = true; + } + + if (source.SizeInPoints != (float)target.Size) + { + target.Size = (CY)source.SizeInPoints; + targetChanged = true; + } + + LOGFONTW logfont = LOGFONTW.FromFont(source); + + if (target.Weight != (short)logfont.lfWeight) + { + target.Weight = (short)logfont.lfWeight; + targetChanged = true; + } + + bool isBold = logfont.lfWeight >= (int)FW.BOLD; + if (target.Bold != isBold) + { + target.Bold = isBold; + targetChanged = true; + } + + bool isItalic = logfont.lfItalic != 0; + if (target.Italic != isItalic) + { + target.Italic = isItalic; + targetChanged = true; + } + + bool isUnderline = logfont.lfUnderline != 0; + if (target.Underline != isUnderline) + { + target.Underline = isUnderline; + targetChanged = true; + } + + bool isStrike = logfont.lfStrikeOut != 0; + if (target.Strikethrough != isStrike) + { + target.Strikethrough = isStrike; + targetChanged = true; + } + + if (target.Charset != (short)logfont.lfCharSet) + { + target.Charset = (short)logfont.lfCharSet; + targetChanged = true; + } + } } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ComponentModel/COM2Interop/COM2PropertyDescriptor.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ComponentModel/COM2Interop/COM2PropertyDescriptor.cs index 77184c3da5c..a33911d8e5e 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ComponentModel/COM2Interop/COM2PropertyDescriptor.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ComponentModel/COM2Interop/COM2PropertyDescriptor.cs @@ -80,8 +80,8 @@ internal partial class Com2PropertyDescriptor : PropertyDescriptor, ICloneable private static readonly IDictionary s_oleConverters = new SortedList { [GUID_COLOR] = typeof(Com2ColorConverter), - [typeof(Ole32.IFontDisp).GUID] = typeof(Com2FontConverter), - [typeof(Ole32.IFont).GUID] = typeof(Com2FontConverter), + [IID.GetRef()] = typeof(Com2FontConverter), + [IID.GetRef()] = typeof(Com2FontConverter), [IID.GetRef()] = typeof(Com2PictureConverter), [IID.GetRef()] = typeof(Com2PictureConverter) }; @@ -664,30 +664,29 @@ private TypeConverter GetBaseTypeConverter() component = descriptor.GetPropertyOwner(this); } - if (component is null || !Marshal.IsComObject(component) || component is not Oleaut32.IDispatch dispatch) + if (component is null || component is not IDispatch.Interface dispatch) { return null; } - object[] pVarResult = new object[1]; + VARIANT result = default; EXCEPINFO pExcepInfo = default; DISPPARAMS dispParams = default; Guid guid = Guid.Empty; HRESULT hr = dispatch.Invoke( - DISPID, + (int)DISPID, &guid, PInvoke.GetThreadLocale(), DISPATCH_FLAGS.DISPATCH_PROPERTYGET, &dispParams, - pVarResult, + &result, &pExcepInfo, null); if (hr == HRESULT.S_OK || hr == HRESULT.S_FALSE) { - _lastValue = pVarResult[0] is null || Convert.IsDBNull(pVarResult[0]) ? null : pVarResult[0]; - + _lastValue = result.ToObject(); return _lastValue; } else diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ComponentModel/COM2Interop/ComNativeDescriptor.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ComponentModel/COM2Interop/ComNativeDescriptor.cs index d2dde78b3b8..ff1bc08d0bc 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ComponentModel/COM2Interop/ComNativeDescriptor.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ComponentModel/COM2Interop/ComNativeDescriptor.cs @@ -116,7 +116,8 @@ internal static string GetName(object component) Guid guid = Guid.Empty; try { - return dispatch.GetIDsOfNames(&guid, names, 1, PInvoke.GetThreadLocale(), &dispid).Failed || dispid == DispatchID.UNKNOWN + HRESULT result = dispatch.GetIDsOfNames(&guid, names, 1, PInvoke.GetThreadLocale(), &dispid); + return result.Failed || dispid == DispatchID.UNKNOWN ? null : GetPropertyValue(component, dispid, ref succeeded); } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Control.ActiveXFontMarshaler.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Control.ActiveXFontMarshaler.cs index dd9aa17000f..9872763d6b1 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Control.ActiveXFontMarshaler.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Control.ActiveXFontMarshaler.cs @@ -4,71 +4,71 @@ using System.Drawing; using System.Runtime.InteropServices; -using static Interop; +using Windows.Win32.System.Com; +using Windows.Win32.System.Ole; -namespace System.Windows.Forms +namespace System.Windows.Forms; + +public partial class Control { - public partial class Control + /// + /// This is a marshaler object that knows how to marshal IFont to Font and back. + /// + private unsafe class ActiveXFontMarshaler : ICustomMarshaler { - /// - /// This is a marshaler object that knows how to marshal IFont to Font and back. - /// - private class ActiveXFontMarshaler : ICustomMarshaler + private static ActiveXFontMarshaler? s_instance; + + public void CleanUpManagedData(object obj) { - private static ActiveXFontMarshaler? s_instance; + } - public void CleanUpManagedData(object obj) - { - } + public void CleanUpNativeData(IntPtr pObj) => Marshal.Release(pObj); - public void CleanUpNativeData(IntPtr pObj) => Marshal.Release(pObj); + internal static ICustomMarshaler GetInstance(string cookie) + { + return s_instance ??= new ActiveXFontMarshaler(); + } - internal static ICustomMarshaler GetInstance(string cookie) - { - return s_instance ??= new ActiveXFontMarshaler(); - } + public int GetNativeDataSize() => -1; // not a value type, so use -1 - public int GetNativeDataSize() => -1; // not a value type, so use -1 + public nint MarshalManagedToNative(object obj) + { + Font font = (Font)obj; + LOGFONTW logFont = LOGFONTW.FromFont(font); - public IntPtr MarshalManagedToNative(object obj) + fixed (char* n = font.Name) { - Font font = (Font)obj; - LOGFONTW logFont = LOGFONTW.FromFont(font); - var fontDesc = new Oleaut32.FONTDESC + FONTDESC fontDesc = new() { - cbSizeOfStruct = (uint)Marshal.SizeOf(), - lpstrName = font.Name, - cySize = (long)(font.SizeInPoints * 10000), + cbSizeofstruct = (uint)sizeof(FONTDESC), + lpstrName = n, + cySize = (CY)font.SizeInPoints, sWeight = (short)logFont.lfWeight, sCharset = (short)logFont.lfCharSet, fItalic = font.Italic, fUnderline = font.Underline, fStrikethrough = font.Strikeout, }; - Guid iid = typeof(Ole32.IFont).GUID; - Ole32.IFont oleFont = Oleaut32.OleCreateFontIndirect(ref fontDesc, in iid); - IntPtr pFont = Marshal.GetIUnknownForObject(oleFont); - - int hr = Marshal.QueryInterface(pFont, ref iid, out IntPtr pIFont); - - Marshal.Release(pFont); - ((HRESULT)hr).ThrowOnFailure(); + PInvoke.OleCreateFontIndirect( + in fontDesc, + in IID.GetRef(), + out void* lplpvObj).ThrowOnFailure(); - return pIFont; + return (nint)lplpvObj; } + } - public object MarshalNativeToManaged(IntPtr pObj) + public object MarshalNativeToManaged(nint pObj) + { + IFont.Interface nativeFont = (IFont.Interface)Marshal.GetObjectForIUnknown(pObj); + try { - Ole32.IFont nativeFont = (Ole32.IFont)Marshal.GetObjectForIUnknown(pObj); - try - { - return Font.FromHfont(nativeFont.hFont); - } - catch (Exception e) when (!ClientUtils.IsCriticalException(e)) - { - return DefaultFont; - } + return Font.FromHfont(nativeFont.hFont); + } + catch (Exception e) when (!ClientUtils.IsCriticalException(e)) + { + return DefaultFont; } } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Control.ActiveXImpl.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Control.ActiveXImpl.cs index 5da75ad0e75..ec0884d2a5d 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Control.ActiveXImpl.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Control.ActiveXImpl.cs @@ -132,7 +132,7 @@ internal Font? AmbientFont { Debug.Assert(obj is not null, "GetAmbientProperty failed"); Debug.WriteLineIf(CompModSwitches.ActiveX.TraceInfo, $"Object font type={obj.GetType().FullName}"); - Ole32.IFont ifont = (Ole32.IFont)obj; + IFont.Interface ifont = (IFont.Interface)obj; prop.Value = Font.FromHfont(ifont.hFont); } catch (Exception e) when (!ClientUtils.IsCriticalException(e)) diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ControlPaint.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ControlPaint.cs index 49fb65eb1ac..27e98b42d7b 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ControlPaint.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ControlPaint.cs @@ -2118,87 +2118,6 @@ internal static Font FontInPoints(Font font) font.GdiCharSet, font.GdiVerticalFont); - /// - /// Returns whether or not was changed. - /// - internal static bool FontToIFont(Font source, Ole32.IFont target) - { - bool changed = false; - - // We need to go through all the pain of the diff here because it looks like setting them all has different - // results based on the order and each individual IFont implementor. - - string fontName = target.Name; - if (!source.Name.Equals(fontName)) - { - target.Name = source.Name; - changed = true; - } - - // This always seems to come back as the point size * 10000 (HIMETRIC?), regardless or ratio or mapping - // mode despite what the documentation says. - - float fontSize = (float)target.Size / 10000; - - // Size must be in points - float winformsSize = source.SizeInPoints; - if (winformsSize != fontSize) - { - target.Size = (long)(winformsSize * 10000); - changed = true; - } - - LOGFONTW logfont = LOGFONTW.FromFont(source); - - short fontWeight = target.Weight; - if (fontWeight != (short)logfont.lfWeight) - { - target.Weight = (short)logfont.lfWeight; - changed = true; - } - - bool fontBold = target.Bold; - bool isBold = logfont.lfWeight >= (int)FW.BOLD; - if (fontBold != isBold) - { - target.Bold = isBold; - changed = true; - } - - bool fontItalic = target.Italic; - bool isItalic = logfont.lfItalic != 0; - if (fontItalic != isItalic) - { - target.Italic = isItalic; - changed = true; - } - - bool fontUnderline = target.Underline; - bool isUnderline = logfont.lfUnderline != 0; - if (fontUnderline != isUnderline) - { - target.Underline = isUnderline; - changed = true; - } - - bool fontStrike = target.Strikethrough; - bool isStrike = logfont.lfStrikeOut != 0; - if (fontStrike != isStrike) - { - target.Strikethrough = isStrike; - changed = true; - } - - short fontCharset = target.Charset; - if (fontCharset != (short)logfont.lfCharSet) - { - target.Charset = (short)logfont.lfCharSet; - changed = true; - } - - return changed; - } - /// /// This makes a choice from a set of raster op codes, based on the color given. If the color is considered to /// be "dark", the raster op provided by dark will be returned. diff --git a/src/System.Windows.Forms/tests/TestUtilities/ThreadExceptionFixture.cs b/src/System.Windows.Forms/tests/TestUtilities/ThreadExceptionFixture.cs index e3b336e1f93..217e6b9a700 100644 --- a/src/System.Windows.Forms/tests/TestUtilities/ThreadExceptionFixture.cs +++ b/src/System.Windows.Forms/tests/TestUtilities/ThreadExceptionFixture.cs @@ -7,6 +7,9 @@ namespace System { + /// + /// Prevents the exception dialog from firing. + /// public class ThreadExceptionFixture : IDisposable { public ThreadExceptionFixture() @@ -22,7 +25,6 @@ public ThreadExceptionFixture() public virtual void Dispose() { Application.ThreadException -= OnThreadException; - //Xunit.Assert.Equal(new Drawing.Point(Screen.PrimaryScreen.WorkingArea.Width, Screen.PrimaryScreen.WorkingArea.Height), Cursor.Position); } private void OnThreadException(object sender, ThreadExceptionEventArgs e) diff --git a/src/System.Windows.Forms/tests/UnitTests/System.Windows.Forms.Tests.csproj b/src/System.Windows.Forms/tests/UnitTests/System.Windows.Forms.Tests.csproj index 0a541e307e4..8f1c4a98dae 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System.Windows.Forms.Tests.csproj +++ b/src/System.Windows.Forms/tests/UnitTests/System.Windows.Forms.Tests.csproj @@ -60,6 +60,12 @@ + + + PreserveNewest + + + $(ArtifactsBinDir)\AxHosts\$(Configuration)\net472\AxInterop.WMPLib.dll diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/AxHost.VisualBasic6Tests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/AxHost.VisualBasic6Tests.cs new file mode 100644 index 00000000000..b0f06079513 --- /dev/null +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/AxHost.VisualBasic6Tests.cs @@ -0,0 +1,30 @@ +// 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.ComponentModel; +using System.Runtime.InteropServices; +using System.Windows.Forms.Tests.TestResources; +using Xunit; + +namespace System.Windows.Forms.Tests; + +public class AxHostVisualBasic6Tests : IClassFixture +{ + [WinFormsFact(Skip = "Causes test run to abort, must be run manually.")] + public void AxHost_SimpleControl_Create() + { + if (RuntimeInformation.ProcessArchitecture != Architecture.X86) + { + return; + } + + using Form form = new(); + form.Shown += (object sender, EventArgs e) => form.Close(); + using DynamicAxHost control = new(ComClasses.VisualBasicSimpleControl); + ((ISupportInitialize)control).BeginInit(); + form.Controls.Add(control); + ((ISupportInitialize)control).EndInit(); + form.ShowDialog(); + } +} diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/AxHostTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/AxHostTests.cs index 310f0bab836..dc1c478f7f3 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/AxHostTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/AxHostTests.cs @@ -13,7 +13,6 @@ using Windows.Win32.System.Com; using Windows.Win32.System.Ole; using Xunit; -using static Interop; using Point = System.Drawing.Point; using Size = System.Drawing.Size; @@ -1433,13 +1432,13 @@ public void AxHost_GetFontFromIFont_InvalidFont_ThrowsInvalidCastException() } [WinFormsFact] - public void AxHost_GetIFontDispFromFont_InvokeSimpleStyle_Roundtrips() + public unsafe void AxHost_GetIFontDispFromFont_InvokeSimpleStyle_Roundtrips() { using var font = new Font("Arial", 10); object disp = SubAxHost.GetIFontDispFromFont(font); - Ole32.IFont iFont = (Ole32.IFont)disp; - Assert.Equal(font.Name, iFont.Name); - Assert.Equal(97500, iFont.Size); + IFont.Interface iFont = (IFont.Interface)disp; + Assert.Equal(font.Name, iFont.Name.ToStringAndFree()); + Assert.Equal(97500, iFont.Size.int64); Assert.False(iFont.Bold); Assert.False(iFont.Italic); Assert.False(iFont.Underline); @@ -1448,13 +1447,14 @@ public void AxHost_GetIFontDispFromFont_InvokeSimpleStyle_Roundtrips() // Assert.Equal(0, iFont.Charset); Assert.NotEqual(IntPtr.Zero, iFont.hFont); - Ole32.IFontDisp iFontDisp = (Ole32.IFontDisp)disp; - Assert.Equal(font.Name, iFontDisp.Name); - Assert.Equal(10, iFontDisp.Size); - Assert.False(iFontDisp.Bold); - Assert.False(iFontDisp.Italic); - Assert.False(iFontDisp.Underline); - Assert.False(iFontDisp.Strikethrough); + IFontDisp.Interface iFontDisp = (IFontDisp.Interface)disp; + using ComScope dispatch = new((IDispatch*)Marshal.GetIDispatchForObject(iFontDisp)); + Assert.Equal(font.Name, ((BSTR)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_NAME)).ToStringAndFree()); + Assert.Equal(97500, ((CY)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_SIZE)).int64); + Assert.False((bool)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_BOLD)); + Assert.False((bool)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_ITALIC)); + Assert.False((bool)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_UNDER)); + Assert.False((bool)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_STRIKE)); // Charset is locale specific // Assert.Equal(0, iFontDisp.Charset); @@ -1472,14 +1472,14 @@ public void AxHost_GetIFontDispFromFont_InvokeSimpleStyle_Roundtrips() } [WinFormsFact] - public void AxHost_GetIFontDispFromFont_InvokeComplexStyle_Roundtrips() + public unsafe void AxHost_GetIFontDispFromFont_InvokeComplexStyle_Roundtrips() { using var font = new Font("Arial", 10, FontStyle.Bold | FontStyle.Underline | FontStyle.Italic | FontStyle.Strikeout, GraphicsUnit.Point, 10); object disp = SubAxHost.GetIFontDispFromFont(font); - Ole32.IFont iFont = (Ole32.IFont)disp; - Assert.Equal(font.Name, iFont.Name); - Assert.Equal(97500, iFont.Size); + IFont.Interface iFont = (IFont.Interface)disp; + Assert.Equal(font.Name, iFont.Name.ToStringAndFree()); + Assert.Equal(97500, iFont.Size.int64); Assert.True(iFont.Bold); Assert.True(iFont.Italic); Assert.True(iFont.Underline); @@ -1487,14 +1487,15 @@ public void AxHost_GetIFontDispFromFont_InvokeComplexStyle_Roundtrips() Assert.Equal(0, iFont.Charset); Assert.NotEqual(IntPtr.Zero, iFont.hFont); - Ole32.IFontDisp iFontDisp = (Ole32.IFontDisp)disp; - Assert.Equal(font.Name, iFontDisp.Name); - Assert.Equal(10, iFontDisp.Size); - Assert.True(iFontDisp.Bold); - Assert.True(iFontDisp.Italic); - Assert.True(iFontDisp.Underline); - Assert.True(iFontDisp.Strikethrough); - Assert.Equal(0, iFontDisp.Charset); + IFontDisp.Interface iFontDisp = (IFontDisp.Interface)disp; + using ComScope dispatch = new((IDispatch*)Marshal.GetIDispatchForObject(iFontDisp)); + Assert.Equal(font.Name, ((BSTR)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_NAME)).ToStringAndFree()); + Assert.Equal(97500, ((CY)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_SIZE)).int64); + Assert.True((bool)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_BOLD)); + Assert.True((bool)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_ITALIC)); + Assert.True((bool)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_UNDER)); + Assert.True((bool)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_STRIKE)); + Assert.Equal(0, (short)dispatch.Value->GetProperty(PInvoke.DISPID_FONT_CHARSET)); Font result = SubAxHost.GetFontFromIFont(iFont); Assert.Equal(font.Name, result.Name); @@ -1531,9 +1532,10 @@ public void AxHost_GetIFontDispFromFont_NullFont_ReturnsNull() public void AxHost_GetIFontFromFont_InvokeSimpleStyle_Roundtrips() { using var font = new Font("Arial", 10); - Ole32.IFont iFont = (Ole32.IFont)SubAxHost.GetIFontFromFont(font); - Assert.Equal(font.Name, iFont.Name); - Assert.Equal(97500, iFont.Size); + IFont.Interface iFont = (IFont.Interface)SubAxHost.GetIFontFromFont(font); + using BSTR name = iFont.Name; + Assert.Equal(font.Name, name.ToString()); + Assert.Equal(97500, iFont.Size.int64); Assert.False(iFont.Bold); Assert.False(iFont.Italic); Assert.False(iFont.Underline); @@ -1553,9 +1555,10 @@ public void AxHost_GetIFontFromFont_InvokeSimpleStyle_Roundtrips() public void AxHost_GetIFontFromFont_InvokeComplexStyle_Roundtrips() { using var font = new Font("Arial", 10, FontStyle.Bold | FontStyle.Underline | FontStyle.Italic | FontStyle.Strikeout, GraphicsUnit.Point, 10); - Ole32.IFont iFont = (Ole32.IFont)SubAxHost.GetIFontFromFont(font); - Assert.Equal(font.Name, iFont.Name); - Assert.Equal(97500, iFont.Size); + IFont.Interface iFont = (IFont.Interface)SubAxHost.GetIFontFromFont(font); + using BSTR name = iFont.Name; + Assert.Equal(font.Name, name.ToString()); + Assert.Equal(97500, iFont.Size.int64); Assert.True(iFont.Bold); Assert.True(iFont.Italic); Assert.True(iFont.Underline); diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComponentModel/Com2Interop/COM2FontConverterTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComponentModel/Com2Interop/COM2FontConverterTests.cs new file mode 100644 index 00000000000..fe629078db8 --- /dev/null +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComponentModel/Com2Interop/COM2FontConverterTests.cs @@ -0,0 +1,166 @@ +// 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. + +#nullable enable + +using System.ComponentModel; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows.Forms.ComponentModel.Com2Interop; +using Windows.Win32.System.Com; +using Windows.Win32.System.Ole; +using Xunit; + +namespace System.Windows.Forms.Tests.ComponentModel.Com2Interop; + +public unsafe class COM2FontConverterTests : IClassFixture +{ + private static readonly Com2PropertyDescriptor s_stubDescriptor = new Com2PropertyDescriptor( + default, + "Foo", + Array.Empty(), + default, + default, + default, + default); + + [Fact] + public void COM2FontConverter_ConvertNativeToManaged() + { + fixed (char* n = "Arial") + { + FONTDESC fontDesc = new() + { + cbSizeofstruct = (uint)sizeof(FONTDESC), + lpstrName = n, + cySize = (CY)12.0f + }; + + using ComScope iFont = new(null); + PInvoke.OleCreateFontIndirect(&fontDesc, IID.Get(), iFont).ThrowOnFailure(); + object wrapper = Marshal.GetObjectForIUnknown((nint)(void*)iFont.Value); + + Com2FontConverter converter = new(); + using Font font = (Font)converter.ConvertNativeToManaged(wrapper, s_stubDescriptor); + + Assert.Equal("Arial", font.Name); + Assert.Equal(12, font.Size); + + bool cancelSet = false; + object? native = converter.ConvertManagedToNative(font, s_stubDescriptor, ref cancelSet); + Assert.Null(native); + Assert.True(cancelSet); + } + } + + [Fact] + public void COM2FontConverter_ConvertManagedToNative() + { + fixed (char* n = "Arial") + { + FONTDESC fontDesc = new() + { + cbSizeofstruct = (uint)sizeof(FONTDESC), + lpstrName = n, + cySize = (CY)12.0f + }; + + using ComScope iFont = new(null); + PInvoke.OleCreateFontIndirect(&fontDesc, IID.Get(), iFont).ThrowOnFailure(); + object wrapper = Marshal.GetObjectForIUnknown((nint)(void*)iFont.Value); + + Com2FontConverter converter = new(); + using Font font = (Font)converter.ConvertNativeToManaged(wrapper, s_stubDescriptor); + using Font newFont = new(font.Name, 20.0f); + + bool cancelSet = false; + object? native = (IFont.Interface)converter.ConvertManagedToNative( + newFont, + new CustomGetNativeValueDescriptor(new VARIANT() + { + vt = VARENUM.VT_UNKNOWN, + data = new() + { + punkVal = (IUnknown*)iFont.Value + } + }), + ref cancelSet)!; + + Assert.True(cancelSet); + Assert.Null(native); + Assert.Equal("Arial", iFont.Value->Name.ToStringAndFree()); + Assert.Equal(20.0f, (float)iFont.Value->Size, precision: 0); + } + } + + private class CustomGetNativeValueDescriptor : Com2PropertyDescriptor + { + private ICustomTypeDescriptor _descriptor; + + public CustomGetNativeValueDescriptor(VARIANT nativeValue) + : base(default, "Foo", Array.Empty(), default, default, default, default) + { + _descriptor = new CustomDescriptor(nativeValue); + } + + public override object TargetObject => _descriptor; + + private class CustomDescriptor : ICustomTypeDescriptor + { + private readonly object _propertyOwner; + + public CustomDescriptor(VARIANT variant) => _propertyOwner = new DispatchStub(variant); + + public AttributeCollection GetAttributes() => throw new NotImplementedException(); + + public string? GetClassName() => throw new NotImplementedException(); + + public string? GetComponentName() => throw new NotImplementedException(); + + public TypeConverter? GetConverter() => throw new NotImplementedException(); + + public EventDescriptor? GetDefaultEvent() => throw new NotImplementedException(); + + public PropertyDescriptor? GetDefaultProperty() => throw new NotImplementedException(); + + public object? GetEditor(Type editorBaseType) => throw new NotImplementedException(); + + public EventDescriptorCollection GetEvents() => throw new NotImplementedException(); + + public EventDescriptorCollection GetEvents(Attribute[]? attributes) => throw new NotImplementedException(); + + public PropertyDescriptorCollection GetProperties() => throw new NotImplementedException(); + + public PropertyDescriptorCollection GetProperties(Attribute[]? attributes) => throw new NotImplementedException(); + + public object? GetPropertyOwner(PropertyDescriptor? pd) => _propertyOwner; + + private class DispatchStub : IDispatch.Interface + { + private readonly VARIANT _variant; + public DispatchStub(VARIANT variant) => _variant = variant; + + HRESULT IDispatch.Interface.GetTypeInfoCount(uint* pctinfo) => throw new NotImplementedException(); + + HRESULT IDispatch.Interface.GetTypeInfo(uint iTInfo, uint lcid, ITypeInfo** ppTInfo) => throw new NotImplementedException(); + + HRESULT IDispatch.Interface.GetIDsOfNames(Guid* riid, PWSTR* rgszNames, uint cNames, uint lcid, int* rgDispId) => throw new NotImplementedException(); + + HRESULT IDispatch.Interface.Invoke( + int dispIdMember, + Guid* riid, + uint lcid, + DISPATCH_FLAGS dwFlags, + DISPPARAMS* pDispParams, + VARIANT* pVarResult, + EXCEPINFO* pExcepInfo, + uint* pArgErr) + { + *pVarResult = _variant; + return HRESULT.S_OK; + } + } + } + } +} diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComponentModel/Com2Interop/ComNativeDescriptorTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComponentModel/Com2Interop/ComNativeDescriptorTests.cs index d071d6d9669..e27e970a0ad 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComponentModel/Com2Interop/ComNativeDescriptorTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComponentModel/Com2Interop/ComNativeDescriptorTests.cs @@ -4,6 +4,8 @@ using System.ComponentModel; using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows.Forms.Tests.TestResources; using Windows.Win32.System.Ole; using WMPLib; using Xunit; @@ -105,5 +107,30 @@ public void ComNativeDescriptor_GetProperties_FromActiveXControl() var converter = (Com2ExtendedTypeConverter)urlProperty.Converter; Assert.IsAssignableFrom(converter.InnerConverter); } + + [WinFormsFact(Skip = "Causes test run to abort, must be run manually.")] + public void ComNativeDescriptor_GetProperties_FromSimpleVBControl() + { + if (RuntimeInformation.ProcessArchitecture != Architecture.X86) + { + return; + } + + // Not much to see with this control, but it does exercise a fair amount of code. + ComClasses.VisualBasicSimpleControl.CreateInstance(out object vbcontrol).ThrowOnFailure(); + + var properties = TypeDescriptor.GetProperties(vbcontrol); + Assert.Empty(properties); + + var events = TypeDescriptor.GetEvents(vbcontrol); + Assert.Empty(events); + + var attributes = TypeDescriptor.GetAttributes(vbcontrol); + Assert.Equal(2, attributes.Count); + BrowsableAttribute browsable = (BrowsableAttribute)attributes[0]; + Assert.True(browsable.Browsable); + DesignTimeVisibleAttribute visible = (DesignTimeVisibleAttribute)attributes[1]; + Assert.False(visible.Visible); + } } } diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/Control.ActiveXFontMarshallerTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/Control.ActiveXFontMarshallerTests.cs new file mode 100644 index 00000000000..9de81cc7d35 --- /dev/null +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/Control.ActiveXFontMarshallerTests.cs @@ -0,0 +1,36 @@ +// 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. + +#nullable enable + +using System.Drawing; +using System.Runtime.InteropServices; +using Windows.Win32.System.Ole; +using Xunit; + +namespace System.Windows.Forms.Tests; + +public unsafe class Control_ActiveXFontMarshalerTests : IClassFixture +{ + private static readonly ICustomMarshaler s_marshaler = + (ICustomMarshaler)Activator.CreateInstance( + typeof(Control).Assembly.GetType("System.Windows.Forms.Control+ActiveXFontMarshaler")!, + nonPublic: true)!; + + [StaFact] + public void ActiveXFontMarshaler_RoundTripManagedFont() + { + using Font font = new("Arial", 11.0f, GraphicsUnit.Point); + nint native = s_marshaler.MarshalManagedToNative(font); + Assert.NotEqual(0, native); + using ComScope iFont = new((IFont*)native); + using BSTR name = iFont.Value->Name; + Assert.Equal(font.Name, name.ToString()); + Assert.Equal(11.25f, (float)iFont.Value->Size); + + using Font outFont = (Font)s_marshaler.MarshalNativeToManaged(native); + Assert.Equal(font.Name, outFont.Name); + Assert.Equal(11.25f, outFont.SizeInPoints); + } +} diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DynamicAxHost.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DynamicAxHost.cs new file mode 100644 index 00000000000..486f2879d6d --- /dev/null +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DynamicAxHost.cs @@ -0,0 +1,30 @@ +// 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. + +#nullable enable + +using Windows.Win32.System.Com; +using System.Diagnostics; + +namespace System.Windows.Forms.Tests; + +/// +/// Custom that directly uses a . +/// +public unsafe class DynamicAxHost : AxHost +{ + private readonly ComClassFactory _factory; + + internal DynamicAxHost(ComClassFactory factory) : base(factory.ClassId.ToString("D"), 0) + { + _factory = factory; + } + + protected override object CreateInstanceCore(Guid clsid) + { + Debug.Assert(clsid == _factory.ClassId); + _factory.CreateInstance(out object unknown).ThrowOnFailure(); + return unknown; + } +} diff --git a/src/System.Windows.Forms/tests/UnitTests/TestResources/ComClasses.cs b/src/System.Windows.Forms/tests/UnitTests/TestResources/ComClasses.cs new file mode 100644 index 00000000000..d49c88336d7 --- /dev/null +++ b/src/System.Windows.Forms/tests/UnitTests/TestResources/ComClasses.cs @@ -0,0 +1,21 @@ +// 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. + +#nullable enable + +using System.Runtime.InteropServices; +using Windows.Win32.System.Com; + +namespace System.Windows.Forms.Tests.TestResources; + +internal static class ComClasses +{ + public static ComClassFactory? VisualBasicSimpleControl { get; } + = RuntimeInformation.ProcessArchitecture == Architecture.X86 + ? new ComClassFactory( + // This is actually a .ocx + Path.GetFullPath(@"TestResources\VB6\SimpleControl.vb6"), + new Guid("366D0F1F-2D8A-4C9B-897D-CE1E74EDD6E5")) + : null; +} diff --git a/src/System.Windows.Forms/tests/UnitTests/TestResources/VB6/SimpleControl.vb6 b/src/System.Windows.Forms/tests/UnitTests/TestResources/VB6/SimpleControl.vb6 new file mode 100644 index 0000000000000000000000000000000000000000..79d9d2124e6cc10b355cdf4c4af1874a46b2d61d GIT binary patch literal 24576 zcmeHP4RBP|6+RmlNg%%zBuF4j4N5g;-sX=-ve-307$6C25|T($lO_9>U9;JZyPF?I z=tQxQ7=NNt1*HykphN|XmX2t#QgslgsElHZg|VrF3YJzzu`)Gf`<=V{lFgC;W9!t; zyc^Cr_nv$1J@Hyx54Q)6fLbZG<$4HMqJ#)v{2W7Ui{Y4$FAMd zrBd>|U0si&{CW11LVB}z3hB$Y6!>hPFFkH-YvVgchmSM?ERIvAxzFW9TO=XqU1=VG_Qbti} zsjAbYhJc2EhJc2EhJc2EhJc2EhJc2EhJc2^w~xTMF1>y7GYR&0w+Gq03HH<{6YM+J>+N6uQDg|=tSEAi|$XA|7uz&G%ti9?hy}b!?W$$?V+55-ae+a+EhZF2~&P=qYCF|{1 z!XM9eJVjI7OyW=JGzBs+XKo^Mv*4wIrwBep@G8Nh1#b}CAb6eNwSt!jE(u;Fc&y-h z!8-)U=T~^SOK^+eX@bjwcM85*@Gij<1y2^-BzU^u*@D{yZxP%jxI=J1IMH&)hCE7g zYy_(~`AL9ZyHG7i0`W&^Gsw98haZ^d#gzA--8Sy?7v{bV6-9|R6r3ZfbT+rRltPc& z@AbGwDGQ?z+Y*w&_ZUQ}3C+28U-wvP_nNn6`TzEZkN6{eKWHC_^AG6w<%86U>`*Fb z_4_^UD!go1;pu?(g@MNpf4wgC#-Hz4u6TkA0^n# zulk#IEh%~U5mWYqtLV<5&cZU|;E?@Aj_GvB=B_I!iC+{{7v#)7oqA1JOQN5QC{3i8 zp_ey!7xX_W~|>0c;6B&QPMB@%0pg5b#wz2QXb?o@{vMb@H)Cx

T~ekr+8DngBu+K{V5 z-b0PlidqGAe$k?u3Q$uCn`UYu7itP&#kF2Y7iFOB3e;%nyNm#@Lv?)VSRb)iH(0WK zBv1;r#eE!)mCS#~LhDRzYq*?}c>Sw;fcN^5X~DiqAXPh- ziLmAU5DUqk#`WRlWhh1N^N1Bs_qENazw-BM%}*`P$X-)7^JwJ0jokAWJ10IY-%zP`OX$7wLZ?gd8RmA#xj(2Y zb2fTCK9AjRsCN2Vtu8~Qzt!gS)KwSEm%(x~mX;Ra>>%2Rx$+e8Hke{SeJCFX9m)*D z=DjZxCtF62XPHm&s;BJd5o0a%G&ft_HXbg;hQ0jEK)H$>WW{c5!d^9y9Xrws*@%7V z1aAU&(@JP~N3;E3BKoc_xFJ`4M_OOsw!*s5t0+rX+{(U0xx}mZT3!A^htk*-u1CI7 z@r(0LJ&RV)7+zDnZumUnev*4$Vkm}^B{{e*%pt7Qa&1Uo110M#Vrabvt#B`#j{UHC zQ;Z>`H)GgEs7yy$7uH`g?hz@lGllf4QRY5)oV*7LaL$*2%0X)1-=@6lf8_VSJ*OR` z%)=kT_CN7;hbv8Ahz?GQJ2P(42aOaJd2mD?G?5$gQU>q{jILwDI2w7b&7H2p-7`n+ zW8Y*fzIY5oCqorAo`R=PlzxuxG_@yQec7aGRJE!_F|<3Ze#0tHtHI@LQVb5os}Q~) znidF*<0lhcGz}dIdM>z}jZKC!h4k-@Z;75ZVa7Ntzd)3p_oM1sJ)_k!%Z^aJA5x|8 zdVaRj;{dO9^x^I?{4i-bdFPjb!1-G5sM-}nTFc2gOa2#X9)$$o30%VzG74VY&9yl*;Ow zZspAD#b5=&85&aU>URpNtKsLB5qHvdQ^r;w*fo z0ev5o2{MCfK@LzGs0;KcXe;PB(0kJ`1$q>u;JC#H zsm_%k2gnKPgZ??tUeJEfArPmL-^Eo@ec_@;kB{hAx_Yba)_Qx3-|EJr&vtLiHkl`pBk!Dg#a?9{K0314!$X==Ddh2m1IKEP5V`n%2Rrzzp}H?=C> zRg2ty#cQ`VDs*4aS8+$9(&8_{-QA^liJn#a$DE0t88KEsLJtL7EwlvNIXxKlGWO^4 z;6gk(Y+v7;m5JHz8rER8tlAl&A$odPQ}3k;?oG^YH}o!7bE^xV8KD!`sETt+2Ojo! zhDI{{v_M1OiA#pz@3`@(NYN-KgXV(H%^-RQbON*&Q~+HC$OdWyaZA33kJ$Sep2iL; za=8kv?qzP5$7(D0v~&4J#b4;M`h2+BN~51O70ODd4;WV}-ZlUV%`&%lq>g=PNkc$G zKtn)7Ktn)7Ktn)7Ktn)7Ktn)7Ktn)7Ktn)7Ktn)7Ktn)7Ktn)7Ktn)7Ktn)7Ktn)7 zKtn)7;F}_#{$_q0zTw}hqt1j1int>L!V@7#3RS~#1sofIa3qc) zKTpq5)%vMakJ#-B#B49ZUlB?Lh#0BD-%=_EFGh4n z6?97>Cm?q-&@ZM^*cQ-aSYN!A7$(My@D#+-Ai4*<7#=IpmVIy^<+KoaPA};{I~R-& z34tK4sHhT*r_F01JOKivYQ(vE(T`Kapf5sPD@VI>N+WJng<6h;^`YL1=yWG)5gvmS zqwm-6g*zR_#8KYFd{8zpkkCFGi0eeuJ4e8?k5!nFRm9YC|7Ga0 z5&e4L+k;X0p)*km+E|3vhKTw`Xv5o#9Z!$AQ<{jj>iDV=YsInq!}BpBl2;NVd5{3oiEE8DE^2~|>zIP6Gq|feI|(@u z{*c+XZyzLU0^%S4CnU&$@W(2!cCMxg?d|K^kyvsO^8WUOb|Kd%Ob{}VAoOANlRDXM zeZr&`G`>HOaC#57Z*OmT9+CqPCv}D-ap$O*9+C+Bgqz6UwXiNSroF6kVHKm6j5;;` z9>)bV!9Bw<)8R>v!n}}zV+J{E0Cj@64HG9D2xli*phyR?Eo1!}I*f%>%ZE@-2`g_! z*$`GLB=emA0)>u_%Iywx&G6fe2#Li_OXUaWog?BH3+ zJ~;94#!9^24k0>~=MRX)m{eQ#qeBhj&2>>YNjb58&TO4HkA(v4LOvwa8$dE>G#`8} zvXAh@KGJ4KU9yi}G)9;!PRr4KuwVAU{X&Suc{Mdaqvl!im|60gWT_^`OU;MNk}>pb z!($7#X2eZ|+D>o}oSEP)2-7z<83hU>`Zw|QJjW`^IjqNb)M zXnk?6?#GK;o^Qdv&)5EcYj|CcHUr*Mqv^lvx;WbO@`zYitPS&H<$L7K z@>Y4b{F;1FJ}e)T2jt`O3Hg*+Ab!F01>YE3t)Yg1hJc2EhJc2EhJc2EhJc2EhJc2EhJc2^cLafd17EAC Am;e9( literal 0 HcmV?d00001