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

API Proposal: Expose System.Drawing.Graphics.NativeGraphics. #8833

Open
JeremyKuhne opened this issue Jul 16, 2020 · 29 comments
Open

API Proposal: Expose System.Drawing.Graphics.NativeGraphics. #8833

JeremyKuhne opened this issue Jul 16, 2020 · 29 comments
Assignees
Labels
api-approved (4) API was approved in API review, it can be implemented area-Interop area-System.Drawing System.Drawing issues tenet-performance Improve performance, flag performance regressions across core releases
Milestone

Comments

@JeremyKuhne
Copy link
Member

JeremyKuhne commented Jul 16, 2020

Background and Motivation

In Windows Forms painting it would be helpful if we had the option to plug our own P/Invokes calls into existing GDI+ objects. We could, for example, have lighter-weight GpPen and GpBrush wrappers (structs perhaps) and invoke the GDI+ API directly with them via the exposed Graphics pointer.

While we could create our own GpGraphics object that usually isn't practical as we often have to interop via the System.Drawing types.

The form of the API here follows what Icon does (which is a GDI wrapper). Having a static construction method is necessary as we sometimes have derived types that we want to return (SolidBrush, TextureBrush, etc.).

Proposed API

namespace System.Drawing
{
    public class Graphics
    {
+      public IntPtr Handle { get; }
+      public static Graphics FromHandle(IntPtr handle);
    }

    public abstract class Brush
    {
+      public IntPtr Handle { get; }
+      public static Brush FromHandle(IntPtr handle);
    }

    public class Pen
    {
+      public IntPtr Handle { get; }
+      public static Pen FromHandle(IntPtr handle);
    }

    public class Image
    {
+      public IntPtr Handle { get; }
+      public static Image FromHandle(IntPtr handle);
    }

    public class Region
    {
+      public IntPtr Handle { get; }
+      public static Region FromHandle(IntPtr handle);
    }
}

namespace System.Drawing.Drawing2D
{
    public class Matrix
    {
+      public IntPtr Handle { get; }
+      public static Matrix FromHandle(IntPtr handle);
    }

    public class GraphicsPath
    {
+      public IntPtr Handle { get; }
+      public static GraphicsPath FromHandle(IntPtr handle);
    }
}

Notes

  • Like Icon.FromHandle, these FromHandle APIs will not take ownership and closing the handle is the caller's responsibility.

Risks

No specific risks outside of what comes normally with accessing backing handles. You can delete the backing object or try to use the handle after the Graphics closes the native handle. Not much different than what you'd be running into using a disposed Graphics.

Note that we already expose IDeviceContext on Graphics which is much more risky as everything on Graphics throws when you're in-between GetHDC() and ReleaseHDC(). The throw there comes from GDI+ as it doesn't want to conflict with the usage of the backing HDC. GDI+ maintains and restores the state of the HDC outside of those calls.

@JeremyKuhne JeremyKuhne added api-suggestion (1) Early API idea and discussion, it is NOT ready for implementation area-System.Drawing System.Drawing issues labels Jul 16, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the untriaged The team needs to look at this issue in the next triage label Jul 16, 2020
@ghost
Copy link

ghost commented Jul 16, 2020

Tagging subscribers to this area: @safern, @tannergooding
Notify danmosemsft if you want to be subscribed.

@safern
Copy link
Member

safern commented Jul 16, 2020

@JeremyKuhne given the timeframe I think this would need to be added in 6.0, right?

@JeremyKuhne
Copy link
Member Author

given the timeframe I think this would need to be added in 6.0, right?

Probably. Fyi, I'm investigating the painting loops so I'll likely open more API proposals on System.Drawing.

@safern safern removed the untriaged The team needs to look at this issue in the next triage label Jul 16, 2020
@JeremyKuhne
Copy link
Member Author

@safern The other thing to consider here is having a public constructor overload that takes the GpGraphics pointer.

@safern
Copy link
Member

safern commented Feb 8, 2021

Sounds reasonable. Feel free to update the proposal to include the new constructor overload if needed.

@safern safern added api-ready-for-review (2) API is ready for formal API review; applied by the issue owner and removed api-suggestion (1) Early API idea and discussion, it is NOT ready for implementation labels Feb 8, 2021
@tannergooding
Copy link
Member

Is NativeGraphics better than Handle which is how (IIRC) WinForms and other similar scenarios expose the "same"?

@tannergooding
Copy link
Member

Likewise, are there any other types where it may be beneficial to expose the underlying "handle" and/or support constructing from them?

@JeremyKuhne
Copy link
Member Author

Is NativeGraphics better than Handle which is how (IIRC) WinForms and other similar scenarios expose the "same"?

Handle might be better. I just picked what it is currently called in the proposal.

