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

Adding some more improvements to the PInvokeGenerator #58

Merged
merged 7 commits into from
May 31, 2019
2 changes: 1 addition & 1 deletion ClangSharp.PInvokeGenerator.Test/PInvokeGeneratorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ private async Task ValidateGeneratedBindings(string inputContents, string expect
var unsavedInputFile = CXUnsavedFile.Create(DefaultInputFileName, inputContents);
var config = new PInvokeGeneratorConfiguration(DefaultLibraryPath, DefaultNamespaceName, Path.GetRandomFileName(), configOptions, excludedNames, methodClassName: null, methodPrefixToStrip: null, remappedNames);

using (var pinvokeGenerator = new PInvokeGenerator(config, ((path) => (outputStream, leaveOpen: true))))
using (var pinvokeGenerator = new PInvokeGenerator(config, (path) => outputStream))
using (var translationUnitHandle = CXTranslationUnit.Parse(pinvokeGenerator.IndexHandle, DefaultInputFileName, DefaultClangCommandLineArgs, new CXUnsavedFile[] { unsavedInputFile }, DefaultTranslationUnitFlags))
{
pinvokeGenerator.GenerateBindings(translationUnitHandle);
Expand Down
155 changes: 155 additions & 0 deletions ClangSharp.PInvokeGenerator.Test/StructDeclarationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,78 @@ public async Task ExcludeTest()
await ValidateGeneratedBindings(inputContents, expectedOutputContents, excludedNames);
}

[Theory]
[InlineData("unsigned char", "byte")]
[InlineData("double", "double")]
[InlineData("short", "short")]
[InlineData("int", "int")]
[InlineData("long long", "long")]
[InlineData("char", "sbyte")]
[InlineData("float", "float")]
[InlineData("unsigned short", "ushort")]
[InlineData("unsigned int", "uint")]
[InlineData("unsigned long long", "ulong")]
public async Task FixedSizedBufferNonPrimitiveTest(string nativeType, string expectedManagedType)
{
var inputContents = $@"struct MyStruct
{{
{nativeType} value;
}};

struct MyOtherStruct
{{
MyStruct c[3];
}};
";

var expectedOutputContents = $@"namespace ClangSharp.Test
{{
public partial struct MyStruct
{{
public {expectedManagedType} value;
}}

public partial struct MyOtherStruct
{{
public MyStruct c0; public MyStruct c1; public MyStruct c2;
Copy link
Member Author

Choose a reason for hiding this comment

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

This covers the existing generation logic for fixed-sized buffers.

}}
}}
";

await ValidateGeneratedBindings(inputContents, expectedOutputContents);
}

[Theory]
[InlineData("unsigned char", "byte")]
[InlineData("double", "double")]
[InlineData("short", "short")]
[InlineData("int", "int")]
[InlineData("long long", "long")]
[InlineData("char", "sbyte")]
[InlineData("float", "float")]
[InlineData("unsigned short", "ushort")]
[InlineData("unsigned int", "uint")]
[InlineData("unsigned long long", "ulong")]
public async Task FixedSizedBufferPrimitiveTest(string nativeType, string expectedManagedType)
{
var inputContents = $@"struct MyStruct
{{
{nativeType} c[3];
}};
";

var expectedOutputContents = $@"namespace ClangSharp.Test
{{
public partial struct MyStruct
{{
public {expectedManagedType} c0; public {expectedManagedType} c1; public {expectedManagedType} c2;
}}
}}
";

await ValidateGeneratedBindings(inputContents, expectedOutputContents);
}

[Theory]
[InlineData("unsigned char", "byte")]
[InlineData("double", "double")]
Expand Down Expand Up @@ -218,6 +290,89 @@ public partial struct MyStruct
await ValidateGeneratedBindings(inputContents, expectedOutputContents);
}

[Theory]
[InlineData("unsigned char", "byte")]
[InlineData("double", "double")]
[InlineData("short", "short")]
[InlineData("int", "int")]
[InlineData("long long", "long")]
[InlineData("char", "sbyte")]
[InlineData("float", "float")]
[InlineData("unsigned short", "ushort")]
[InlineData("unsigned int", "uint")]
[InlineData("unsigned long long", "ulong")]
public async Task UnsafeFixedSizedBufferNonPrimitiveTest(string nativeType, string expectedManagedType)
{
var inputContents = $@"struct MyStruct
{{
{nativeType} value;
}};

struct MyOtherStruct
{{
MyStruct c[3];
}};
";

var expectedOutputContents = $@"using System.Runtime.InteropServices;

namespace ClangSharp.Test
{{
public unsafe partial struct MyStruct
{{
public {expectedManagedType} value;
}}

public unsafe partial struct MyOtherStruct
{{
public _c_e__FixedBuffer c;

public partial struct _c_e__FixedBuffer
{{
private MyStruct e0;
private MyStruct e1;
private MyStruct e2;

public ref MyStruct this[int index] => ref MemoryMarshal.CreateSpan(ref e0, 3)[index];
Copy link
Member Author

Choose a reason for hiding this comment

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

This is the fallback generation (still for unsafe code) for when C# doesn't support the type for "proper" fixed-sized buffers.

It takes advantage of Span and ref returns to avoid pinning and allow "normal" use of the API.

}}
}}
}}
";

await ValidateUnsafeGeneratedBindings(inputContents, expectedOutputContents);
}

