Skip to content

Commit

Permalink
Merge pull request #628 from elachlan/626
Browse files Browse the repository at this point in the history
Add drawing types templates
  • Loading branch information
AArnott committed Aug 3, 2022
2 parents b834103 + f5fc9a5 commit eb1c5c2
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 2 deletions.
30 changes: 28 additions & 2 deletions src/Microsoft.Windows.CsWin32/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,20 @@ public class Generator : IDisposable
{ "LARGE_INTEGER", PredefinedType(Token(SyntaxKind.LongKeyword)) },
{ "ULARGE_INTEGER", PredefinedType(Token(SyntaxKind.ULongKeyword)) },
{ "OVERLAPPED", ParseTypeName("global::System.Threading.NativeOverlapped") },
{ "POINT", ParseTypeName("global::System.Drawing.Point") },
{ "POINTF", ParseTypeName("global::System.Drawing.PointF") },
};

/// <summary>
/// A map of .NET interop structs to use, keyed by the native structs that should <em>not</em> be generated <em>when marshaling is enabled.</em>
/// That is, these interop types should only be generated when marshaling is disabled.
/// </summary>
/// <devremarks>
/// When adding to this dictionary, consider also adding to <see cref="BannedAPIsWithMarshaling"/>.
/// </devremarks>
internal static readonly Dictionary<string, TypeSyntax> AdditionalBclInteropStructsMarshaled = new Dictionary<string, TypeSyntax>(StringComparer.Ordinal)
{
{ nameof(System.Runtime.InteropServices.ComTypes.IDataObject), ParseTypeName("global::System.Runtime.InteropServices.ComTypes.IDataObject") },
////{ "BOOL", PredefinedType(TokenWithSpace(SyntaxKind.BoolKeyword)) },
};

internal static readonly Dictionary<string, TypeSyntax> BclInteropSafeHandles = new Dictionary<string, TypeSyntax>(StringComparer.Ordinal)
Expand Down Expand Up @@ -408,13 +416,21 @@ private enum FriendlyOverloadOf
InterfaceMethod,
}

/// <summary>
/// Gets a map of interop APIs that should never be generated, whether marshaling is allowed or not, and messages to emit in diagnostics if these APIs are ever directly requested.
/// </summary>
internal static ImmutableDictionary<string, string> BannedAPIsWithoutMarshaling { get; } = ImmutableDictionary<string, string>.Empty
.Add("GetLastError", "Do not generate GetLastError. Call Marshal.GetLastWin32Error() instead. Learn more from https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshal.getlastwin32error")
.Add("OLD_LARGE_INTEGER", "Use the C# long keyword instead.")
.Add("LARGE_INTEGER", "Use the C# long keyword instead.")
.Add("ULARGE_INTEGER", "Use the C# ulong keyword instead.")
.Add("OVERLAPPED", "Use System.Threading.NativeOverlapped instead.");
.Add("OVERLAPPED", "Use System.Threading.NativeOverlapped instead.")
.Add("POINT", "Use System.Drawing.Point instead.")
.Add("POINTF", "Use System.Drawing.PointF instead.");

/// <summary>
/// Gets a map of interop APIs that should not be generated when marshaling is allowed, and messages to emit in diagnostics if these APIs are ever directly requested.
/// </summary>
internal static ImmutableDictionary<string, string> BannedAPIsWithMarshaling { get; } = BannedAPIsWithoutMarshaling
.Add("VARIANT", "Use `object` instead of VARIANT when in COM interface mode. VARIANT can only be emitted when emitting COM interfaces as structs.");