Likewise, are there any other types where it may be beneficial to expose the underlying "handle" and/or support constructing from them?

Would probably be useful to put on all of the Gp items. Exchanging the drawing surface (Graphics) was the most important.

Here are the other types. Constructing requires a little bit of investigation as we have derived classes. We'd probably have to have static factory methods (which we do have internally in at least some cases).

  • Font
  • Brush
  • Pen
  • Image
  • Region
  • GraphicsPath
  • Matrix

Icon is actually a GDI wrapper, not a GDI+ object. It has Handle and FromHandle().

I'll update the proposal above.

@JeremyKuhne
Copy link
Member Author

@tannergooding / @safern Updated the proposal to be more thorough.

@terrajobst terrajobst added api-approved (4) API was approved in API review, it can be implemented and removed api-ready-for-review (2) API is ready for formal API review; applied by the issue owner labels Feb 9, 2021
@terrajobst
Copy link
Member

terrajobst commented Feb 9, 2021

Video

  • Looks good as proposed
namespace System.Drawing
{
    public partial class Graphics
    {
        public IntPtr Handle { get; }
        public static Graphics FromHandle(IntPtr handle);
    }
    public partial class Brush
    {
        public IntPtr Handle { get; }
        public static Brush FromHandle(IntPtr handle);
    }
    public partial class Pen
    {
        public IntPtr Handle { get; }
        public static Pen FromHandle(IntPtr handle);
    }
    public partial class Image
    {
        public IntPtr Handle { get; }
        public static Image FromHandle(IntPtr handle);
    }

    public partial class Region
    {
        public IntPtr Handle { get; }
        public static Region FromHandle(IntPtr handle);
    }
}
namespace System.Drawing.Drawing2D
{
    public partial class Matrix
    {
        public IntPtr Handle { get; }
        public static Matrix FromHandle(IntPtr handle);
    }

    public partial class GraphicsPath
    {
        public IntPtr Handle { get; }
        public static GraphicsPath FromHandle(IntPtr handle);
    }
}

@reflectronic
Copy link

We should expose these for CachedBitmap (#8822) too.

namespace System.Drawing.Imaging
{
    public partial class CachedBitmap
    {
        public IntPtr Handle { get; }
        public static CachedBitmap FromHandle(IntPtr handle);
    }
}

@safern
Copy link
Member

safern commented Jul 22, 2021

@JeremyKuhne are you planning on working on this one or should we move it to 7.0.0?

@teo-tsirpanis
Copy link
Contributor

How about making the Handle properties return SafeHandles?

@JeremyKuhne
Copy link
Member Author

How about making the Handle properties return SafeHandles?

Good question. Doing so would have performance implications (finalizable objects are expensive to create) for the scenarios this is intended for (advanced interop). It would also be a bit difficult to express ownership in these cases as the originating class actually owns the handle. We could ref count, but that also adds weight.

When I implement this I'll make sure to be very explicit about expectations with the handle ownership.

@JeremyKuhne
Copy link
Member Author

@safern sorry about not responding here, I was distracted with Jury Duty. 7 is fine. :)

@deeprobin
Copy link

You're welcome to assign me 😄

@danmoseley
Copy link
Member

@deeprobin assigned, thanks. We should try to reduce your backlog of open PR's first though. Are all except the draft ones waiting on us?

@deeprobin
Copy link

@danmoseley Thank you. Thats right. Most prs waiting :/
grafik

@danmoseley
Copy link
Member

danmoseley commented Dec 21, 2021

OK. Yeah, this is the slowest time of the year so folks mostly won't be back until Jan. I reviewed your 2nd one, if you address feedback we can merge that. The others I should wait for area experts.

I don't have a problem with you throwing up more PR's meantime, when you become entirely blocked on us 😄

@deeprobin
Copy link

deeprobin commented Dec 21, 2021

OK. Yeah, this is the slowest time of the year so folks mostly won't be back until Jan. I reviewed your 2nd one, if you address feedback we can merge that. The others I should wait for area experts.

I don't have a problem with you throwing up more PR's meantime, when you become entirely blocked on us 😄

Perfect. I should just not get bogged down when then comes the mass of reviews in the new year 😄

@teo-tsirpanis
Copy link
Contributor

Is it decided that we won't expose them as SafeHandles? I am interested in moving the interop code away from HandleRefs to help with the DllImport source generator and to address @JeremyKuhne's concerns about handle ownership (it will be SafeHandles all the way in, like with FileStream). If we go that way, we would also need to add public SafeHandle derivatives such as SafeGraphicsHandle or SafeBrushHandle.

@jkoritzinsky
Copy link
Member

I would be in support of @teo-tsirpanis’s proposal to move to SafeHandle-based interop.

@danmoseley
Copy link
Member