[Theory]
[InlineData("unsigned char", "byte")]
[InlineData("double", "double")]
[InlineData("short", "short")]
[InlineData("int", "int")]
[InlineData("long long", "long")]
[InlineData("char", "sbyte")]
[InlineData("float", "float")]
[InlineData("unsigned short", "ushort")]
[InlineData("unsigned int", "uint")]
[InlineData("unsigned long long", "ulong")]
public async Task UnsafeFixedSizedBufferPrimitiveTest(string nativeType, string expectedManagedType)
{
var inputContents = $@"struct MyStruct
{{
{nativeType} c[3];
}};
";

var expectedOutputContents = $@"namespace ClangSharp.Test
{{
public unsafe partial struct MyStruct
{{
public fixed {expectedManagedType} c[3];
Copy link
Member Author

Choose a reason for hiding this comment

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

This is the "ideal" generation and is what is used when the type is one of the types C# supports for fixed-sized buffers.

}}
}}
";

await ValidateUnsafeGeneratedBindings(inputContents, expectedOutputContents);
}

[Theory]
[InlineData("unsigned char", "byte")]
[InlineData("double", "double")]
Expand Down
37 changes: 37 additions & 0 deletions ClangSharp.PInvokeGenerator.Test/VarDeclarationTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Threading.Tasks;
using Xunit;

namespace ClangSharp.Test
{
public sealed class VarDeclarationTest : PInvokeGeneratorTest
{
[Theory]
[InlineData("unsigned char", "byte")]
[InlineData("double", "double")]
[InlineData("short", "short")]
[InlineData("int", "int")]
[InlineData("long long", "long")]
[InlineData("char", "sbyte")]
[InlineData("float", "float")]
[InlineData("unsigned short", "ushort")]
[InlineData("unsigned int", "uint")]
[InlineData("unsigned long long", "ulong")]
public async Task BasicTest(string nativeType, string expectedManagedType)
{
var inputContents = $@"{nativeType} MyVariable;";

var expectedOutputContents = $@"namespace ClangSharp.Test
{{
public static partial class Methods
{{
private const string libraryPath = ""ClangSharpPInvokeGenerator"";

// public static extern {expectedManagedType} MyVariable;
Copy link
Member Author

Choose a reason for hiding this comment

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

As mentioned, ideally this would be an IntPtr or a T* (depending on if safe or unsafe code) and would use the native library loader APIs (only available in .NET Core 3 and higher) to initialize the handle/address correctly.

I'll likely fix this up in a future PR, but for now, it lets consumers more easily see when these variables exist and take action themselves.

}}
}}
";

await ValidateGeneratedBindings(inputContents, expectedOutputContents);
}
}
}
7 changes: 7 additions & 0 deletions ClangSharp.PInvokeGenerator/Cursors/Decls/RecordDecl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ namespace ClangSharp
{
internal class RecordDecl : TagDecl
{
private readonly List<FieldDecl> _constantArrays = new List<FieldDecl>();
private readonly List<FieldDecl> _fields = new List<FieldDecl>();

public RecordDecl(CXCursor handle, Cursor parent) : base(handle, parent)
Expand All @@ -16,6 +17,8 @@ public RecordDecl(CXCursor handle, Cursor parent) : base(handle, parent)

public bool IsUnion => Kind == CXCursorKind.CXCursor_UnionDecl;

public IReadOnlyList<FieldDecl> ConstantArrays => _constantArrays;

public IReadOnlyList<FieldDecl> Fields => _fields;

protected override Decl GetOrAddDecl(CXCursor childHandle)
Expand All @@ -24,6 +27,10 @@ protected override Decl GetOrAddDecl(CXCursor childHandle)

if (decl is FieldDecl fieldDecl)
{
if (fieldDecl.Type.Kind == CXTypeKind.CXType_ConstantArray)
{
_constantArrays.Add(fieldDecl);
}
_fields.Add(fieldDecl);
}

Expand Down
22 changes: 22 additions & 0 deletions ClangSharp.PInvokeGenerator/Extensions/IReadOnlyListExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Collections.Generic;

namespace ClangSharp
{
internal static class IReadOnlyListExtensions
{
public static int IndexOf<T>(this IReadOnlyList<T> self, T value)
{
var comparer = EqualityComparer<T>.Default;

for (int i = 0; i < self.Count; i++)
{
if (comparer.Equals(self[i], value))
{
return i;
}
}

return -1;
}
}
}
Loading