Expand Down Expand Up @@ -3659,6 +3675,16 @@ private StructDeclarationSyntax DeclareStruct(TypeDefinitionHandle typeDefHandle
// Add the additional members, taking care to not introduce redundant declarations.
members.AddRange(additionalMembers.Where(c => c is not StructDeclarationSyntax cs || !members.OfType<StructDeclarationSyntax>().Any(m => m.Identifier.ValueText == cs.Identifier.ValueText)));

switch (name.Identifier.ValueText)
{
case "RECT":
case "SIZE":
members.AddRange(this.ExtractMembersFromTemplate(name.Identifier.ValueText));
break;
default:
break;
}

StructDeclarationSyntax result = StructDeclaration(name.Identifier)
.AddMembers(members.ToArray())
.WithModifiers(TokenList(TokenWithSpace(this.Visibility), TokenWithSpace(SyntaxKind.PartialKeyword)));
Expand Down
26 changes: 26 additions & 0 deletions src/Microsoft.Windows.CsWin32/templates/RECT.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
partial struct RECT
{
internal RECT(global::System.Drawing.Rectangle value) :
this(value.Left, value.Top, value.Right, value.Bottom) { }
internal RECT(global::System.Drawing.Point location, global::System.Drawing.Size size) :
this(location.X, location.Y, unchecked(location.X + size.Width), unchecked(location.Y + size.Height)) { }
internal RECT(int left, int top, int right, int bottom)
{
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}

internal static RECT FromXYWH(int x, int y, int width, int height) =>
new RECT(x, y, unchecked(x + width), unchecked(y + height));
internal readonly int Width => unchecked(this.right - this.left);
internal readonly int Height => unchecked(this.bottom - this.top);
internal readonly bool IsEmpty => this.left == 0 && this.top == 0 && this.right == 0 && this.bottom == 0;
internal readonly int X => this.left;
internal readonly int Y => this.top;
internal readonly global::System.Drawing.Size Size => new global::System.Drawing.Size(this.Width, this.Height);
public static implicit operator global::System.Drawing.Rectangle(RECT value) => new global::System.Drawing.Rectangle(value.left, value.top, value.Width, value.Height);
public static implicit operator global::System.Drawing.RectangleF(RECT value) => new global::System.Drawing.RectangleF(value.left, value.top, value.Width, value.Height);
public static implicit operator RECT(global::System.Drawing.Rectangle value) => new RECT(value);
}
15 changes: 15 additions & 0 deletions src/Microsoft.Windows.CsWin32/templates/SIZE.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
partial struct SIZE
{
internal SIZE(global::System.Drawing.Size value) : this(value.Width, value.Height) { }
internal SIZE(int width, int height)
{
this.cx = width;
this.cy = height;
}

internal readonly int Width => this.cx;
internal readonly int Height => this.cy;
internal readonly bool IsEmpty => this.cx == 0 && this.cy == 0;
public static implicit operator global::System.Drawing.Size(SIZE value) => new global::System.Drawing.Size(value.cx, value.cy);
public static implicit operator SIZE(global::System.Drawing.Size value) => new SIZE(value);
}
2 changes: 2 additions & 0 deletions test/GenerationSandbox.Tests/NativeMethods.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
BOOL
BOOLEAN
RECT
SIZE
CHAR
CreateCursor
CreateFile
Expand Down
77 changes: 77 additions & 0 deletions test/GenerationSandbox.Tests/SystemDrawingStructTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Drawing;
using Windows.Win32.Foundation;

public class SystemDrawingStructTests
{
[Fact]
public void Size()
{
SIZE s = new SIZE(1, 1);
Size s2 = s;
Assert.False(s.IsEmpty);
Assert.False(s2.IsEmpty);

Assert.True(default(SIZE).IsEmpty);
}

[Fact]
public void NotLossyConversionBetweenSizeAndSIZE()
{
SIZE nativeSize = new SIZE(1, 1);
Size managedSize = nativeSize;
SIZE roundtrippedNativeSize = managedSize;
Assert.Equal(nativeSize, roundtrippedNativeSize);
}

[Fact]
public void NotLossyConversionBetweenSizeAndSIZE_Ctors()
{
SIZE nativeSize = new SIZE(1, 1);
Size managedSize = nativeSize;
SIZE roundtrippedNativeSize = new SIZE(managedSize);
Assert.Equal(nativeSize, roundtrippedNativeSize);
}

[Fact]
public void Rect()
{
RECT r = new RECT(1, 1, 2, 2);
Rectangle r2 = r;
Assert.False(r.IsEmpty);
Assert.False(r2.IsEmpty);

Assert.True(default(RECT).IsEmpty);
}

[Fact]
public void NotLossyConversionBetweenRectangleAndRECT()
{
RECT nativeSize = new RECT(1, 1, 2, 2);
Rectangle managedSize = nativeSize;
RECT roundtrippedNativeSize = managedSize;
Assert.Equal(nativeSize, roundtrippedNativeSize);
}

[Fact]
public void NotLossyConversionBetweenRectangleAndRECT_Ctors()
{
RECT nativeSize = new RECT(1, 1, 2, 2);
Rectangle managedSize = nativeSize;
RECT roundtrippedNativeSize = new RECT(managedSize);
Assert.Equal(nativeSize, roundtrippedNativeSize);
}

[Fact]
public void RectangleAndRECTFromXYWH_AreEqual()
{
RECT nativeSize = RECT.FromXYWH(1, 1, 2, 2);
Rectangle managedSize = new Rectangle(1, 1, 2, 2);
Assert.Equal(nativeSize.left, managedSize.Left);
Assert.Equal(nativeSize.right, managedSize.Right);
Assert.Equal(nativeSize.top, managedSize.Top);
Assert.Equal(nativeSize.bottom, managedSize.Bottom);
}
}

0 comments on commit eb1c5c2

Please sign in to comment.