diff --git a/.gitignore b/.gitignore index b3433c8..7539c03 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ obj/ /artifacts/ src/c/tests/**/ffi/*.json src/c/tests/**/ffi-x/*.json + +# Native library files +lib/*.* diff --git a/src/c/tests/functions/function_implicit_enum/config.json b/src/c/tests/functions/function_implicit_enum/config.json new file mode 100644 index 0000000..307eaaf --- /dev/null +++ b/src/c/tests/functions/function_implicit_enum/config.json @@ -0,0 +1,30 @@ +{ + "inputFilePath": "./main.c", + "userIncludeDirectories": [ + "../../../production/ffi_helper/include" + ], + "ignoredIncludeFiles": [ + "../../../production/ffi_helper/include/ffi_helper.h" + ], + "includedNames": [ + "enum_implicit" + ], + "targetPlatforms": { + "windows": { + "i686-pc-windows-msvc": {}, + "x86_64-pc-windows-msvc": {}, + "aarch64-pc-windows-msvc": {} + }, + "macos": { + "i686-apple-darwin": {}, + "aarch64-apple-darwin": {}, + "x86_64-apple-darwin": {}, + "aarch64-apple-ios": {} + }, + "linux": { + "i686-unknown-linux-gnu": {}, + "x86_64-unknown-linux-gnu": {}, + "aarch64-unknown-linux-gnu": {} + } + } +} diff --git a/src/c/tests/functions/function_implicit_enum/main.c b/src/c/tests/functions/function_implicit_enum/main.c new file mode 100644 index 0000000..939b91f --- /dev/null +++ b/src/c/tests/functions/function_implicit_enum/main.c @@ -0,0 +1,12 @@ +#include +#include "ffi_helper.h" + +enum enum_implicit { + ENUM_IMPLICIT_VALUE0 = 0, + ENUM_IMPLICIT_VALUE1 = 255 +} enum_implicit; + +FFI_API_DECL int function_implicit_enum(int value) +{ + return ENUM_IMPLICIT_VALUE1; +} diff --git a/src/cs/production/c2ffi.Data/Generated/System.Text.Json.SourceGeneration/System.Text.Json.SourceGeneration.JsonSourceGenerator/JsonSerializerContextCFfiCrossPlatform.g.cs b/src/cs/production/c2ffi.Data/Generated/System.Text.Json.SourceGeneration/System.Text.Json.SourceGeneration.JsonSourceGenerator/JsonSerializerContextCFfiCrossPlatform.g.cs index c72dfe1..746a71b 100644 --- a/src/cs/production/c2ffi.Data/Generated/System.Text.Json.SourceGeneration/System.Text.Json.SourceGeneration.JsonSourceGenerator/JsonSerializerContextCFfiCrossPlatform.g.cs +++ b/src/cs/production/c2ffi.Data/Generated/System.Text.Json.SourceGeneration/System.Text.Json.SourceGeneration.JsonSourceGenerator/JsonSerializerContextCFfiCrossPlatform.g.cs @@ -8,7 +8,7 @@ namespace c2ffi.Data.Serialization { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Text.Json.SourceGeneration", "8.0.10.11423")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Text.Json.SourceGeneration", "8.0.10.31311")] public partial class JsonSerializerContextCFfiCrossPlatform { private readonly static global::System.Text.Json.JsonSerializerOptions s_defaultOptions = new() diff --git a/src/cs/production/c2ffi.Data/Generated/System.Text.Json.SourceGeneration/System.Text.Json.SourceGeneration.JsonSourceGenerator/JsonSerializerContextCFfiTargetPlatform.g.cs b/src/cs/production/c2ffi.Data/Generated/System.Text.Json.SourceGeneration/System.Text.Json.SourceGeneration.JsonSourceGenerator/JsonSerializerContextCFfiTargetPlatform.g.cs index 0477062..61ea06b 100644 --- a/src/cs/production/c2ffi.Data/Generated/System.Text.Json.SourceGeneration/System.Text.Json.SourceGeneration.JsonSourceGenerator/JsonSerializerContextCFfiTargetPlatform.g.cs +++ b/src/cs/production/c2ffi.Data/Generated/System.Text.Json.SourceGeneration/System.Text.Json.SourceGeneration.JsonSourceGenerator/JsonSerializerContextCFfiTargetPlatform.g.cs @@ -8,7 +8,7 @@ namespace c2ffi.Data.Serialization { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Text.Json.SourceGeneration", "8.0.10.11423")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Text.Json.SourceGeneration", "8.0.10.31311")] public partial class JsonSerializerContextCFfiTargetPlatform { private readonly static global::System.Text.Json.JsonSerializerOptions s_defaultOptions = new() diff --git a/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/Context/ExploreContext.cs b/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/Context/ExploreContext.cs index c037564..3667260 100644 --- a/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/Context/ExploreContext.cs +++ b/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/Context/ExploreContext.cs @@ -125,6 +125,24 @@ public void Dispose() ParseContext.Dispose(); } + public string GetFieldName(clang.CXCursor clangCursor) + { + if (clangCursor.kind != clang.CXCursorKind.CXCursor_FieldDecl) + { + return string.Empty; + } + + var name = clangCursor.Spelling(); + + // NOTE: In newer versions of Clang (specifically found on 18.1), getting the name of the field has changed if it anonymous. Old behavior was to return empty string. + if (name.Contains("::(anonymous at", StringComparison.OrdinalIgnoreCase)) + { + return string.Empty; + } + + return name; + } + private CType VisitTypeInternal( CNodeKind nodeKind, string typeName, diff --git a/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/ExploreNodeInfo.cs b/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/ExploreNodeInfo.cs index b4037d5..cb2b18b 100644 --- a/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/ExploreNodeInfo.cs +++ b/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/ExploreNodeInfo.cs @@ -28,6 +28,16 @@ public sealed class ExploreNodeInfo public override string ToString() { - return Name; + if (!string.IsNullOrEmpty(Name)) + { + return Name; + } + + if (!string.IsNullOrEmpty(TypeName)) + { + return TypeName; + } + + return "???"; } } diff --git a/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/Explorer.cs b/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/Explorer.cs index af899bd..d7d7f92 100644 --- a/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/Explorer.cs +++ b/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/Explorer.cs @@ -66,10 +66,18 @@ public CFfiTargetPlatform ExtractFfi( private void VisitTranslationUnit(ExploreContext context, ParseContext parseContext) { LogVisitingTranslationUnit(parseContext.FilePath); - VisitIncludes(context, parseContext); + + var isMultipleHeaders = !context.ParseContext.ExtractInput.IsSingleHeader; + if (isMultipleHeaders) + { + VisitIncludes(context, parseContext); + } + VisitFunctions(context, parseContext); VisitVariables(context, parseContext); VisitMacroObjects(context, parseContext); + VisitExplicitIncludedNames(context, parseContext); + LogVisitedTranslationUnit(parseContext.FilePath); } @@ -118,6 +126,33 @@ private void VisitMacroObject(ExploreContext context, clang.CXCursor clangCursor context.TryEnqueueNode(info); } + private void VisitExplicitIncludedNames(ExploreContext context, ParseContext parseContext) + { + var cursors = parseContext.GetExplicitlyIncludedNamedCursors(); + foreach (var cursor in cursors) + { + VisitExplicitlyIncludedName(context, cursor); + } + } + + private void VisitExplicitlyIncludedName(ExploreContext context, clang.CXCursor clangCursor) + { + var nodeKind = clangCursor.kind switch + { + clang.CXCursorKind.CXCursor_EnumDecl => CNodeKind.Enum, + _ => CNodeKind.Unknown + }; + + if (nodeKind == CNodeKind.Unknown) + { + // TODO: Add more allowed kinds to explicitly included names + return; + } + + var info = context.CreateTopLevelNodeInfo(nodeKind, clangCursor); + context.TryEnqueueNode(info); + } + private void VisitIncludes(ExploreContext context, ParseContext parseContext) { var includeCursors = parseContext.GetIncludes(); diff --git a/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/NodeExplorers/StructExplorer.cs b/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/NodeExplorers/StructExplorer.cs index 6c5dd90..7ade5b6 100644 --- a/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/NodeExplorers/StructExplorer.cs +++ b/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/NodeExplorers/StructExplorer.cs @@ -71,7 +71,7 @@ private CRecordField StructField( ExploreNodeInfo structInfo, CXCursor clangCursor) { - var fieldName = clangCursor.Spelling(); + var fieldName = context.GetFieldName(clangCursor); var clangType = clang_getCursorType(clangCursor); var location = context.ParseContext.Location(clangCursor); var type = context.VisitType(clangType, structInfo); diff --git a/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/NodeExplorers/UnionExplorer.cs b/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/NodeExplorers/UnionExplorer.cs index 4da9b3e..137aa1a 100644 --- a/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/NodeExplorers/UnionExplorer.cs +++ b/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Explore/NodeExplorers/UnionExplorer.cs @@ -68,7 +68,7 @@ private CRecordField UnionField( CXCursor clangCursor, ExploreNodeInfo parentInfo) { - var name = clangCursor.Spelling(); + var name = context.GetFieldName(clangCursor); var clangType = clang_getCursorType(clangCursor); var location = context.ParseContext.Location(clangCursor); var type = context.VisitType(clangType, parentInfo); diff --git a/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Parse/ClangTranslationUnitParser.cs b/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Parse/ClangTranslationUnitParser.cs index ebf5a09..3131611 100644 --- a/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Parse/ClangTranslationUnitParser.cs +++ b/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Parse/ClangTranslationUnitParser.cs @@ -68,7 +68,8 @@ private static unsafe bool TryParseTranslationUnit( ImmutableArray commandLineArgs, out CXTranslationUnit translationUnit, bool skipFunctionBodies = true, - bool keepGoing = false) + bool keepGoing = false, + bool isSingleHeader = false) { // ReSharper disable BitwiseOperatorOnEnumWithoutFlags uint options = 0x0 | @@ -79,6 +80,11 @@ private static unsafe bool TryParseTranslationUnit( 0x4000 | // CXTranslationUnit_IgnoreNonErrorsFromIncludedFiles 0x0; + if (isSingleHeader) + { + options |= 0x400; // CXTranslationUnit_SingleFileParse + } + if (skipFunctionBodies) { options |= 0x40; // CXTranslationUnit_SkipFunctionBodies diff --git a/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Parse/ParseContext.cs b/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Parse/ParseContext.cs index 217468e..3869d88 100644 --- a/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Parse/ParseContext.cs +++ b/src/cs/production/c2ffi.Tool/Commands/Extract/Domain/Parse/ParseContext.cs @@ -199,7 +199,24 @@ static bool IsExternalVariable(clang.CXCursor child, clang.CXCursor parent) return false; } - return child.kind == clang.CXCursorKind.CXCursor_VarDecl && IsExternal(child); + var isExternal = IsExternal(child); + var isVariable = child.kind == clang.CXCursorKind.CXCursor_VarDecl; + return isVariable && isExternal; + } + } + + public ImmutableArray GetExplicitlyIncludedNamedCursors() + { + var translationUnitCursor = clang.clang_getTranslationUnitCursor(_translationUnit); + var result = translationUnitCursor.GetDescendents( + (cursor, parent) => IsExplicitlyIncludedName(cursor, ExtractInput.IncludedNames)); + return result; + + static bool IsExplicitlyIncludedName(clang.CXCursor cursor, ImmutableHashSet names) + { + var name = cursor.Spelling(); + var isIncluded = names.Contains(name); + return isIncluded; } } @@ -278,12 +295,6 @@ private void TryReleaseUnmanagedResources() private static bool IsExternal(clang.CXCursor cursor) { - var spelling = cursor.Spelling(); - if (spelling == "SDL_malloc") - { - Console.WriteLine(); - } - var linkage = clang.clang_getCursorLinkage(cursor); if (linkage == clang.CXLinkageKind.CXLinkage_Invalid) { diff --git a/src/cs/production/c2ffi.Tool/Commands/Extract/Input/ExtractInputSanitizer.cs b/src/cs/production/c2ffi.Tool/Commands/Extract/Input/ExtractInputSanitizer.cs index 7354e5f..7f3a23e 100644 --- a/src/cs/production/c2ffi.Tool/Commands/Extract/Input/ExtractInputSanitizer.cs +++ b/src/cs/production/c2ffi.Tool/Commands/Extract/Input/ExtractInputSanitizer.cs @@ -126,6 +126,8 @@ private ExtractTargetPlatformInput SanitizeTargetPlatformInput( MacroObjectDefines = ClangDefines(input, targetPlatformInput), AdditionalArguments = ClangArguments(targetPlatformInput), IsEnabledFindSystemHeaders = input.IsEnabledAutomaticallyFindSystemHeaders ?? true, + IsSingleHeader = input.IsSingleHeader ?? false, + IncludedNames = IncludedNames(input), IgnoredMacroObjectsRegexes = IgnoredMacroObjects(input), IgnoredVariableRegexes = IgnoredVariables(input), IgnoredFunctionRegexes = IgnoredFunctions(input) @@ -194,6 +196,11 @@ private ImmutableArray IgnoredFunctions(UnsanitizedExtractInput input) return SanitizeRegexes(input.IgnoredFunctions); } + private ImmutableHashSet IncludedNames(UnsanitizedExtractInput input) + { + return SanitizeStrings(input.IncludedNames).ToImmutableHashSet(); + } + private string SanitizeOutputDirectoryPath( string? outputDirectoryPath, string targetPlatformString) diff --git a/src/cs/production/c2ffi.Tool/Commands/Extract/Input/Sanitized/ExtractTargetPlatformInput.cs b/src/cs/production/c2ffi.Tool/Commands/Extract/Input/Sanitized/ExtractTargetPlatformInput.cs index 9a52d88..6d0098e 100644 --- a/src/cs/production/c2ffi.Tool/Commands/Extract/Input/Sanitized/ExtractTargetPlatformInput.cs +++ b/src/cs/production/c2ffi.Tool/Commands/Extract/Input/Sanitized/ExtractTargetPlatformInput.cs @@ -3,6 +3,7 @@ using System.Collections.Immutable; using System.Text.RegularExpressions; +using bottlenoselabs; using c2ffi.Data; namespace c2ffi.Tool.Commands.Extract.Input.Sanitized; @@ -25,6 +26,10 @@ public sealed class ExtractTargetPlatformInput public bool IsEnabledFindSystemHeaders { get; init; } + public bool IsSingleHeader { get; init; } + + public ImmutableHashSet IncludedNames { get; init; } = ImmutableHashSet.Empty; + public ImmutableArray IgnoredMacroObjectsRegexes { get; init; } = ImmutableArray.Empty; public ImmutableArray IgnoredVariableRegexes { get; init; } = ImmutableArray.Empty; diff --git a/src/cs/production/c2ffi.Tool/Commands/Extract/Input/Unsanitized/UnsanitizedExtractInput.cs b/src/cs/production/c2ffi.Tool/Commands/Extract/Input/Unsanitized/UnsanitizedExtractInput.cs index da86686..00a08a9 100644 --- a/src/cs/production/c2ffi.Tool/Commands/Extract/Input/Unsanitized/UnsanitizedExtractInput.cs +++ b/src/cs/production/c2ffi.Tool/Commands/Extract/Input/Unsanitized/UnsanitizedExtractInput.cs @@ -54,45 +54,6 @@ public sealed class UnsanitizedExtractInput : ToolUnsanitizedInput [JsonPropertyName("ignoredIncludeFiles")] public ImmutableArray? IgnoredIncludeFiles { get; set; } - /// - /// Gets or sets a value that determines whether to show the the path of header code locations with full paths - /// or relative paths. - /// - /// - /// - /// Default is false. Use true to use the full path for header locations. Use false to - /// show only relative file paths. - /// - /// - [JsonPropertyName("isEnabledLocationFullPaths")] - public bool? IsEnabledLocationFullPaths { get; set; } - - /// - /// Gets or sets a value that determines whether to include or exclude declarations (functions, enums, structs, - /// typedefs, etc) with a prefixed underscore that are assumed to be 'non public'. - /// - /// - /// - /// Default is false. Use true to include declarations with a prefixed underscore. Use - /// false to exclude declarations with a prefixed underscore. - /// - /// - [JsonPropertyName("isEnabledAllowNamesWithPrefixedUnderscore")] - public bool? IsEnabledAllowNamesWithPrefixedUnderscore { get; set; } - - /// - /// Gets or sets a value that determines whether to include or exclude system declarations (functions, enums, - /// typedefs, records, etc). - /// - /// - /// - /// Default is `false`. Use true to include system declarations. Use `false` to exclude system - /// declarations. - /// - /// - [JsonPropertyName("isEnabledSystemDeclarations")] - public bool? IsEnabledSystemDeclarations { get; set; } - /// /// Gets or sets a value that determines whether to automatically find and append the system headers for the /// target platform. @@ -107,15 +68,14 @@ public sealed class UnsanitizedExtractInput : ToolUnsanitizedInput public bool? IsEnabledAutomaticallyFindSystemHeaders { get; set; } /// - /// Gets or sets determines whether to parse only the top-level cursors which are externally visible, or all - /// top-level cursors. + /// Gets or sets a value that determines whether the C code is parsed as a single header or multiple headers. /// /// - /// Default is true. Use true to parse only top-level cursors which are externally visible. Use - /// false to parse all top-level cursors whether or not they are externally visible. + /// Default is false. Use true to parse the C code as a single header. Use false to parse + /// the C code as multiple headers. /// - [JsonPropertyName("isEnabledOnlyExternalTopLevelCursors")] - public bool? IsEnabledOnlyExternalTopLevelCursors { get; set; } + [JsonPropertyName("isSingleHeader")] + public bool? IsSingleHeader { get; set; } /// /// Gets or sets the cursor names to be treated as opaque types. @@ -153,4 +113,10 @@ public sealed class UnsanitizedExtractInput : ToolUnsanitizedInput /// [JsonPropertyName("appleFrameworks")] public ImmutableArray? AppleFrameworks { get; set; } + + /// + /// Gets or sets the name of enums that are explicitly allowed. + /// + [JsonPropertyName("includedNames")] + public ImmutableArray? IncludedNames { get; set; } } diff --git a/src/cs/production/c2ffi.Tool/c2ffi.Tool.csproj b/src/cs/production/c2ffi.Tool/c2ffi.Tool.csproj index d2a29ed..924b786 100644 --- a/src/cs/production/c2ffi.Tool/c2ffi.Tool.csproj +++ b/src/cs/production/c2ffi.Tool/c2ffi.Tool.csproj @@ -52,5 +52,13 @@ + + + + + libclang.dll + Always + + diff --git a/src/cs/tests/c2ffi.Tests.EndToEnd.Extract/Functions/function_implicit_enum/Test.cs b/src/cs/tests/c2ffi.Tests.EndToEnd.Extract/Functions/function_implicit_enum/Test.cs new file mode 100644 index 0000000..a3bc102 --- /dev/null +++ b/src/cs/tests/c2ffi.Tests.EndToEnd.Extract/Functions/function_implicit_enum/Test.cs @@ -0,0 +1,53 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +#pragma warning disable CA1707 + +namespace c2ffi.Tests.EndToEnd.Extract.Functions.function_implicit_enum; + +public class Test : ExtractFfiTest +{ + private const string FunctionName = "function_implicit_enum"; + + [Fact] + public void Function() + { + var ffis = GetTargetPlatformFfis( + $"src/c/tests/functions/{FunctionName}/config.json"); + Assert.True(ffis.Length > 0); + + foreach (var ffi in ffis) + { + FfiFunctionExists(ffi); + FfiEnumExists(ffi); + } + } + + private void FfiFunctionExists(CTestFfiTargetPlatform ffi) + { + var function = ffi.GetFunction(FunctionName); + function.CallingConvention.Should().Be("cdecl"); + + var returnType = function.ReturnType; + returnType.Name.Should().Be("int"); + returnType.NodeKind.Should().Be("primitive"); + returnType.SizeOf.Should().Be(4); + returnType.AlignOf.Should().Be(4); + returnType.InnerType.Should().BeNull(); + + function.Parameters.Should().HaveCount(1); + var parameter = function.Parameters[0]; + parameter.Name.Should().Be("value"); + parameter.Type.Name.Should().Be("int"); + parameter.Type.NodeKind.Should().Be("primitive"); + parameter.Type.SizeOf.Should().Be(4); + parameter.Type.AlignOf.Should().Be(4); + } + + private void FfiEnumExists(CTestFfiTargetPlatform ffi) + { + var @enum = ffi.GetEnum("enum_implicit"); + @enum.Values.Should().HaveCount(2); + @enum.SizeOf.Should().Be(4); + } +} diff --git a/src/cs/tests/c2ffi.Tests.EndToEnd.Merge/Functions/function_implicit_enum/Test.cs b/src/cs/tests/c2ffi.Tests.EndToEnd.Merge/Functions/function_implicit_enum/Test.cs new file mode 100644 index 0000000..78c6055 --- /dev/null +++ b/src/cs/tests/c2ffi.Tests.EndToEnd.Merge/Functions/function_implicit_enum/Test.cs @@ -0,0 +1,53 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +#pragma warning disable CA1707 + +namespace c2ffi.Tests.EndToEnd.Merge.Functions.function_implicit_enum; + +public class Test : MergeFfisTest +{ + private const string FunctionName = "function_implicit_enum"; + + [Fact] + public void Function() + { + var ffi = GetCrossPlatformFfi( + $"src/c/tests/functions/{FunctionName}/ffi"); + + FfiFunctionExists(ffi); + FfiEnumExists(ffi); + } + + private void FfiFunctionExists(CTestFfiCrossPlatform ffi) + { + var function = ffi.GetFunction(FunctionName); + function.CallingConvention.Should().Be("cdecl"); + + var returnType = function.ReturnType; + returnType.Name.Should().Be("int"); + returnType.NodeKind.Should().Be("primitive"); + returnType.SizeOf.Should().Be(4); + returnType.AlignOf.Should().Be(4); + returnType.InnerType.Should().BeNull(); + + function.Parameters.Should().HaveCount(1); + var parameter = function.Parameters[0]; + parameter.Name.Should().Be("value"); + parameter.Type.Name.Should().Be("int"); + parameter.Type.NodeKind.Should().Be("primitive"); + parameter.Type.SizeOf.Should().Be(4); + parameter.Type.AlignOf.Should().Be(4); + + var @enum = ffi.GetEnum("enum_implicit"); + @enum.Values.Should().HaveCount(2); + @enum.SizeOf.Should().Be(4); + } + + private void FfiEnumExists(CTestFfiCrossPlatform ffi) + { + var @enum = ffi.GetEnum("enum_implicit"); + @enum.Values.Should().HaveCount(2); + @enum.SizeOf.Should().Be(4); + } +}