From ea03a250260996ba81fffa229f88de6f30e01825 Mon Sep 17 00:00:00 2001 From: ThomasGoulet73 Date: Wed, 23 Feb 2022 00:00:16 -0500 Subject: [PATCH] Migrate DWrite Factory to managed. Contributes to dotnet/wpf#5305 --- .../Windows/Interop.HRESULT_FROM_WIN32.cs | 19 ++ .../Windows/Kernel32/Interop.FreeLibrary.cs | 14 + .../Kernel32/Interop.GetModuleHandle.cs | 14 + .../Kernel32/Interop.GetProcAddress.cs | 14 + .../Windows/Kernel32/Interop.LoadLibrary.cs | 14 + .../Windows/Kernel32/Interop.LoadLibraryEx.cs | 16 + .../CPP/DWriteWrapper/Factory.cpp | 286 +----------------- .../CPP/DWriteWrapper/Factory.h | 161 +--------- .../CPP/DWriteWrapper/Font.cpp | 2 +- .../CPP/DWriteWrapper/FontFileEnumerator.cpp | 2 +- .../CPP/DWriteWrapper/TextAnalyzer.cpp | 7 +- .../CPP/DWriteWrapper/TextAnalyzer.h | 8 +- .../src/DirectWriteForwarder/main.cpp | 96 +----- .../Interop/DWrite/DWRITE_FONT_FACE_TYPE.cs | 15 + .../Interop/DWrite/DWRITE_FONT_SIMULATIONS.cs | 9 + .../internal/Interop/DWrite/IDWriteFactory.cs | 102 +++++++ .../Interop/DWrite/IDWriteFontCollection.cs | 39 +++ .../DWrite/IDWriteFontCollectionLoader.cs | 39 +++ .../Interop/DWrite/IDWriteFontFace.cs | 39 +++ .../Interop/DWrite/IDWriteFontFile.cs | 39 +++ .../Interop/DWrite/IDWriteFontFileLoader.cs | 39 +++ .../Interop/DWrite/IDWriteTextAnalyzer.cs | 39 +++ .../MS/internal/Interop/IUnknown.cs | 13 + .../Interop/NativePointerCriticalHandle.cs | 37 +++ .../MS/internal/Shaping/TypefaceMap.cs | 2 +- .../Text/TextInterface/DWriteLoader.cs | 95 ++++++ .../internal/Text/TextInterface/DWriteUtil.cs | 60 ++++ .../MS/internal/Text/TextInterface/Factory.cs | 247 +++++++++++++++ .../src/PresentationCore/ModuleInitializer.cs | 8 + .../PresentationCore/PresentationCore.csproj | 28 ++ 30 files changed, 954 insertions(+), 549 deletions(-) create mode 100644 src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Interop.HRESULT_FROM_WIN32.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.FreeLibrary.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.GetModuleHandle.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.GetProcAddress.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.LoadLibrary.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.LoadLibraryEx.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/DWRITE_FONT_FACE_TYPE.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/DWRITE_FONT_SIMULATIONS.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFactory.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontCollection.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontCollectionLoader.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontFace.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontFile.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontFileLoader.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteTextAnalyzer.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/IUnknown.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/NativePointerCriticalHandle.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Text/TextInterface/DWriteLoader.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Text/TextInterface/DWriteUtil.cs create mode 100644 src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Text/TextInterface/Factory.cs diff --git a/src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Interop.HRESULT_FROM_WIN32.cs b/src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Interop.HRESULT_FROM_WIN32.cs new file mode 100644 index 00000000000..2d225456e80 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Interop.HRESULT_FROM_WIN32.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +internal static partial class Interop +{ + // Implementation of HRESULT_FROM_WIN32 macro + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int HRESULT_FROM_WIN32(int errorCode) + { + if (errorCode <= 0 || (errorCode & 0x80000000) == 0x80000000) + { + return errorCode; + } + + return (errorCode & 0x0000FFFF) | unchecked((int)0x80070000); + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.FreeLibrary.cs b/src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.FreeLibrary.cs new file mode 100644 index 00000000000..b98655a9132 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.FreeLibrary.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Kernel32 + { + [DllImport(Libraries.Kernel32, SetLastError = true, ExactSpelling = true)] + internal static extern int FreeLibrary(IntPtr hModule); + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.GetModuleHandle.cs b/src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.GetModuleHandle.cs new file mode 100644 index 00000000000..a963c3962d9 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.GetModuleHandle.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Kernel32 + { + [DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)] + internal static extern IntPtr GetModuleHandleW(string moduleName); + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.GetProcAddress.cs b/src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.GetProcAddress.cs new file mode 100644 index 00000000000..cc52de7f977 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.GetProcAddress.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Kernel32 + { + [DllImport(Libraries.Kernel32, CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)] + internal static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.LoadLibrary.cs b/src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.LoadLibrary.cs new file mode 100644 index 00000000000..9cf0ac772c0 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.LoadLibrary.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Kernel32 + { + [DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)] + internal static extern IntPtr LoadLibraryW(string libFilename); + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.LoadLibraryEx.cs b/src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.LoadLibraryEx.cs new file mode 100644 index 00000000000..3f47dbc8c3d --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.LoadLibraryEx.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. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Kernel32 + { + public const int LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800; + + [DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)] + internal static extern IntPtr LoadLibraryExW(string lpwLibFileName, IntPtr hFile, uint dwFlags); + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/Factory.cpp b/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/Factory.cpp index f96c34564fa..03b56e99336 100644 --- a/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/Factory.cpp +++ b/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/Factory.cpp @@ -18,281 +18,14 @@ extern void *GetDWriteCreateFactoryFunctionPointer(); namespace MS { namespace Internal { namespace Text { namespace TextInterface { - Factory^ Factory::Create( - FactoryType factoryType, - IFontSourceCollectionFactory^ fontSourceCollectionFactory, - IFontSourceFactory^ fontSourceFactory - ) - { - return gcnew Factory(factoryType, fontSourceCollectionFactory, fontSourceFactory); - } - - Factory::Factory( - FactoryType factoryType, - IFontSourceCollectionFactory^ fontSourceCollectionFactory, - IFontSourceFactory^ fontSourceFactory - ) : CriticalHandle(IntPtr::Zero) - { - Initialize(factoryType); - _wpfFontFileLoader = gcnew FontFileLoader(fontSourceFactory); - _wpfFontCollectionLoader = gcnew FontCollectionLoader( - fontSourceCollectionFactory, - _wpfFontFileLoader - ); - - _fontSourceFactory = fontSourceFactory; - - IntPtr pIDWriteFontFileLoaderMirror = Marshal::GetComInterfaceForObject( - _wpfFontFileLoader, - IDWriteFontFileLoaderMirror::typeid); - - // Future improvement note: - // This seems a bit hacky, but unclear at this time how to implement this any better. - // When we attempt to unregister these, do we need to keep around the same IntPtr - // representing the result of GetComInterfaceForObject to free it ? Or will it - // be the same if we call it again? - - - - HRESULT hr = _pFactory->RegisterFontFileLoader( - (IDWriteFontFileLoader *)pIDWriteFontFileLoaderMirror.ToPointer() - ); - - Marshal::Release(pIDWriteFontFileLoaderMirror); - - ConvertHresultToException(hr, "Factory::Factory"); - - IntPtr pIDWriteFontCollectionLoaderMirror = Marshal::GetComInterfaceForObject( - _wpfFontCollectionLoader, - IDWriteFontCollectionLoaderMirror::typeid); - hr = _pFactory->RegisterFontCollectionLoader( - (IDWriteFontCollectionLoader *)pIDWriteFontCollectionLoaderMirror.ToPointer() - ); - - Marshal::Release(pIDWriteFontCollectionLoaderMirror); - - ConvertHresultToException(hr, "Factory::Factory"); - } - - __declspec(noinline) void Factory::Initialize( - FactoryType factoryType - ) - { - IUnknown* factoryTemp; - DWRITECREATEFACTORY pfnDWriteCreateFactory = (DWRITECREATEFACTORY)GetDWriteCreateFactoryFunctionPointer(); - - HRESULT hr = (*pfnDWriteCreateFactory)( - DWriteTypeConverter::Convert(factoryType), - __uuidof(IDWriteFactory), - &factoryTemp - ); - - ConvertHresultToException(hr, "Factory::Initialize"); - - _pFactory = (IDWriteFactory*)factoryTemp; - } - - __declspec(noinline) bool Factory::ReleaseHandle() - { - if (_wpfFontCollectionLoader != nullptr) - { - IntPtr pIDWriteFontCollectionLoaderMirror = Marshal::GetComInterfaceForObject( - _wpfFontCollectionLoader, - IDWriteFontCollectionLoaderMirror::typeid); - - _pFactory->UnregisterFontCollectionLoader((IDWriteFontCollectionLoader *)pIDWriteFontCollectionLoaderMirror.ToPointer()); - Marshal::Release(pIDWriteFontCollectionLoaderMirror); - _wpfFontCollectionLoader = nullptr; - } - - if (_wpfFontFileLoader != nullptr) - { - IntPtr pIDWriteFontFileLoaderMirror = Marshal::GetComInterfaceForObject( - _wpfFontFileLoader, - IDWriteFontFileLoaderMirror::typeid); - - _pFactory->UnregisterFontFileLoader((IDWriteFontFileLoader *)pIDWriteFontFileLoaderMirror.ToPointer()); - Marshal::Release(pIDWriteFontFileLoaderMirror); - _wpfFontFileLoader = nullptr; - } - - if (_pFactory != NULL) - { - _pFactory ->Release(); - _pFactory = NULL; - } - - return true; - } - - __declspec(noinline) FontFile^ Factory::CreateFontFile( - System::Uri^ filePathUri - ) - { - IDWriteFontFile* dwriteFontFile = NULL; - HRESULT hr = Factory::CreateFontFile(_pFactory, _wpfFontFileLoader, filePathUri, &dwriteFontFile); - - // If DWrite's CreateFontFileReference fails then try opening the file using WPF's logic. - // The failures that WPF returns are more granular than the HRESULTs that DWrite returns - // thus we use WPF's logic to open the file to throw the same exceptions that - // WPF would have thrown before. - if (FAILED(hr)) - { - IFontSource^ fontSource = _fontSourceFactory->Create(filePathUri->AbsoluteUri); - fontSource->TestFileOpenable(); - - } - - //This call is made to prevent this object from being collected and hence get its finalize method called - //While there are others referencing it. - System::GC::KeepAlive(this); - - ConvertHresultToException(hr, "FontFile^ Factory::CreateFontFile"); - - return gcnew FontFile(dwriteFontFile); - - } - - FontFace^ Factory::CreateFontFace( - System::Uri^ filePathUri, - unsigned int faceIndex - ) - { - return CreateFontFace( - filePathUri, - faceIndex, - FontSimulations::None - ); - } - - FontFace^ Factory::CreateFontFace( - System::Uri^ filePathUri, - unsigned int faceIndex, - FontSimulations fontSimulationFlags - ) - { - FontFile^ fontFile = CreateFontFile(filePathUri); - DWRITE_FONT_FILE_TYPE dwriteFontFileType; - DWRITE_FONT_FACE_TYPE dwriteFontFaceType; - unsigned int numberOfFaces = 0; - - HRESULT hr; - if (fontFile->Analyze( - dwriteFontFileType, - dwriteFontFaceType, - numberOfFaces, - hr - )) - { - if (faceIndex >= numberOfFaces) - { - throw gcnew System::ArgumentOutOfRangeException("faceIndex"); - } - - unsigned char dwriteFontSimulationsFlags = DWriteTypeConverter::Convert(fontSimulationFlags); - IDWriteFontFace* dwriteFontFace = NULL; - IDWriteFontFile* dwriteFontFile = fontFile->DWriteFontFileNoAddRef; - - HRESULT hr = _pFactory->CreateFontFace( - dwriteFontFaceType, - 1, - &dwriteFontFile, - faceIndex, - (DWRITE_FONT_SIMULATIONS) dwriteFontSimulationsFlags, - &dwriteFontFace - ); - System::GC::KeepAlive(fontFile); - System::GC::KeepAlive(this); - - ConvertHresultToException(hr, "FontFace^ Factory::CreateFontFace"); - - return gcnew FontFace(dwriteFontFace); - } - - // This path is here because there is a behavior mismatch between DWrite and WPF. - // If a directory was given instead of a font uri WPF previously throws - // System.UnauthorizedAccessException. We handle most of the exception behavior mismatch - // in FontFile^ Factory::CreateFontFile by opening the file using WPF's previous (prior to DWrite integration) logic if - // CreateFontFileReference fails (please see comments in FontFile^ Factory::CreateFontFile). - // However in this special case DWrite's CreateFontFileReference will succeed if given - // a directory instead of a font file and it is the Analyze() call will fail returning DWRITE_E_FILEFORMAT. - // Thus, incase the hr returned from Analyze() was DWRITE_E_FILEFORMAT we do as we did in FontFile^ Factory::CreateFontFile - // to try and open the file using WPF old logic and throw System.UnauthorizedAccessException as WPF used to do. - // If a file format exception is expected then opening the file should succeed and ConvertHresultToException() - // Should throw the correct exception. - // A final note would be that this overhead is only incurred in error conditions and so the normal execution path should - // not be affected. - else - { - if (hr == DWRITE_E_FILEFORMAT) - { - IFontSource^ fontSource = _fontSourceFactory->Create(filePathUri->AbsoluteUri); - fontSource->TestFileOpenable(); - } - ConvertHresultToException(hr, "FontFace^ Factory::CreateFontFace"); - } - - return nullptr; - } - - FontCollection^ Factory::GetSystemFontCollection() - { - return GetSystemFontCollection(false); - } - - __declspec(noinline) FontCollection^ Factory::GetSystemFontCollection( - bool checkForUpdates - ) - { - IDWriteFontCollection* dwriteFontCollection = NULL; - HRESULT hr = _pFactory->GetSystemFontCollection( - &dwriteFontCollection, - checkForUpdates - ); - System::GC::KeepAlive(this); - - ConvertHresultToException(hr, "FontCollection^ Factory::GetSystemFontCollection"); - - return gcnew FontCollection(dwriteFontCollection); - } - - __declspec(noinline) FontCollection^ Factory::GetFontCollection(System::Uri^ uri) - { - System::String^ uriString = uri->AbsoluteUri; - IDWriteFontCollection* dwriteFontCollection = NULL; - pin_ptr uriPathWChar = PtrToStringChars(uriString); - - IntPtr pIDWriteFontCollectionLoaderMirror = Marshal::GetComInterfaceForObject( - _wpfFontCollectionLoader, - IDWriteFontCollectionLoaderMirror::typeid); - - IDWriteFontCollectionLoader * pIDWriteFontCollectionLoader = - (IDWriteFontCollectionLoader *)pIDWriteFontCollectionLoaderMirror.ToPointer(); - - HRESULT hr = _pFactory->CreateCustomFontCollection( - pIDWriteFontCollectionLoader, - uriPathWChar, - (uriString->Length+1) * sizeof(WCHAR), - &dwriteFontCollection - ); - - Marshal::Release(pIDWriteFontCollectionLoaderMirror); - - System::GC::KeepAlive(this); - - ConvertHresultToException(hr, "FontCollection^ Factory::GetFontCollection"); - - return gcnew FontCollection(dwriteFontCollection); - } - - HRESULT Factory::CreateFontFile( + HRESULT InternalFactory::CreateFontFile( IDWriteFactory* factory, FontFileLoader^ fontFileLoader, System::Uri^ filePathUri, __out IDWriteFontFile** dwriteFontFile ) { - bool isLocal = Factory::IsLocalUri(filePathUri); + bool isLocal = InternalFactory::IsLocalUri(filePathUri); HRESULT hr = E_FAIL; if (isLocal) @@ -402,23 +135,10 @@ namespace MS { namespace Internal { namespace Text { namespace TextInterface return hr; } - __declspec(noinline) void Factory::CleanupTimeStampCache() + __declspec(noinline) void InternalFactory::CleanupTimeStampCache() { _timeStampCacheCleanupOp = nullptr; _timeStampCache->Clear(); } - __declspec(noinline) TextAnalyzer^ Factory::CreateTextAnalyzer() - { - IDWriteTextAnalyzer* textAnalyzer = NULL; - HRESULT hr = _pFactory->CreateTextAnalyzer(&textAnalyzer); - System::GC::KeepAlive(this); - ConvertHresultToException(hr, "TextAnalyzer^ Factory::CreateTextAnalyzer"); - return gcnew TextAnalyzer(textAnalyzer); - } - - bool Factory::IsInvalid::get() - { - return (_pFactory == NULL); - } }}}}//MS::Internal::Text::TextInterface diff --git a/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/Factory.h b/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/Factory.h index 894c1e9278f..e49f9bb81a8 100644 --- a/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/Factory.h +++ b/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/Factory.h @@ -25,159 +25,18 @@ namespace MS { namespace Internal { namespace Text { namespace TextInterface /// /// The root factory interface for all DWrite objects. /// - private ref class Factory sealed : public CriticalHandle + private ref class InternalFactory abstract sealed { private: - - /// - /// A pointer to the wrapped DWrite factory object. - /// - IDWriteFactory* _pFactory; - - /// - /// Constructs a factory object. - /// - /// Identifies whether the factory object will be shared or isolated. - /// A factory object that will create managed FontSourceCollection - /// objects that will be utilized to load embedded fonts. - /// A factory object that will create managed FontSource - /// objects that will be utilized to load embedded fonts. - /// - /// The factory just created. - /// - Factory( - FactoryType factoryType, - IFontSourceCollectionFactory^ fontSourceCollectionFactory, - IFontSourceFactory^ fontSourceFactory - ); - - /// - /// Initializes a factory object. - /// - /// Identifies whether the factory object will be shared or isolated. - void Initialize(FactoryType factoryType); - - /// - /// The custom loader used by WPF to load font collections. - /// - FontCollectionLoader^ _wpfFontCollectionLoader; - - /// - /// The custom loader used by WPF to load font files. - /// - FontFileLoader^ _wpfFontFileLoader; - - IFontSourceFactory^ _fontSourceFactory; - [ThreadStatic] - static Dictionary^ _timeStampCache; - + static Dictionary^ _timeStampCache; + [ThreadStatic] static DispatcherOperation^ _timeStampCacheCleanupOp; - - static void CleanupTimeStampCache(); - - protected: - - virtual bool ReleaseHandle() override; + static void CleanupTimeStampCache(); internal: - property IDWriteFactory* DWriteFactoryAddRef - { - IDWriteFactory* get() - { - _pFactory->AddRef(); - return _pFactory; - } - } - - /// - /// Creates a DirectWrite factory object that is used for subsequent creation of individual DirectWrite objects. - /// - /// Identifies whether the factory object will be shared or isolated. - /// A factory object that will create managed FontSourceCollection - /// objects that will be utilized to load embedded fonts. - /// A factory object that will create managed FontSource - /// objects that will be utilized to load embedded fonts. - /// - /// The factory just created. - /// - static Factory^ Create( - FactoryType factoryType, - IFontSourceCollectionFactory^ fontSourceCollectionFactory, - IFontSourceFactory^ fontSourceFactory - ); - - - /// - /// Creates a font file object from a local font file. - /// - /// file path uri. - /// - /// Newly created font file object, or NULL in case of failure. - /// - FontFile^ CreateFontFile(System::Uri^ filePathUri); - - /// - /// Creates a font face object. - /// - /// The file path of the font face. - /// The zero based index of a font face in cases when the font files contain a collection of font faces. - /// If the font files contain a single face, this value should be zero. - /// Font face simulation flags for algorithmic emboldening and italicization. - /// - /// Newly created font face object, or NULL in case of failure. - /// - FontFace^ CreateFontFace( - System::Uri^ filePathUri, - unsigned int faceIndex, - FontSimulations fontSimulationFlags - ); - - /// - /// Creates a font face object. - /// - /// The file path of the font face. - /// The zero based index of a font face in cases when the font files contain a collection of font faces. - /// If the font files contain a single face, this value should be zero. - /// - /// Newly created font face object, or NULL in case of failure. - /// - FontFace^ CreateFontFace( - System::Uri^ filePathUri, - unsigned int faceIndex - ); - - /// - /// Gets a font collection representing the set of installed fonts. - /// - /// - /// The system font collection. - /// - FontCollection^ GetSystemFontCollection(); - - /// - /// Gets a font collection in a custom location. - /// - /// The uri of the font collection. - /// - /// The font collection. - /// - FontCollection^ GetFontCollection(System::Uri^ uri); - - /// - /// Gets a font collection representing the set of installed fonts. - /// - /// If this parameter is true, the function performs an immediate check for changes to the set of - /// installed fonts. If this parameter is FALSE, the function will still detect changes if the font cache service is running, but - /// there may be some latency. For example, an application might specify TRUE if it has itself just installed a font and wants to - /// be sure the font collection contains that font. - /// - /// The system font collection. - /// - FontCollection^ GetSystemFontCollection(bool checkForUpdates); - static bool IsLocalUri(System::Uri^ uri) { return (uri->IsFile && uri->IsLoopback && !uri->IsUnc); @@ -213,18 +72,6 @@ namespace MS { namespace Internal { namespace Text { namespace TextInterface return transform; } - - TextAnalyzer^ CreateTextAnalyzer(); - - public: - - virtual property bool IsInvalid - { - #pragma warning (disable : 4950) // The Constrained Execution Region (CER) feature is not supported. - [ReliabilityContract(Consistency::WillNotCorruptState, Cer::Success)] - #pragma warning (default : 4950) // The Constrained Execution Region (CER) feature is not supported. - bool get() override; - } }; }}}}//MS::Internal::Text::TextInterface diff --git a/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/Font.cpp b/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/Font.cpp index 3d8008f5715..d7cc67b2aa4 100644 --- a/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/Font.cpp +++ b/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/Font.cpp @@ -306,7 +306,7 @@ namespace MS { namespace Internal { namespace Text { namespace TextInterface IDWriteFontFace* fontFace = NULL; HRESULT hr = _font->Value->CreateFontFace(&fontFace); ConvertHresultToException(hr, "FontMetrics^ Font::DisplayMetrics"); - DWRITE_MATRIX transform = Factory::GetIdentityTransform(); + DWRITE_MATRIX transform = InternalFactory::GetIdentityTransform(); hr = fontFace->GetGdiCompatibleMetrics( emSize, pixelsPerDip, diff --git a/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/FontFileEnumerator.cpp b/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/FontFileEnumerator.cpp index 896c016a9e8..9837aca7b9c 100644 --- a/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/FontFileEnumerator.cpp +++ b/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/FontFileEnumerator.cpp @@ -46,7 +46,7 @@ namespace MS { namespace Internal { namespace Text { namespace TextInterface return E_INVALIDARG; } - return Factory::CreateFontFile( + return InternalFactory::CreateFontFile( _factory, _fontFileLoader, _fontSourceCollectionEnumerator->Current->Uri, diff --git a/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/TextAnalyzer.cpp b/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/TextAnalyzer.cpp index c06c4bc8ddf..604366e1ea8 100644 --- a/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/TextAnalyzer.cpp +++ b/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/TextAnalyzer.cpp @@ -20,7 +20,7 @@ namespace MS { namespace Internal { namespace Text { namespace TextInterface __in_ecount(length) const WCHAR* text, UINT32 length, CultureInfo^ culture, - Factory^ factory, + IDWriteFactory* pDWriteFactory, bool isRightToLeftParagraph, CultureInfo^ numberCulture, bool ignoreUserOverride, @@ -39,9 +39,6 @@ namespace MS { namespace Internal { namespace Text { namespace TextInterface IDWriteTextAnalysisSink* pTextAnalysisSink = NULL; IDWriteTextAnalysisSource* pTextAnalysisSource = NULL; - // We obtain an AddRef factory so as not to worry about having to call GC::KeepAlive(factory) - // which puts unnecessary maintenance cost on this code. - IDWriteFactory* pDWriteFactory = factory->DWriteFactoryAddRef; HRESULT hr = S_OK; try { @@ -607,7 +604,7 @@ namespace MS { namespace Internal { namespace Text { namespace TextInterface { String^ localeName = cultureInfo->IetfLanguageTag; pin_ptr pLocaleName = Native::Util::GetPtrToStringChars(localeName); - DWRITE_MATRIX transform = Factory::GetIdentityTransform(); + DWRITE_MATRIX transform = InternalFactory::GetIdentityTransform(); if (features != nullptr) { diff --git a/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/TextAnalyzer.h b/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/TextAnalyzer.h index 7e79bf93aca..437ad6ab4ba 100644 --- a/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/TextAnalyzer.h +++ b/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/CPP/DWriteWrapper/TextAnalyzer.h @@ -32,12 +32,6 @@ using namespace MS::Internal::Text::TextInterface::Generics; namespace MS { namespace Internal { namespace Text { namespace TextInterface { - /*******************************************************************************************************************************/ - //Forward declaration of Factory since there was a circular reference between "TextAnalyzer" & "Factory" - ref class Factory; - /*******************************************************************************************************************************/ - - // The 4 delegates below are used to introduce a level of indirection so we can define // the external methods that reference PresentationNative*.dll in PresenationCore.dll. // The reason we define the methods in PresentationCore.dll is that the string values for the @@ -157,7 +151,7 @@ namespace MS { namespace Internal { namespace Text { namespace TextInterface __in_ecount(length) const WCHAR* text, UINT32 length, CultureInfo^ culture, - Factory^ factory, + IDWriteFactory* pDWriteFactory, bool isRightToLeftParagraph, CultureInfo^ numberCulture, bool ignoreUserOverride, diff --git a/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/main.cpp b/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/main.cpp index 06ba7f1c1b5..3f204965ed1 100644 --- a/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/main.cpp +++ b/src/Microsoft.DotNet.Wpf/src/DirectWriteForwarder/main.cpp @@ -50,49 +50,7 @@ private ref class NativeWPFDLLLoader sealed // static void LoadDwrite( ) { - // We load dwrite here because it's cleanup logic is different from the other native dlls - // and don't want to abstract that - VOID *pTemp = NULL; - m_hDWrite = System::IntPtr(WPFUtils::LoadDWriteLibraryAndGetProcAddress(&pTemp)); - if (m_hDWrite == IntPtr::Zero) - throw gcnew DllNotFoundException(gcnew String(L"dwrite.dll"), gcnew Win32Exception()); - if (pTemp == NULL) - throw gcnew InvalidOperationException(); - m_pfnDWriteCreateFactory = pTemp; } - - __declspec(noinline) - static void UnloadDWrite() - { - ClearDWriteCreateFactoryFunctionPointer(); - - if (m_hDWrite != IntPtr::Zero) - { - if (!FreeLibrary((HMODULE)(m_hDWrite.ToPointer()))) - { - DWORD lastError = GetLastError(); - Marshal::ThrowExceptionForHR(__HRESULT_FROM_WIN32(lastError)); - } - - m_hDWrite = IntPtr::Zero; - } - } - - static void *GetDWriteCreateFactoryFunctionPointer() - { - return m_pfnDWriteCreateFactory; - } - - static void ClearDWriteCreateFactoryFunctionPointer() - { - m_pfnDWriteCreateFactory = NULL; - } - -private: - - static System::IntPtr m_hDWrite; - - static void *m_pfnDWriteCreateFactory; }; }} // namespace MS.Internal @@ -101,49 +59,14 @@ private class CModuleInitialize public: // Constructor of class CModuleInitialize - __declspec(noinline) CModuleInitialize(void (*cleaningUpFunc)()) + __declspec(noinline) CModuleInitialize() { - MS::Internal::NativeWPFDLLLoader::LoadDwrite(); - // Initialize some global arrays. MS::Internal::TtfDelta::GlobalInit::Init(); MS::Internal::TtfDelta::ControlTableInit::Init(); - atexit(cleaningUpFunc); - } - - // Previously we had this as a class dtor but we found out that - // we can't use a destructor due to an issue with how it's registered to be called on exit: - // A compiler-generated function calls _atexit_m_appdomain(). But that generated function is transparenct, - // which causes a violation because _atexit_m_appdomain() is Critical. - __declspec(noinline) void UnInitialize() - { - MS::Internal::NativeWPFDLLLoader::UnloadDWrite(); - - MS::Internal::NativeWPFDLLLoader::ClearDWriteCreateFactoryFunctionPointer(); - // - // Finalizers run after this dtor so if we unload dwrite now - // we may end up making calls into unloaded code. Yes, this - // is a "leak" but it's only really a leak if no more WPF - // AppDomains are present and it's a single leak since only - // one instance of a version of a CLR may be in proc at - // once. - // - // We could also use a critical finalizer for the handle - // but that requires changing this code quite a bit plus - // if other critical finalizers ever call dwrite code - // we have the same problem again. - // - // MS::Internal::NativeWPFDLLLoader::UnloadDWrite(); - } - - void *GetDWriteCreateFactoryFunctionPointer() - { - return MS::Internal::NativeWPFDLLLoader::GetDWriteCreateFactoryFunctionPointer(); } }; -void CleanUp(); - /// /// This method is a workaround to bug in the compiler. /// The compiler generates a static unsafe method to initialize cmiStartupRunner @@ -152,7 +75,7 @@ void CleanUp(); /// __declspec(noinline) static System::IntPtr CreateCModuleInitialize() { - return System::IntPtr(new CModuleInitialize(CleanUp)); + return System::IntPtr(new CModuleInitialize()); } // Important Note: This variable is declared as System::IntPtr to fool the compiler into creating @@ -160,18 +83,3 @@ __declspec(noinline) static System::IntPtr CreateCModuleInitialize() // Then the generated method is unsafe, fails NGENing and causes Jitting. __declspec(appdomain) static System::IntPtr cmiStartupRunner = CreateCModuleInitialize(); -void CleanUp() -{ - CModuleInitialize* pCmiStartupRunner = static_cast(cmiStartupRunner.ToPointer()); - - pCmiStartupRunner->UnInitialize(); - delete pCmiStartupRunner; - cmiStartupRunner = System::IntPtr(NULL); - -} - -void *GetDWriteCreateFactoryFunctionPointer() -{ - return (static_cast(cmiStartupRunner.ToPointer()))->GetDWriteCreateFactoryFunctionPointer(); -} - diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/DWRITE_FONT_FACE_TYPE.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/DWRITE_FONT_FACE_TYPE.cs new file mode 100644 index 00000000000..614aaa248c1 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/DWRITE_FONT_FACE_TYPE.cs @@ -0,0 +1,15 @@ +namespace MS.Internal.Interop.DWrite +{ + internal enum DWRITE_FONT_FACE_TYPE + { + DWRITE_FONT_FACE_TYPE_CFF, + DWRITE_FONT_FACE_TYPE_TRUETYPE, + DWRITE_FONT_FACE_TYPE_OPENTYPE_COLLECTION, + DWRITE_FONT_FACE_TYPE_TYPE1, + DWRITE_FONT_FACE_TYPE_VECTOR, + DWRITE_FONT_FACE_TYPE_BITMAP, + DWRITE_FONT_FACE_TYPE_UNKNOWN, + DWRITE_FONT_FACE_TYPE_RAW_CFF, + DWRITE_FONT_FACE_TYPE_TRUETYPE_COLLECTION = DWRITE_FONT_FACE_TYPE_OPENTYPE_COLLECTION, + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/DWRITE_FONT_SIMULATIONS.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/DWRITE_FONT_SIMULATIONS.cs new file mode 100644 index 00000000000..1e71901bff9 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/DWRITE_FONT_SIMULATIONS.cs @@ -0,0 +1,9 @@ +namespace MS.Internal.Interop.DWrite +{ + internal enum DWRITE_FONT_SIMULATIONS + { + DWRITE_FONT_SIMULATIONS_NONE = 0x0000, + DWRITE_FONT_SIMULATIONS_BOLD = 0x0001, + DWRITE_FONT_SIMULATIONS_OBLIQUE = 0x0002, + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFactory.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFactory.cs new file mode 100644 index 00000000000..67c868b1a3f --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFactory.cs @@ -0,0 +1,102 @@ +using System; +using System.Runtime.CompilerServices; + +namespace MS.Internal.Interop.DWrite +{ + internal unsafe struct IDWriteFactory : IUnknown + { + private readonly void** Vtbl; + + public int QueryInterface(Guid* guid, void** comObject) + { + var function = (delegate* unmanaged)Vtbl[0]; + + fixed (IDWriteFactory* handle = &this) + { + return function(handle, guid, comObject); + } + } + + public uint AddReference() + { + var function = (delegate* unmanaged)Vtbl[1]; + + fixed (IDWriteFactory* handle = &this) + { + return function(handle); + } + } + + public uint Release() + { + var function = (delegate* unmanaged)Vtbl[2]; + + fixed (IDWriteFactory* handle = &this) + { + return function(handle); + } + } + + internal int GetSystemFontCollection(void* fontCollection, int checkForUpdate) + { + var function = (delegate* unmanaged)Vtbl[3]; + + fixed (IDWriteFactory* handle = &this) + { + return function(handle, fontCollection, checkForUpdate); + } + } + + internal int CreateCustomFontCollection(IDWriteFontCollectionLoader* collectionLoader, void* collectionKey, uint collectionKeySize, IDWriteFontCollection** fontCollection) + { + var function = (delegate* unmanaged)Vtbl[4]; + + fixed (IDWriteFactory* handle = &this) + { + return function(handle, collectionLoader, collectionKey, collectionKeySize, fontCollection); + } + } + + public int RegisterFontCollectionLoader(IDWriteFontCollectionLoader* fontCollectionLoader) + { + var function = (delegate* unmanaged)Vtbl[5]; + + fixed (IDWriteFactory* handle = &this) + { + return function(handle, fontCollectionLoader); + } + } + + internal int CreateFontFace(DWRITE_FONT_FACE_TYPE fontFaceType, uint numberOfFiles, IDWriteFontFile** fontFiles, uint faceIndex, DWRITE_FONT_SIMULATIONS fontFaceSimulationFlags, IDWriteFontFace** fontFace) + { + var function = (delegate* unmanaged)Vtbl[9]; + + fixed (IDWriteFactory* handle = &this) + { + return function(handle, fontFaceType, numberOfFiles, fontFiles, faceIndex, fontFaceSimulationFlags, fontFace); + } + } + + internal int RegisterFontFileLoader(IDWriteFontFileLoader* fontFileLoader) + { + var function = (delegate* unmanaged)Vtbl[13]; + + fixed (IDWriteFactory* handle = &this) + { + return function(handle, fontFileLoader); + } + } + + internal int CreateTextAnalyzer(IDWriteTextAnalyzer** textAnalyzer) + { + var function = (delegate* unmanaged)Vtbl[21]; + + fixed (IDWriteFactory* handle = &this) + { + int hr = function(handle, textAnalyzer); + + return hr; + } + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontCollection.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontCollection.cs new file mode 100644 index 00000000000..d35ce0f15e9 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontCollection.cs @@ -0,0 +1,39 @@ +using System; + +namespace MS.Internal.Interop.DWrite +{ + internal unsafe struct IDWriteFontCollection : IUnknown + { + private readonly void** Vtbl; + + public int QueryInterface(Guid* guid, void** comObject) + { + var function = (delegate* unmanaged)Vtbl[0]; + + fixed (IDWriteFontCollection* handle = &this) + { + return function(handle, guid, comObject); + } + } + + public uint AddReference() + { + var function = (delegate* unmanaged)Vtbl[1]; + + fixed (IDWriteFontCollection* handle = &this) + { + return function(handle); + } + } + + public uint Release() + { + var function = (delegate* unmanaged)Vtbl[2]; + + fixed (IDWriteFontCollection* handle = &this) + { + return function(handle); + } + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontCollectionLoader.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontCollectionLoader.cs new file mode 100644 index 00000000000..7b25ea6e161 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontCollectionLoader.cs @@ -0,0 +1,39 @@ +using System; + +namespace MS.Internal.Interop.DWrite +{ + internal unsafe struct IDWriteFontCollectionLoader : IUnknown + { + private readonly void** Vtbl; + + public int QueryInterface(Guid* guid, void** comObject) + { + var function = (delegate* unmanaged)Vtbl[0]; + + fixed (IDWriteFontCollectionLoader* handle = &this) + { + return function(handle, guid, comObject); + } + } + + public uint AddReference() + { + var function = (delegate* unmanaged)Vtbl[1]; + + fixed (IDWriteFontCollectionLoader* handle = &this) + { + return function(handle); + } + } + + public uint Release() + { + var function = (delegate* unmanaged)Vtbl[2]; + + fixed (IDWriteFontCollectionLoader* handle = &this) + { + return function(handle); + } + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontFace.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontFace.cs new file mode 100644 index 00000000000..22d91e6da2b --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontFace.cs @@ -0,0 +1,39 @@ +using System; + +namespace MS.Internal.Interop.DWrite +{ + internal unsafe struct IDWriteFontFace : IUnknown + { + private readonly void** Vtbl; + + public int QueryInterface(Guid* guid, void** comObject) + { + var function = (delegate* unmanaged)Vtbl[0]; + + fixed (IDWriteFontFace* handle = &this) + { + return function(handle, guid, comObject); + } + } + + public uint AddReference() + { + var function = (delegate* unmanaged)Vtbl[1]; + + fixed (IDWriteFontFace* handle = &this) + { + return function(handle); + } + } + + public uint Release() + { + var function = (delegate* unmanaged)Vtbl[2]; + + fixed (IDWriteFontFace* handle = &this) + { + return function(handle); + } + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontFile.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontFile.cs new file mode 100644 index 00000000000..4f74e51a2b2 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontFile.cs @@ -0,0 +1,39 @@ +using System; + +namespace MS.Internal.Interop.DWrite +{ + internal unsafe struct IDWriteFontFile : IUnknown + { + private readonly void** Vtbl; + + public int QueryInterface(Guid* guid, void** comObject) + { + var function = (delegate* unmanaged)Vtbl[0]; + + fixed (IDWriteFontFile* handle = &this) + { + return function(handle, guid, comObject); + } + } + + public uint AddReference() + { + var function = (delegate* unmanaged)Vtbl[1]; + + fixed (IDWriteFontFile* handle = &this) + { + return function(handle); + } + } + + public uint Release() + { + var function = (delegate* unmanaged)Vtbl[2]; + + fixed (IDWriteFontFile* handle = &this) + { + return function(handle); + } + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontFileLoader.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontFileLoader.cs new file mode 100644 index 00000000000..fa7fa0ecbf6 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteFontFileLoader.cs @@ -0,0 +1,39 @@ +using System; + +namespace MS.Internal.Interop.DWrite +{ + internal unsafe struct IDWriteFontFileLoader : IUnknown + { + private readonly void** Vtbl; + + public int QueryInterface(Guid* guid, void** comObject) + { + var function = (delegate* unmanaged)Vtbl[0]; + + fixed (IDWriteFontFileLoader* handle = &this) + { + return function(handle, guid, comObject); + } + } + + public uint AddReference() + { + var function = (delegate* unmanaged)Vtbl[1]; + + fixed (IDWriteFontFileLoader* handle = &this) + { + return function(handle); + } + } + + public uint Release() + { + var function = (delegate* unmanaged)Vtbl[2]; + + fixed (IDWriteFontFileLoader* handle = &this) + { + return function(handle); + } + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteTextAnalyzer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteTextAnalyzer.cs new file mode 100644 index 00000000000..37fb5b50f92 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/DWrite/IDWriteTextAnalyzer.cs @@ -0,0 +1,39 @@ +using System; + +namespace MS.Internal.Interop.DWrite +{ + internal unsafe struct IDWriteTextAnalyzer : IUnknown + { + private readonly void** Vtbl; + + public int QueryInterface(Guid* guid, void** comObject) + { + var function = (delegate* unmanaged)Vtbl[0]; + + fixed (IDWriteTextAnalyzer* handle = &this) + { + return function(handle, guid, comObject); + } + } + + public uint AddReference() + { + var function = (delegate* unmanaged)Vtbl[1]; + + fixed (IDWriteTextAnalyzer* handle = &this) + { + return function(handle); + } + } + + public uint Release() + { + var function = (delegate* unmanaged)Vtbl[2]; + + fixed (IDWriteTextAnalyzer* handle = &this) + { + return function(handle); + } + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/IUnknown.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/IUnknown.cs new file mode 100644 index 00000000000..d70582942f3 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/IUnknown.cs @@ -0,0 +1,13 @@ +using System; + +namespace MS.Internal.Interop +{ + internal unsafe interface IUnknown + { + int QueryInterface(Guid* guid, void** comObject); + + uint AddReference(); + + uint Release(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/NativePointerCriticalHandle.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/NativePointerCriticalHandle.cs new file mode 100644 index 00000000000..b98ee1fb8bf --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Interop/NativePointerCriticalHandle.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.InteropServices; + +namespace MS.Internal.Interop +{ + internal abstract unsafe class NativePointerCriticalHandle : CriticalHandle + where TClass : unmanaged + { + public NativePointerCriticalHandle(IntPtr nativePointer) + : base(IntPtr.Zero) + { + SetHandle(nativePointer); + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + public TClass* Value => (TClass*)handle; + } + + internal unsafe class NativeIUnknownWrapper : NativePointerCriticalHandle + where TClass : unmanaged, IUnknown + { + public NativeIUnknownWrapper(void* nativePointer) + : base((IntPtr)nativePointer) + { + } + + protected override bool ReleaseHandle() + { + Value->Release(); + + handle = IntPtr.Zero; + + return true; + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Shaping/TypefaceMap.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Shaping/TypefaceMap.cs index 9961ecde59d..e6f661fde37 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Shaping/TypefaceMap.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Shaping/TypefaceMap.cs @@ -120,7 +120,7 @@ TextFormattingMode textFormattingMode (char*)ptext.ToPointer(), (uint)stringLength, culture, - MS.Internal.FontCache.DWriteFactory.Instance, + (MS.Internal.Text.TextInterface.Native.IDWriteFactory*)MS.Internal.FontCache.DWriteFactory.Instance.DWriteFactoryAddRef, isRightToLeftParagraph, digitCulture, ignoreUserOverride, diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Text/TextInterface/DWriteLoader.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Text/TextInterface/DWriteLoader.cs new file mode 100644 index 00000000000..3278f44b07a --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Text/TextInterface/DWriteLoader.cs @@ -0,0 +1,95 @@ +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; + +using static Interop; + +namespace MS.Internal.Text.TextInterface +{ + internal static unsafe class DWriteLoader + { + private static IntPtr _dwrite; + private static delegate* unmanaged _dwriteCreateFactory; + + internal static void LoadDWrite() + { + _dwrite = LoadDWriteLibraryAndGetProcAddress(out delegate* unmanaged dwriteCreateFactory); + + if (_dwrite == IntPtr.Zero) + throw new DllNotFoundException("dwrite.dll", new Win32Exception()); + + if (dwriteCreateFactory == null) + throw new InvalidOperationException(); + + _dwriteCreateFactory = dwriteCreateFactory; + } + + internal static void UnloadDWrite() + { + ClearDWriteCreateFactoryFunctionPointer(); + + if (_dwrite != IntPtr.Zero) + { + if (Kernel32.FreeLibrary(_dwrite) != 0) + { + int lastError = Marshal.GetLastPInvokeError(); + Marshal.ThrowExceptionForHR(HRESULT_FROM_WIN32(lastError)); + } + + _dwrite = IntPtr.Zero; + } + } + + internal static delegate* unmanaged GetDWriteCreateFactoryFunctionPointer() + { + return _dwriteCreateFactory; + } + + private static void ClearDWriteCreateFactoryFunctionPointer() + { + _dwriteCreateFactory = null; + } + + private static IntPtr LoadDWriteLibraryAndGetProcAddress(out delegate* unmanaged DWriteCreateFactory) + { + IntPtr hDWriteLibrary = IntPtr.Zero; + + // KB2533623 introduced the LOAD_LIBRARY_SEARCH_SYSTEM32 flag. It also introduced + // the AddDllDirectory function. We test for presence of AddDllDirectory as an + // indirect evidence for the support of LOAD_LIBRARY_SEARCH_SYSTEM32 flag. + IntPtr hKernel32 = Kernel32.GetModuleHandleW(Libraries.Kernel32); + + if (hKernel32 != IntPtr.Zero) + { + if (Kernel32.GetProcAddress(hKernel32, "AddDllDirectory") != IntPtr.Zero) + { + // All supported platforms newer than Vista SP2 shipped with dwrite.dll. + // On Vista SP2, the .NET servicing process will ensure that a MSU containing + // dwrite.dll will be delivered as a prerequisite - effectively guaranteeing that + // this following call to LoadLibraryEx(dwrite.dll) will succeed, and that it will + // not be susceptible to typical DLL planting vulnerability vectors. + hDWriteLibrary = Kernel32.LoadLibraryExW("dwrite.dll", IntPtr.Zero, Kernel32.LOAD_LIBRARY_SEARCH_SYSTEM32); + } + else + { + // LOAD_LIBRARY_SEARCH_SYSTEM32 is not supported on this OS. + // Fall back to using plain ol' LoadLibrary + // There is risk that this call might fail, or that it might be + // susceptible to DLL hijacking. + hDWriteLibrary = Kernel32.LoadLibraryW("dwrite.dll"); + } + } + + if (hDWriteLibrary != IntPtr.Zero) + { + DWriteCreateFactory = (delegate* unmanaged)Kernel32.GetProcAddress(hDWriteLibrary, "DWriteCreateFactory"); + } + else + { + DWriteCreateFactory = null; + } + + return hDWriteLibrary; + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Text/TextInterface/DWriteUtil.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Text/TextInterface/DWriteUtil.cs new file mode 100644 index 00000000000..a5d4eaf39e5 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Text/TextInterface/DWriteUtil.cs @@ -0,0 +1,60 @@ +namespace MS.Internal.Text.TextInterface +{ + internal static class DWriteUtil + { + private const int COR_E_INVALIDOPERATION = 0x1509; + private const int DWRITE_E_FILENOTFOUND = unchecked((int)0x88985003L); + private const int DWRITE_E_FILEACCESS = unchecked((int)0x88985004L); + private const int DWRITE_E_FILEFORMAT = unchecked((int)0x88985000L); + + internal static void ConvertHresultToException(int hr) + { + + if (hr != 0) + { + if (hr == DWRITE_E_FILENOTFOUND) + { + throw new System.IO.FileNotFoundException(); + } + else if (hr == DWRITE_E_FILEACCESS) + { + throw new System.UnauthorizedAccessException(); + } + else if (hr == DWRITE_E_FILEFORMAT) + { + throw new System.IO.FileFormatException(); + } + else + { + SanitizeAndThrowIfKnownException(hr); + + // ThrowExceptionForHR method returns an exception based on the IErrorInfo of + // the current thread if one is set. When this happens, the errorCode parameter + // is ignored. + // We pass an IntPtr that has a value of -1 so that ThrowExceptionForHR ignores + // IErrorInfo of the current thread. + System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(hr, new System.IntPtr(-1)); + } + } + } + + /// + /// Exceptions known to have security sensitive data are sanitized in this method, + /// by throwing a copy of the original exception without security sensitive data. + /// Or, to put another way - this function acts only on a list of security sensitive HRESULT/IErrorInfo combinations, throwing for matches. + /// The IErrorInfo is taken into account in a call to GetExceptionForHR(HRESULT), see MSDN for more details. + /// + + private static void SanitizeAndThrowIfKnownException(int hr) + { + if (hr == COR_E_INVALIDOPERATION) + { + System.Exception e = System.Runtime.InteropServices.Marshal.GetExceptionForHR(hr); + if (e is System.Net.WebException) + { + throw e; + } + } + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Text/TextInterface/Factory.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Text/TextInterface/Factory.cs new file mode 100644 index 00000000000..ea4725d9e6d --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Text/TextInterface/Factory.cs @@ -0,0 +1,247 @@ +using System; +using System.Runtime.InteropServices; +using MS.Internal.Interop; +using MS.Internal.Interop.DWrite; +using MS.Internal.Text.TextInterface.Interfaces; + +namespace MS.Internal.Text.TextInterface +{ + internal unsafe class Factory + { + private const int DWRITE_E_FILEFORMAT = unchecked((int)0x88985000L); + private NativeIUnknownWrapper _factory; + + private IFontSourceFactory _fontSourceFactory; + private FontFileLoader _wpfFontFileLoader; + private FontCollectionLoader _wpfFontCollectionLoader; + + // b859ee5a-d838-4b5b-a2e8-1adc7d93db48 + private static readonly Guid IID_IDWriteFactory = new Guid(0xb859ee5a, 0xd838, 0x4b5b, 0xa2, 0xe8, 0x1a, 0xdc, 0x7d, 0x93, 0xdb, 0x48); + + private Factory(IDWriteFactory* nativePointer) + { + _factory = new NativeIUnknownWrapper(nativePointer); + } + + internal IDWriteFactory* DWriteFactoryAddRef + { + get + { + _factory.Value->AddReference(); + + return _factory.Value; + } + } + + private Factory(FactoryType factoryType, IFontSourceCollectionFactory fontSourceCollectionFactory, IFontSourceFactory fontSourceFactory) + { + Initialize(factoryType); + + _wpfFontFileLoader = new FontFileLoader(fontSourceFactory); + _wpfFontCollectionLoader = new FontCollectionLoader( + fontSourceCollectionFactory, + _wpfFontFileLoader + ); + + _fontSourceFactory = fontSourceFactory; + + IntPtr pIDWriteFontFileLoaderMirror = Marshal.GetComInterfaceForObject( + _wpfFontFileLoader, + typeof(IDWriteFontFileLoaderMirror)); + + // Future improvement note: + // This seems a bit hacky, but unclear at this time how to implement this any better. + // When we attempt to unregister these, do we need to keep around the same IntPtr + // representing the result of GetComInterfaceForObject to free it ? Or will it + // be the same if we call it again? + + + + int hr = _factory.Value->RegisterFontFileLoader( + (IDWriteFontFileLoader*)pIDWriteFontFileLoaderMirror.ToPointer() + ); + + Marshal.Release(pIDWriteFontFileLoaderMirror); + + DWriteUtil.ConvertHresultToException(hr); + + IntPtr pIDWriteFontCollectionLoaderMirror = Marshal.GetComInterfaceForObject( + _wpfFontCollectionLoader, + typeof(IDWriteFontCollectionLoaderMirror)); + hr = _factory.Value->RegisterFontCollectionLoader( + (IDWriteFontCollectionLoader*)pIDWriteFontCollectionLoaderMirror.ToPointer() + ); + + Marshal.Release(pIDWriteFontCollectionLoaderMirror); + + DWriteUtil.ConvertHresultToException(hr); + } + + private void Initialize(FactoryType factoryType) + { + Guid iid = IID_IDWriteFactory; + IDWriteFactory* factory = null; + + delegate* unmanaged pfnDWriteCreateFactory = DWriteLoader.GetDWriteCreateFactoryFunctionPointer(); + + int hr = pfnDWriteCreateFactory((int)DWriteTypeConverter.Convert(factoryType), &iid, &factory); + + DWriteUtil.ConvertHresultToException(hr); + + _factory = new NativeIUnknownWrapper(factory); + } + + internal static Factory Create( + FactoryType factoryType, + IFontSourceCollectionFactory fontSourceCollectionFactory, + IFontSourceFactory fontSourceFactory) + { + return new Factory(factoryType, fontSourceCollectionFactory, fontSourceFactory); + } + + internal TextAnalyzer CreateTextAnalyzer() + { + IDWriteTextAnalyzer* textAnalyzer = null; + + _factory.Value->CreateTextAnalyzer(&textAnalyzer); + + return new TextAnalyzer((Native.IDWriteTextAnalyzer*)textAnalyzer); + } + + internal FontFile CreateFontFile(Uri filePathUri) + { + Native.IDWriteFontFile* dwriteFontFile = null; + int hr = InternalFactory.CreateFontFile((Native.IDWriteFactory*)_factory.Value, null, filePathUri, &dwriteFontFile); + + // If DWrite's CreateFontFileReference fails then try opening the file using WPF's logic. + // The failures that WPF returns are more granular than the HRESULTs that DWrite returns + // thus we use WPF's logic to open the file to throw the same exceptions that + // WPF would have thrown before. + if (hr != 0) + { + IFontSource fontSource = _fontSourceFactory.Create(filePathUri.AbsoluteUri); + fontSource.TestFileOpenable(); + + } + + //This call is made to prevent this object from being collected and hence get its finalize method called + //While there are others referencing it. + GC.KeepAlive(this); + + DWriteUtil.ConvertHresultToException(hr); + + return new FontFile(dwriteFontFile); + } + + internal FontFace CreateFontFace(Uri filePathUri, uint faceIndex) + { + return CreateFontFace( + filePathUri, + faceIndex, + FontSimulations.None + ); + } + + internal FontFace CreateFontFace(Uri filePathUri, uint faceIndex, FontSimulations fontSimulationFlags) + { + FontFile fontFile = CreateFontFile(filePathUri); + Native.DWRITE_FONT_FILE_TYPE dwriteFontFileType; + Native.DWRITE_FONT_FACE_TYPE dwriteFontFaceType; + uint numberOfFaces = 0; + + int hr; + if (fontFile.Analyze( + out dwriteFontFileType, + out dwriteFontFaceType, + out numberOfFaces, + &hr + )) + { + if (faceIndex >= numberOfFaces) + { + throw new ArgumentOutOfRangeException("faceIndex"); + } + + byte dwriteFontSimulationsFlags = DWriteTypeConverter.Convert(fontSimulationFlags); + IDWriteFontFace* dwriteFontFace = null; + IDWriteFontFile* dwriteFontFile = (IDWriteFontFile*)fontFile.DWriteFontFileNoAddRef; + + hr = _factory.Value->CreateFontFace( + (DWRITE_FONT_FACE_TYPE)dwriteFontFaceType, + 1, + &dwriteFontFile, + faceIndex, + (DWRITE_FONT_SIMULATIONS)dwriteFontSimulationsFlags, + &dwriteFontFace + ); + GC.KeepAlive(fontFile); + GC.KeepAlive(this); + + DWriteUtil.ConvertHresultToException(hr); + + return new FontFace((Native.IDWriteFontFace*)dwriteFontFace); + } + + // This path is here because there is a behavior mismatch between DWrite and WPF. + // If a directory was given instead of a font uri WPF previously throws + // System.UnauthorizedAccessException. We handle most of the exception behavior mismatch + // in FontFile^ Factory::CreateFontFile by opening the file using WPF's previous (prior to DWrite integration) logic if + // CreateFontFileReference fails (please see comments in FontFile^ Factory::CreateFontFile). + // However in this special case DWrite's CreateFontFileReference will succeed if given + // a directory instead of a font file and it is the Analyze() call will fail returning DWRITE_E_FILEFORMAT. + // Thus, incase the hr returned from Analyze() was DWRITE_E_FILEFORMAT we do as we did in FontFile^ Factory::CreateFontFile + // to try and open the file using WPF old logic and throw System.UnauthorizedAccessException as WPF used to do. + // If a file format exception is expected then opening the file should succeed and ConvertHresultToException() + // Should throw the correct exception. + // A final note would be that this overhead is only incurred in error conditions and so the normal execution path should + // not be affected. + else + { + if (hr == DWRITE_E_FILEFORMAT) + { + IFontSource fontSource = _fontSourceFactory.Create(filePathUri.AbsoluteUri); + fontSource.TestFileOpenable(); + } + DWriteUtil.ConvertHresultToException(hr); + } + + return null; + } + + internal FontCollection GetSystemFontCollection() + { + return GetSystemFontCollection(false); + } + + private FontCollection GetSystemFontCollection(bool checkForUpdate) + { + IDWriteFontCollection* fontCollection = null; + int checkForUpdateInt = checkForUpdate ? 1 : 0; + int hr = _factory.Value->GetSystemFontCollection(&fontCollection, checkForUpdateInt); + + DWriteUtil.ConvertHresultToException(hr); + + return new FontCollection((Native.IDWriteFontCollection*)fontCollection); + } + + internal FontCollection GetFontCollection(Uri uri) + { + IDWriteFontCollection* fontCollection = null; + + string uriString = uri.AbsoluteUri; + + fixed (char* uriStringPtr = uriString) + { + uint collectionKeySize = (uint)((uriString.Length + 1) * sizeof(char)); + _factory.Value->CreateCustomFontCollection(null, uriStringPtr, collectionKeySize, &fontCollection); + } + + return new FontCollection((Native.IDWriteFontCollection*)fontCollection); + } + + internal static bool IsLocalUri(Uri uri) + { + return uri.IsFile && uri.IsLoopback && !uri.IsUnc; + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/ModuleInitializer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/ModuleInitializer.cs index 2d68e3c6ce1..732e4302df5 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/ModuleInitializer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/ModuleInitializer.cs @@ -2,6 +2,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using MS.Internal.Text.TextInterface; internal static class ModuleInitializer { @@ -19,6 +20,13 @@ public static void Initialize() { IsProcessDpiAware(); + DWriteLoader.LoadDWrite(); + + AppDomain.CurrentDomain.ProcessExit += static (object sender, EventArgs e) => + { + DWriteLoader.UnloadDWrite(); + }; + MS.Internal.NativeWPFDLLLoader.LoadDwrite(); } #pragma warning restore CA2255 diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/PresentationCore.csproj b/src/Microsoft.DotNet.Wpf/src/PresentationCore/PresentationCore.csproj index dd775ca091c..6f0505e07b2 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/PresentationCore.csproj +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/PresentationCore.csproj @@ -25,6 +25,20 @@ Common\System\SR.cs + + + + + + + @@ -231,6 +245,17 @@ + + + + + + + + + + + @@ -264,6 +289,9 @@ + + +