Skip to content

Commit

Permalink
Boolean and Delegate tests (#152)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
AaronRobinsonMSFT authored Oct 9, 2020
1 parent 778ecfa commit 6c980df
Show file tree
Hide file tree
Showing 10 changed files with 301 additions and 44 deletions.
140 changes: 140 additions & 0 deletions DllImportGenerator/DllImportGenerator.IntegrationTests/BooleanTests.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
46 changes: 35 additions & 11 deletions DllImportGenerator/DllImportGenerator/Marshalling/BoolMarshaller.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand All @@ -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)
Expand Down Expand Up @@ -83,15 +84,19 @@ public IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeCont
case StubCodeContext.Stage.Unmarshal:
if (info.IsManagedReturnPosition || (info.IsByRef && info.RefKind != RefKind.In))
{
// <managedIdentifier> = <nativeIdentifier> != 0;
// <managedIdentifier> = <nativeIdentifier> == _trueValue;
// or
// <managedIdentifier> = <nativeIdentifier> != _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:
Expand All @@ -102,28 +107,47 @@ public IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeCont
public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) => true;
}

internal class CBoolMarshaller : BoolMarshallerBase
/// <summary>
/// Marshals a boolean value as 1 byte.
/// </summary>
/// <remarks>
/// This boolean type is the natural size of a boolean in the CLR (<see href="https://www.ecma-international.org/publications/standards/Ecma-335.htm">ECMA-335 (III.1.1.2)</see>).
///
/// This is typically compatible with <see href="https://en.cppreference.com/w/c/types/boolean">C99</see>
/// and <see href="https://en.cppreference.com/w/cpp/language/types">C++</see>, but those is implementation defined.
/// Consult your compiler specification.
/// </remarks>
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)
{
}
}

/// <summary>
/// Marshals a boolean value as a 4-byte integer.
/// </summary>
/// <remarks>
/// Corresponds to the definition of <see href="https://docs.microsoft.com/windows/win32/winprog/windows-data-types">BOOL</see>.
/// </remarks>
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)
{
}
}


/// <summary>
/// Marshal a boolean value as a VARIANT_BOOL (Windows OLE/Automation type).
/// </summary>
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)
{
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace Microsoft.Interop
{
class DelegateMarshaller : IMarshallingGenerator
internal class DelegateMarshaller : IMarshallingGenerator
{
public TypeSyntax AsNativeType(TypePositionInfo info)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Microsoft.Interop
{
class Forwarder : IMarshallingGenerator
internal class Forwarder : IMarshallingGenerator
{
public TypeSyntax AsNativeType(TypePositionInfo info)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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 } }:
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -8,7 +7,7 @@

namespace Microsoft.Interop
{
class SafeHandleMarshaller : IMarshallingGenerator
internal class SafeHandleMarshaller : IMarshallingGenerator
{
public TypeSyntax AsNativeType(TypePositionInfo info)
{
Expand Down
31 changes: 31 additions & 0 deletions DllImportGenerator/TestAssets/NativeExports/Booleans.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Loading

0 comments on commit 6c980df

Please sign in to comment.