@deeprobin going through old issues. do you plan to continue with this one?

@deeprobin
Copy link

deeprobin commented Apr 13, 2022

@danmoseley
System.Drawing will probably die in the long run anyway.

In the PR it is only about testfailures that fail due to a double-dispose.
Since they are 2 objects (the initial object and the by-handle object), the finalizer will always implicitly call Dispose, so an exception will be thrown in any case.
GdipDeleteRegion is for ex. not safe to call twice 😞.

Basically the implementation of the PR is ready.
Either we leave out the tests or we find an easy way to test this anyway.

Very happy to work on the PR some more, unless it will be such a big effort that it won't be worth it in the long run.

@teo-tsirpanis
Copy link
Contributor

teo-tsirpanis commented Apr 15, 2022

I am working on porting System.Drawing.Common's interop layer to SafeHandles. Here's an alternative proposal based on that:

namespace System.Drawing
{
    public class Graphics
    {
      public SafeGraphicsHandle SafeGraphicsHandle { get; }
      public Graphics(SafeGraphicsHandle handle);
    }

    public abstract class Brush
    {
      public SafeBrushHandle SafeBrushHandle { get; }
      public static Brush FromHandle(SafeBrushHandle handle); // Creates an internal concrete descendant of Brush.
    }

    public class Pen
    {
      public SafePenHandle SafePenHandle { get; }
      public Pen(SafePenHandle handle);
    }

    public class Image
    {
      public SafeImageHandle SafeImageHandle { get; }
      public Image(SafeImageHandle handle);
    }

    public class Region
    {
      public SafeRegionHandle SafeRegionHandle { get; }
      public Region(SafeRegionHandle handle);
    }
}

namespace System.Drawing.Drawing2D
{
    public class Matrix
    {
      public SafeMatrixHandle SafeMatrixHandle { get; }
      public Matrix(SafeMatrixHandle handle);
    }

    public class GraphicsPath
    {
      public SafeGraphicsPathHandle SafeGraphicsPathHandle { get; }
      public GraphicsPath(SafeGraphicsPathHandle handle);
    }
}

namespace Microsoft.Win32.SafeHandles
{
    public abstract class SafeGdiPlusHandle : SafeHandle { }
    public abstract class SafeGraphicsHandle : SafeGdiPlusHandle { }
    public abstract class SafeBrushHandle : SafeGdiPlusHandle { }
    public abstract class SafePenHandle : SafeGdiPlusHandle { }
    public abstract class SafeImageHandle : SafeGdiPlusHandle { }
    public abstract class SafeRegionHandle : SafeGdiPlusHandle { }
    public abstract class SafeMatrixHandle : SafeGdiPlusHandle { }
    public abstract class SafeGraphicsPathHandle : SafeGdiPlusHandle { }
}

@JeremyKuhne what do you think?

@danmoseley
Copy link
Member

System.Drawing will probably die in the long run anyway.

I would rather think of it as in maintenance mode. It has not had new features for a long time. For someone that has existing code that uses it, or wants to do something simple and perhaps already depend on the Windows compatibility pack, it is a reasonable choice.

@JeremyKuhne
Copy link
Member Author

I would rather think of it as in maintenance mode.

It won't go anywhere for a long time as Windows Forms is still a heavy user (there are many, many active WinForms developers). This particular API I still plan to implement when I have bandwidth to consume the changes in the Windows Forms runtime.

@teo-tsirpanis There is measurable overhead to SafeHandle and the intent for this API is allow for inner-loop optimizations in WinForms rendering (skipping straight to native GDI+ exports without additional managed heap allocations).

Doing this internally would add overhead I'm uncomfortable with.

@deeprobin
Copy link

I think this issue should be taken over by someone else with more experience in System.Drawing.

@teo-tsirpanis Would you like to take over the issue?

@deeprobin deeprobin removed their assignment Aug 16, 2022
@teo-tsirpanis
Copy link
Contributor

OK, it looks relatively simple, I will do it at some point in the 8.0.0 timeframe, assigning myself to not forget it.

I have also abandoned my experiments with using SafeHandles. There are performance concerns and SDC is not that of a high-activity part of the BCL either way.

@teo-tsirpanis teo-tsirpanis self-assigned this Aug 16, 2022
@JeremyKuhne JeremyKuhne transferred this issue from dotnet/runtime Mar 14, 2023
@elachlan elachlan added tenet-performance Improve performance, flag performance regressions across core releases area-Interop labels Nov 5, 2023
@merriemcgaw merriemcgaw added this to the Future milestone Jan 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-approved (4) API was approved in API review, it can be implemented area-Interop area-System.Drawing System.Drawing issues tenet-performance Improve performance, flag performance regressions across core releases
Projects
None yet
Development

Successfully merging a pull request may close this issue.