-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Managed DWrite] Migrate Factory to managed #6171
[Managed DWrite] Migrate Factory to managed #6171
Conversation
@@ -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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed Factory
to IDWriteFactory
to be able to remove Factory from DirectWriteForwarder. Factory
was only used to call DWriteFactoryAddRef
and then use the returned IDWriteFactory
. I moved the responsibility of calling EDIT: This is no longer the case, I changed it back to it's initial behavior by just calling AddRef on IDWriteFactory.DWriteFactoryAddRef
to the caller the method.
@@ -25,159 +25,18 @@ namespace MS { namespace Internal { namespace Text { namespace TextInterface | |||
/// <summary> | |||
/// The root factory interface for all DWrite objects. | |||
/// </summary> | |||
private ref class Factory sealed : public CriticalHandle | |||
private ref class InternalFactory abstract sealed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I renamed Factory
to InternalFactory
to avoid conflicts in PresentationCore since I added a new Factory
class with the same namespace.
src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.GetModuleHandle.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.GetProcAddress.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.LoadLibrary.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.LoadLibraryEx.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Interop.HRESULT_FROM_WIN32.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.DotNet.Wpf/src/Common/src/Interop/Windows/Kernel32/Interop.FreeLibrary.cs
Outdated
Show resolved
Hide resolved
So can someone review and merge this PR so we can push forward the removal of C++/CLI in favor of making WPF trim/AOT-compatible? |
Lol, you must be new here 😂. Welcome to the WPF repo where it takes months before a PR get's reviewed. So sit down, relax, take a coke or a coffee while waiting and enjoy your experience. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left comments about RCW created.
|
||
namespace MS.Internal.Interop.DWrite | ||
{ | ||
internal unsafe struct IDWriteFactory : IUnknown |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would not try to replicate COM inheritance here. If you look how RCW/CCW with ComWrapppers implemented in runtime, or even more lean in WinForms.
For example IUnknown
interface not needed, since you can use regular casts instead of QueryInterface and Marshal.AddRef(IntPtr)
/Marshal.Release(IntPtr)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to leave it like this. I try to stay closer to the code generated by ClangSharp which maps the header files to C#. This offers some kind of type safety when using generics (Like here). I also try to avoid using Marshal.AddRef(IntPtr)/Marshal.Release(IntPtr)
because we can just call the methods implemented in the struct or in IUnknown
. The drawback is that the code is slightly larger but I think the type safety and cleaner code (In my opinion) is worth it.
|
||
public int QueryInterface(Guid* riid, void** ppvObject) | ||
{ | ||
return ((delegate* unmanaged[Stdcall]<IDWriteFactory*, Guid*, void**, int>)(lpVtbl[0]))((IDWriteFactory*)Unsafe.AsPointer(ref this), riid, ppvObject); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Stdcall]
is default call convention, and no need to specify it here.
No need to use explicit types like IDWriteFactory*
where you can just have IntPtr
or void*
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 557eea0394c625106e6d7f87fb35426987b47277. I tweaked ClangSharp to not add Stdcall.
For the @dipeshmsft or other team members. This is almost indication that DirectWriteForwarder can be sliced and diced as we like. If I understand correctly, no ref assemblies, no contract guarantees from MS. |
I would also like to point out that there is DWriteCore which should have compatible API (except the factory is createdy by |
@kant2002 @ThomasGoulet73 I don't have much experience with RCW development, so please bear with me - I would like to learn why this approach is better than just relying on the RCW generated by the runtime. i.e. what are the benefits of internal unsafe struct IDWriteFactory : IUnknown
{
public void** lpVtbl;
public int QueryInterface(Guid* riid, void** ppvObject)
{
return ((delegate* unmanaged<IDWriteFactory*, Guid*, void**, int>)(lpVtbl[0]))((IDWriteFactory*)Unsafe.AsPointer(ref this), riid, ppvObject);
}
public uint AddRef()
{
return ((delegate* unmanaged<IDWriteFactory*, uint>)(lpVtbl[1]))((IDWriteFactory*)Unsafe.AsPointer(ref this));
}
public uint Release()
{
return ((delegate* unmanaged<IDWriteFactory*, uint>)(lpVtbl[2]))((IDWriteFactory*)Unsafe.AsPointer(ref this));
}
...
public int CreateTextAnalyzer(IDWriteTextAnalyzer** textAnalyzer)
{
return ((delegate* unmanaged<IDWriteFactory*, IDWriteTextAnalyzer**, int>)(lpVtbl[21]))((IDWriteFactory*)Unsafe.AsPointer(ref this), textAnalyzer);
} over [ComImport, Guid(UuidOf.IDWriteFactory), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IDWriteFactory
{
...
IDWriteTextAnalyzer CreateTextAnalyzer();
} ? |
That's very nice and if that's closely aligned maybe that's best way to use it. Still we have to wait for the feedback from the team, which almost does not receive funding on this area.
Benefits of hand-written RCW/CCW is that this make code linker friendly, and trimming friendly. Without that ILTrim does not know what COM interfaces it should brings. Also NativeAOT does not support built-in COM, so if this PR lands, and subsequent works too, then maybe can have WPF in NativeAOT. Right now because of that built-in COM trimming during R2R publish disabled for WPF apps, making them unnecessary larger. |
557eea0
to
4d756fb
Compare
@dotnet/wpf-developers: Could this PR be considered for the next test pass? I think it's a valuable PR since it's a first step for a migration to managed DirectWriteForwarder to allow linker and NativeAOT friendliness. I think it would be good to merge it early in .Net 7 previews to allow testing. I understand that this PR is rather large but it is the smallest changes I could think of for a starting point for the migration (I'm open to suggestions if someone can think of way to split this PR in smaller changes). |
Looks like they don't. Several PR's got queued for test now but this not. @ThomasGoulet73 I hope you still got patience. |
UPDATE:
|
Thank you guys!! |
Thank you very much for your contribution to promoting this feature. I am also very interested in this feature, but I don’t quite understand the above reply. Is the progress of this PR already at the final stage? |
I can always negatively talk, how WPF team is moving slow, we still do not have all DRT ported, etc, etc, etc. It maybe because we have bad technical team, or other way around because we have bad management, or somebody from MS do not give enough money on the project. I simply don't know. What I know from reply of Ashish is that this issue is getting attention, and we need only fix couple tests to start talking what else is needed. I really appreciate that even after long time of idling this issue may start moving. So I decide to say thanks to WPF team to paying attention to this PR. |
This is the first baby step of the C++/CLI -> C# migration throughout WPF, still, good to hear things finally start moving. Thanks to the team! |
@SmRiley - Well, it's hard to say right now. Once we investigate, isolate and fix the failing tests (either in the PR or the tests themselves), we will be in a better shape to consider the next steps with this PR. We want to ensure that we fix the known issues first and take this PR early on so it gives us enough room to troubleshoot any future compat problems. @kant2002 - I think some of the tests are already available on the test repo (if you'd like to take a crack at them) |
I'm glad this PR is being considered by the team. @pchaurasia14 I tried running the tests locally but it didn't seem to work, I'll try again in a couple of days. Let me know if the team is able to isolate the bug. Thanks. |
cc:// @harshit7962 |
In Annotations Test, we are calling the @ThomasGoulet73 - Will passing the same as the parameter here solve the issue? or did you omit it foreseeing some other failure? |
Thank you @harshit7962. That does seem like a mistake, it was probably an oversight when trying to get it to build and then I forgot to change it. I pushed a commit that fixes the null parameter, let me know if it fixes the tests. I was able to run DrtAnnotations but I can't get it to fail, the tests passes before and after my last commit. DrtAnnotations also seem to pass in the CI (If it's the same thing?). |
DRTAnnotations and related tests are passing after your fix. We will be checking the other failures as of now. |
/// <summary> | ||
/// The root factory interface for all DWrite objects. | ||
/// </summary> | ||
internal unsafe class Factory |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ThomasGoulet73 - I've noticed that the equivalent class in the C++ implementation, does inherit from CriticalHandle
which is missing here.
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a mistake. I think I was trying to reuse the class NativeIUnknownWrapper
but Factory.cpp does more things in ReleaseHandle than what NativeIUnknownWrapper.ReleaseHandle
does. I'll try to fix it soon and make it closer to C++ and we can clean it up later if needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pushed a new commit where I redid in C# what Factory.ReleaseHandle
did in C++. I took a different approach where ReleaseHandle
is in a private class that inherits NativeIUnknownWrapper
where it also unregisters and releases FontFileLoader
and FontCollectionLoader
in ReleaseHandle
.
I verified that everything the FontFileLoader
and FontCollectionLoader
where unregistered and released when Factory
is garbage collected in a sample application that forces it to be garbage collected.
All internal tests are green now for this PR, also the due process is complete. We are good to merge this one. Tagging @dotnet/dotnet-wpf-triage @dotnet/wpf-developers for any final comments or suggestions. |
Thank you @ThomasGoulet73 for all your hard work and patience! Apologies on behalf of the team for the delays. Thank you to all the contributors who commented, reviewed, debugged, and fixed test failures. |
Yay! Updates to WPF! 🥳 |
Big thanks to the team (Even if there was a delay 😄) and to everyone who reviewed this PR! |
BIG one down - well done all who contributed, reviewed, merged, etc this one! |
Contributes to #5305.
Description
This is the first part of #5305. I migrated only Factory to managed for now. This should allow to migrate DirectWriteForwarder to C# by migrating everything related to Factory because almost everything related to DWrite starts from Factory.
For now, the bindings to DWrite are generated by dotnet/ClangSharp and modified by hand (I plan to use dotnet/ClangSharp without modifications when I'm able to tune it like I want).
This is only a small chunk of the migration of DirectWriteForwarder to C# but hopefully this will allow to fully migrate it.
Customer Impact
It might be faster and should allow better support of trimming and the support of NativeAOT once everything is migrated to C#.
Regression
No.
Testing
Local build + CI + I tested a few apps to make sure that the text rendering is working.
Risk
Low. For the most part, it is a copy of the C++ code manually rewritten to C# with as little changes as possible.