From 6c980dff7af857ebc68518150c08a96a95a04d1d Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Fri, 9 Oct 2020 14:07:04 -0700 Subject: [PATCH] Boolean and Delegate tests (#152) * Boolean and Delegate tests Updated to DNNE 1.0.12 with support for C# function pointers. Renamed CBoolMarshaller to ByteBoolMarshaller. Added integration tests for all bool marshaller types. Added integration tests for delegates. Style nits. --- .../BooleanTests.cs | 140 ++++++++++++++++++ .../DelegateTests.cs | 55 +++++++ .../Marshalling/BoolMarshaller.cs | 46 ++++-- .../Marshalling/DelegateMarshaller.cs | 5 +- .../Marshalling/Forwarder.cs | 2 +- .../Marshalling/MarshallingGenerator.cs | 8 +- .../Marshalling/SafeHandleMarshaller.cs | 5 +- .../TestAssets/NativeExports/Booleans.cs | 31 ++++ .../DelegatesAndFunctionPointers.cs | 30 ++++ .../NativeExports/NativeExports.csproj | 23 +-- 10 files changed, 301 insertions(+), 44 deletions(-) create mode 100644 DllImportGenerator/DllImportGenerator.IntegrationTests/BooleanTests.cs create mode 100644 DllImportGenerator/DllImportGenerator.IntegrationTests/DelegateTests.cs create mode 100644 DllImportGenerator/TestAssets/NativeExports/Booleans.cs create mode 100644 DllImportGenerator/TestAssets/NativeExports/DelegatesAndFunctionPointers.cs diff --git a/DllImportGenerator/DllImportGenerator.IntegrationTests/BooleanTests.cs b/DllImportGenerator/DllImportGenerator.IntegrationTests/BooleanTests.cs new file mode 100644 index 000000000000..4f870d7a44a2 --- /dev/null +++ b/DllImportGenerator/DllImportGenerator.IntegrationTests/BooleanTests.cs @@ -0,0 +1,140 @@ +using System.Collections.Generic; +using System.Runtime.InteropServices; + +using Xunit; + +namespace DllImportGenerator.IntegrationTests +{ + partial class NativeExportsNE + { + [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "bytebool_return_as_uint")] + public static partial uint ReturnByteBoolAsUInt([MarshalAs(UnmanagedType.U1)] bool input); + + [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "bytebool_return_as_uint")] + public static partial uint ReturnSByteBoolAsUInt([MarshalAs(UnmanagedType.I1)] bool input); + + [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "variantbool_return_as_uint")] + public static partial uint ReturnVariantBoolAsUInt([MarshalAs(UnmanagedType.VariantBool)] bool input); + + [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "bool_return_as_uint")] + public static partial uint ReturnIntBoolAsUInt([MarshalAs(UnmanagedType.I4)] bool input); + + [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "bool_return_as_uint")] + public static partial uint ReturnUIntBoolAsUInt([MarshalAs(UnmanagedType.U4)] bool input); + + [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "bool_return_as_uint")] + public static partial uint ReturnWinBoolAsUInt([MarshalAs(UnmanagedType.Bool)] bool input); + + [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "bool_return_as_uint")] + [return: MarshalAs(UnmanagedType.U1)] + public static partial bool ReturnUIntAsByteBool(uint input); + + [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "bool_return_as_uint")] + [return: MarshalAs(UnmanagedType.VariantBool)] + public static partial bool ReturnUIntAsVariantBool(uint input); + + [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "bool_return_as_uint")] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool ReturnUIntAsWinBool(uint input); + + [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "bool_return_as_refuint")] + public static partial void ReturnUIntAsRefByteBool(uint input, [MarshalAs(UnmanagedType.U1)] ref bool res); + + [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "bool_return_as_refuint")] + public static partial void ReturnUIntAsOutByteBool(uint input, [MarshalAs(UnmanagedType.U1)] out bool res); + + [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "bool_return_as_refuint")] + public static partial void ReturnUIntAsRefVariantBool(uint input, [MarshalAs(UnmanagedType.VariantBool)] ref bool res); + + [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "bool_return_as_refuint")] + public static partial void ReturnUIntAsOutVariantBool(uint input, [MarshalAs(UnmanagedType.VariantBool)] out bool res); + + [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "bool_return_as_refuint")] + public static partial void ReturnUIntAsRefWinBool(uint input, [MarshalAs(UnmanagedType.Bool)] ref bool res); + + [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "bool_return_as_refuint")] + public static partial void ReturnUIntAsOutWinBool(uint input, [MarshalAs(UnmanagedType.Bool)] out bool res); + } + + public class BooleanTests + { + // See definition of Windows' VARIANT_BOOL + const ushort VARIANT_TRUE = unchecked((ushort)-1); + const ushort VARIANT_FALSE = 0; + + [Fact] + public void ValidateBoolIsMarshalledAsExpected() + { + Assert.Equal((uint)1, NativeExportsNE.ReturnByteBoolAsUInt(true)); + Assert.Equal((uint)0, NativeExportsNE.ReturnByteBoolAsUInt(false)); + Assert.Equal((uint)1, NativeExportsNE.ReturnSByteBoolAsUInt(true)); + Assert.Equal((uint)0, NativeExportsNE.ReturnSByteBoolAsUInt(false)); + Assert.Equal(VARIANT_TRUE, NativeExportsNE.ReturnVariantBoolAsUInt(true)); + Assert.Equal(VARIANT_FALSE, NativeExportsNE.ReturnVariantBoolAsUInt(false)); + Assert.Equal((uint)1, NativeExportsNE.ReturnIntBoolAsUInt(true)); + Assert.Equal((uint)0, NativeExportsNE.ReturnIntBoolAsUInt(false)); + Assert.Equal((uint)1, NativeExportsNE.ReturnUIntBoolAsUInt(true)); + Assert.Equal((uint)0, NativeExportsNE.ReturnUIntBoolAsUInt(false)); + Assert.Equal((uint)1, NativeExportsNE.ReturnWinBoolAsUInt(true)); + Assert.Equal((uint)0, NativeExportsNE.ReturnWinBoolAsUInt(false)); + } + + [Theory] + [InlineData(new object[] { 0, false })] + [InlineData(new object[] { 1, true })] + [InlineData(new object[] { 37, true })] + [InlineData(new object[] { 0xff, true })] + [InlineData(new object[] { 0xffffff00, false })] + public void ValidateByteBoolReturns(uint value, bool expected) + { + Assert.Equal(expected, NativeExportsNE.ReturnUIntAsByteBool(value)); + + bool result = !expected; + NativeExportsNE.ReturnUIntAsRefByteBool(value, ref result); + Assert.Equal(expected, result); + + result = !expected; + NativeExportsNE.ReturnUIntAsOutByteBool(value, out result); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(new object[] { 0, false })] + [InlineData(new object[] { 1, false })] + [InlineData(new object[] { 0xff, false })] + [InlineData(new object[] { VARIANT_TRUE, true })] + [InlineData(new object[] { 0xffffffff, true })] + [InlineData(new object[] { 0xffff0000, false })] + public void ValidateVariantBoolReturns(uint value, bool expected) + { + Assert.Equal(expected, NativeExportsNE.ReturnUIntAsVariantBool(value)); + + bool result = !expected; + NativeExportsNE.ReturnUIntAsRefVariantBool(value, ref result); + Assert.Equal(expected, result); + + result = !expected; + NativeExportsNE.ReturnUIntAsOutVariantBool(value, out result); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(new object[] { 0, false })] + [InlineData(new object[] { 1, true})] + [InlineData(new object[] { 37, true })] + [InlineData(new object[] { 0xffffffff, true })] + [InlineData(new object[] { 0x80000000, true })] + public void ValidateWinBoolReturns(uint value, bool expected) + { + Assert.Equal(expected, NativeExportsNE.ReturnUIntAsWinBool(value)); + + bool result = !expected; + NativeExportsNE.ReturnUIntAsRefWinBool(value, ref result); + Assert.Equal(expected, result); + + result = !expected; + NativeExportsNE.ReturnUIntAsOutWinBool(value, out result); + Assert.Equal(expected, result); + } + } +} diff --git a/DllImportGenerator/DllImportGenerator.IntegrationTests/DelegateTests.cs b/DllImportGenerator/DllImportGenerator.IntegrationTests/DelegateTests.cs new file mode 100644 index 000000000000..24ac17cec0d0 --- /dev/null +++ b/DllImportGenerator/DllImportGenerator.IntegrationTests/DelegateTests.cs @@ -0,0 +1,55 @@ +using System.Runtime.InteropServices; + +using Xunit; + +namespace DllImportGenerator.IntegrationTests +{ + partial class NativeExportsNE + { + public delegate void VoidVoid(); + + [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "invoke_callback_after_gc")] + public static partial void InvokeAfterGC(VoidVoid cb); + + public delegate int IntIntInt(int a, int b); + + [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "invoke_callback_blittable_args")] + public static partial int InvokeWithBlittableArgument(IntIntInt cb, int a, int b); + } + + public class DelegateTests + { + [Fact] + public void DelegateIsKeptAliveDuringCall() + { + bool wasCalled = false; + NativeExportsNE.InvokeAfterGC(new NativeExportsNE.VoidVoid(Callback)); + Assert.True(wasCalled); + + void Callback() + { + wasCalled = true; + } + } + + [Fact] + public void DelegateIsCalledWithArgumentsInOrder() + { + const int a = 100; + const int b = 50; + int result; + + result = NativeExportsNE.InvokeWithBlittableArgument(new NativeExportsNE.IntIntInt(Callback), a, b); + Assert.Equal(Callback(a, b), result); + + result = NativeExportsNE.InvokeWithBlittableArgument(new NativeExportsNE.IntIntInt(Callback), b, a); + Assert.Equal(Callback(b, a), result); + + static int Callback(int a, int b) + { + // Use a noncommutative operation to validate passed in order. + return a - b; + } + } + } +} diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/BoolMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/BoolMarshaller.cs index a0416bbf730c..10d67e9aa761 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/BoolMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/BoolMarshaller.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Diagnostics; -using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -14,12 +13,14 @@ internal abstract class BoolMarshallerBase : IMarshallingGenerator private readonly PredefinedTypeSyntax _nativeType; private readonly int _trueValue; private readonly int _falseValue; + private readonly bool _compareToTrue; - protected BoolMarshallerBase(PredefinedTypeSyntax nativeType, int trueValue, int falseValue) + protected BoolMarshallerBase(PredefinedTypeSyntax nativeType, int trueValue, int falseValue, bool compareToTrue) { _nativeType = nativeType; _trueValue = trueValue; _falseValue = falseValue; + _compareToTrue = compareToTrue; } public TypeSyntax AsNativeType(TypePositionInfo info) @@ -83,15 +84,19 @@ public IEnumerable Generate(TypePositionInfo info, StubCodeCont case StubCodeContext.Stage.Unmarshal: if (info.IsManagedReturnPosition || (info.IsByRef && info.RefKind != RefKind.In)) { - // = != 0; + // = == _trueValue; + // or + // = != _falseValue; + var (binaryOp, comparand) = _compareToTrue ? (SyntaxKind.EqualsExpression, _trueValue) : (SyntaxKind.NotEqualsExpression, _falseValue); + yield return ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, IdentifierName(managedIdentifier), BinaryExpression( - SyntaxKind.NotEqualsExpression, + binaryOp, IdentifierName(nativeIdentifier), - LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(_falseValue))))); + LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(comparand))))); } break; default: @@ -102,28 +107,47 @@ public IEnumerable Generate(TypePositionInfo info, StubCodeCont public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) => true; } - internal class CBoolMarshaller : BoolMarshallerBase + /// + /// Marshals a boolean value as 1 byte. + /// + /// + /// This boolean type is the natural size of a boolean in the CLR (ECMA-335 (III.1.1.2)). + /// + /// This is typically compatible with C99 + /// and C++, but those is implementation defined. + /// Consult your compiler specification. + /// + internal class ByteBoolMarshaller : BoolMarshallerBase { - public CBoolMarshaller() - : base(PredefinedType(Token(SyntaxKind.ByteKeyword)), trueValue: 1, falseValue: 0) + public ByteBoolMarshaller() + : base(PredefinedType(Token(SyntaxKind.ByteKeyword)), trueValue: 1, falseValue: 0, compareToTrue: false) { } } + /// + /// Marshals a boolean value as a 4-byte integer. + /// + /// + /// Corresponds to the definition of BOOL. + /// internal class WinBoolMarshaller : BoolMarshallerBase { public WinBoolMarshaller() - : base(PredefinedType(Token(SyntaxKind.IntKeyword)), trueValue: 1, falseValue: 0) + : base(PredefinedType(Token(SyntaxKind.IntKeyword)), trueValue: 1, falseValue: 0, compareToTrue: false) { } } - + + /// + /// Marshal a boolean value as a VARIANT_BOOL (Windows OLE/Automation type). + /// internal class VariantBoolMarshaller : BoolMarshallerBase { private const short VARIANT_TRUE = -1; private const short VARIANT_FALSE = 0; public VariantBoolMarshaller() - : base(PredefinedType(Token(SyntaxKind.ShortKeyword)), VARIANT_TRUE, VARIANT_FALSE) + : base(PredefinedType(Token(SyntaxKind.ShortKeyword)), trueValue: VARIANT_TRUE, falseValue: VARIANT_FALSE, compareToTrue: true) { } } diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/DelegateMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/DelegateMarshaller.cs index bf2669ecb979..060f54c47776 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/DelegateMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/DelegateMarshaller.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -7,7 +6,7 @@ namespace Microsoft.Interop { - class DelegateMarshaller : IMarshallingGenerator + internal class DelegateMarshaller : IMarshallingGenerator { public TypeSyntax AsNativeType(TypePositionInfo info) { diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/Forwarder.cs b/DllImportGenerator/DllImportGenerator/Marshalling/Forwarder.cs index 53601890de5b..00f307e9a324 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/Forwarder.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/Forwarder.cs @@ -6,7 +6,7 @@ namespace Microsoft.Interop { - class Forwarder : IMarshallingGenerator + internal class Forwarder : IMarshallingGenerator { public TypeSyntax AsNativeType(TypePositionInfo info) { diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs b/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs index 60ef933582a8..cebc19932604 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs @@ -58,7 +58,7 @@ internal interface IMarshallingGenerator internal class MarshallingGenerators { - public static readonly CBoolMarshaller CBool = new CBoolMarshaller(); + public static readonly ByteBoolMarshaller ByteBool = new ByteBoolMarshaller(); public static readonly WinBoolMarshaller WinBool = new WinBoolMarshaller(); public static readonly VariantBoolMarshaller VariantBool = new VariantBoolMarshaller(); public static readonly Forwarder Forwarder = new Forwarder(); @@ -97,12 +97,12 @@ public static bool TryCreate(TypePositionInfo info, StubCodeContext context, out return true; case { ManagedType: { SpecialType: SpecialType.System_Boolean }, MarshallingAttributeInfo: null }: - generator = CBool; + generator = WinBool; // [Compat] Matching the default for the built-in runtime marshallers. return true; case { ManagedType: { SpecialType: SpecialType.System_Boolean }, MarshallingAttributeInfo: MarshalAsInfo { UnmanagedType: UnmanagedType.I1 or UnmanagedType.U1 } }: - generator = CBool; + generator = ByteBool; return true; - case { ManagedType: { SpecialType: SpecialType.System_Boolean }, MarshallingAttributeInfo: MarshalAsInfo { UnmanagedType: UnmanagedType.I4 or UnmanagedType.U4 } }: + case { ManagedType: { SpecialType: SpecialType.System_Boolean }, MarshallingAttributeInfo: MarshalAsInfo { UnmanagedType: UnmanagedType.I4 or UnmanagedType.U4 or UnmanagedType.Bool } }: generator = WinBool; return true; case { ManagedType: { SpecialType: SpecialType.System_Boolean }, MarshallingAttributeInfo: MarshalAsInfo { UnmanagedType: UnmanagedType.VariantBool } }: diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/SafeHandleMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/SafeHandleMarshaller.cs index 041472a7446a..c9b7972fe7e9 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/SafeHandleMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/SafeHandleMarshaller.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -8,7 +7,7 @@ namespace Microsoft.Interop { - class SafeHandleMarshaller : IMarshallingGenerator + internal class SafeHandleMarshaller : IMarshallingGenerator { public TypeSyntax AsNativeType(TypePositionInfo info) { diff --git a/DllImportGenerator/TestAssets/NativeExports/Booleans.cs b/DllImportGenerator/TestAssets/NativeExports/Booleans.cs new file mode 100644 index 000000000000..ac729a128821 --- /dev/null +++ b/DllImportGenerator/TestAssets/NativeExports/Booleans.cs @@ -0,0 +1,31 @@ +using System.Runtime.InteropServices; + +namespace NativeExports +{ + public static unsafe class Booleans + { + [UnmanagedCallersOnly(EntryPoint = "bytebool_return_as_uint")] + public static uint ReturnByteAsUInt(byte input) + { + return input; + } + + [UnmanagedCallersOnly(EntryPoint = "variantbool_return_as_uint")] + public static uint ReturnUShortAsUInt(ushort input) + { + return input; + } + + [UnmanagedCallersOnly(EntryPoint = "bool_return_as_uint")] + public static uint ReturnUIntAsUInt(uint input) + { + return input; + } + + [UnmanagedCallersOnly(EntryPoint = "bool_return_as_refuint")] + public static void ReturnUIntAsRefUInt(uint input, uint* res) + { + *res = input; + } + } +} diff --git a/DllImportGenerator/TestAssets/NativeExports/DelegatesAndFunctionPointers.cs b/DllImportGenerator/TestAssets/NativeExports/DelegatesAndFunctionPointers.cs new file mode 100644 index 000000000000..9aa19952ea31 --- /dev/null +++ b/DllImportGenerator/TestAssets/NativeExports/DelegatesAndFunctionPointers.cs @@ -0,0 +1,30 @@ +using System; +using System.Runtime.InteropServices; + +namespace NativeExports +{ + public static unsafe class DelegatesAndFunctionPointers + { + [UnmanagedCallersOnly(EntryPoint = "invoke_callback_after_gc")] + public static void InvokeCallbackAfterGCCollect(delegate* unmanaged fptr) + { + // We are at the mercy of the GC to verify our delegate has been retain + // across the native function call. This is a best effort validation. + GC.Collect(); + GC.Collect(); + GC.Collect(); + GC.Collect(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + // If the corresponding Delegate was collected, the runtime will rudely abort. + fptr(); + } + + [UnmanagedCallersOnly(EntryPoint = "invoke_callback_blittable_args")] + public static int InvokeCallbackWithBlittableArgument(delegate* unmanaged fptr, int a, int b) + { + return fptr(a, b); + } + } +} diff --git a/DllImportGenerator/TestAssets/NativeExports/NativeExports.csproj b/DllImportGenerator/TestAssets/NativeExports/NativeExports.csproj index 70e5b074e8b8..e72f43fdabdf 100644 --- a/DllImportGenerator/TestAssets/NativeExports/NativeExports.csproj +++ b/DllImportGenerator/TestAssets/NativeExports/NativeExports.csproj @@ -8,28 +8,7 @@ - + - - - - - - - -