Skip to content
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

Introduce AOT friendly version of ToLogFont and FromLogFont #70224

Closed
wants to merge 12 commits into from

Conversation

kant2002
Copy link
Contributor

@kant2002 kant2002 commented Jun 3, 2022

Fixes dotnet/winforms#8846

These methods used by WinForms

@ghost ghost added the community-contribution Indicates that the PR has been added by a community member label Jun 3, 2022
@ghost
Copy link

ghost commented Jun 3, 2022

Tagging subscribers to this area: @dotnet/area-system-drawing
See info in area-owners.md if you want to be subscribed.

Issue Details

These methods used by WinForms

Author: kant2002
Assignees: -
Labels:

area-System.Drawing, community-contribution

Milestone: -

@kant2002
Copy link
Contributor Author

kant2002 commented Jun 3, 2022

/cc @RussKie in case public surface should be modified to be able to use this change.

Interop.User32.LOGFONT nativeLogFont = ToLogFontInternal(graphics);

// PtrToStructure requires that the passed in object not be a value type.
if (!type.IsValueType)
Copy link
Contributor

@Wraith2 Wraith2 Jun 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove the type variable and use typeof(T).IsValueType here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do even better, and put constraints on T to be struct. I do not think it's reasonable to allow objects if you are caring about trimmability.

Comment on lines 316 to 318
GCHandle handle = GCHandle.Alloc(logFont, GCHandleType.Pinned);
Buffer.MemoryCopy(&nativeLogFont, (byte*)handle.AddrOfPinnedObject(), nativeSize, nativeSize);
handle.Free();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try finally to ensure that free is called?

/// </summary>
/// <param name="lf">A boxed LOGFONT.</param>
/// <returns>The newly created <see cref="Font"/>.</returns>
public static Font FromLogFont<T>(T lf)
Copy link
Contributor

@Wraith2 Wraith2 Jun 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lf -> logFont ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. It's easy to be lazy.

@@ -291,6 +291,34 @@ public unsafe void ToLogFont(object logFont, Graphics graphics)
}
}

public unsafe void ToLogFont<T>(T logFont, Graphics graphics)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't you changing the public API surface here? Shouldn't this go through a API design review meeting? System.Drawing.Common ships as a package which doesn't contain a contract assembly and hence customers would start seeing this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly I'm not much experienced in this process, so decide to put some code. Should I create separate issue for API proposal?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 29 to 35
public sealed partial class Font
{
public void ToLogFont<T>(ref T logFont, Graphics graphics) where T: struct { throw null; }
public static Font FromLogFont<T>(in T logFont) where T: struct { throw null; }
public static Font FromLogFont<T>(in T logFont, IntPtr hdc) where T: struct { throw null; }
public void ToLogFont<T>(ref T logFont) where T: struct { throw null; }
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be conditionalised with #if NET7_0_OR_GREATER?

/__w/1/s/.packages/microsoft.dotnet.apicompat/7.0.0-beta.22255.2/build/Microsoft.DotNet.ApiCompat.targets(52,5): error : Compat issues with assembly System.Drawing.Common: [/__w/1/s/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj]
##[error].packages/microsoft.dotnet.apicompat/7.0.0-beta.22255.2/build/Microsoft.DotNet.ApiCompat.targets(52,5): error : (NETCORE_ENGINEERING_TELEMETRY=Build) Compat issues with assembly System.Drawing.Common:
/__w/1/s/.packages/microsoft.dotnet.apicompat/7.0.0-beta.22255.2/build/Microsoft.DotNet.ApiCompat.targets(52,5): error : MembersMustExist : Member 'public System.Drawing.Font System.Drawing.Font.FromLogFont<T>(T)' does not exist in the implementation but it does exist in the contract. [/__w/1/s/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj]
##[error].packages/microsoft.dotnet.apicompat/7.0.0-beta.22255.2/build/Microsoft.DotNet.ApiCompat.targets(52,5): error : (NETCORE_ENGINEERING_TELEMETRY=Build) MembersMustExist : Member 'public System.Drawing.Font System.Drawing.Font.FromLogFont<T>(T)' does not exist in the implementation but it does exist in the contract.

Copy link
Member

@ViktorHofer ViktorHofer Jun 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to enable the new APIs only for net7.0 (in both the ref and impl) instead of net6.0 and upwards?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I simply think that adding new API to .Net 7 would be less of a problem then anything else. Nothing prevent this API to work in .NET 6,

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I as a customer intentionally upgrade my System.Drawing.Common package reference from 6.0.0 to 7.0.0, I would expect that I get new features irrelevant of the underlying target framework that is being used. I think it makes sense to improve both the net6.0 and the net7.0 assets if there's no underlying reason to not do so.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's good point. I forget about that scenario, even if I use it all the time.

Interop.User32.LOGFONT nativeLogFont = ToLogFontInternal(graphics);

// PtrToStructure requires that the passed in object not be a value type.
Marshal.PtrToStructure<T>(new IntPtr(&nativeLogFont), logFont);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be best if these new APIs did not depend on the built-in runtime marshalling support at all.

Can these new APIs take void* that points to LOGFONT and require the callers to take of the marshalling if there is any marshalling required?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would change like this logFont = *(T*)&nativeLogFont; works?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I get why you propose void*, because any struct which given to method can have managed fields, then I cannot copy data over layout. @RussKie can we just adopt these small methods to WinForms in appropriate places, having new void* methods in System.Drawing.Common are probably not worth goal to pursue

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we just adopt these small methods to WinForms in appropriate places, having new void* methods in System.Drawing.Common are probably not worth goal to pursue

We can surely consider that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looping in @JeremyKuhne as he may have thoughts on the subject as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have we considered the unmanaged constraint? Then you can do everything without Marshal. @jkotas?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unmanaged constraint works for me.

ArgumentNullException.ThrowIfNull(logFont);

int nativeSize = sizeof(Interop.User32.LOGFONT);
if (Marshal.SizeOf<T>() != nativeSize)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (Marshal.SizeOf<T>() != nativeSize)
if (sizeof(T) != nativeSize)

It is not correct to use Marshal.SizeOf here

Comment on lines 313 to 316
Interop.User32.LOGFONT nativeLogFont = ToLogFontInternal(graphics);

// PtrToStructure requires that the passed in object not be a value type.
logFont = *(T*)&nativeLogFont;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Interop.User32.LOGFONT nativeLogFont = ToLogFontInternal(graphics);
// PtrToStructure requires that the passed in object not be a value type.
logFont = *(T*)&nativeLogFont;
*(Interop.User32.LOGFONT*)&logFont = ToLogFontInternal(graphics);

Avoid extra copy

}

int nativeSize = sizeof(Interop.User32.LOGFONT);
if (Marshal.SizeOf<T>() != nativeSize)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

// Now that we know the marshalled size is the same as LOGFONT, copy in the data
nativeLogFont = default;

*(T*)&nativeLogFont = logFont;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this one.

@carlossanlop carlossanlop added the NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) label Jun 29, 2022
@carlossanlop
Copy link
Member

Needs the API proposal to be approved first.

@danmoseley
Copy link
Member

I'm going to close this PR pending API approval. (This is what we generally do. It's easy to reopen the PR when API is approved.)

@danmoseley danmoseley closed this Jul 25, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Aug 24, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Drawing community-contribution Indicates that the PR has been added by a community member NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[API Proposal]: Trim friendly Font ToLogFont/FromLogFont
8 participants