diff --git a/docs/generating_csharp.md b/docs/generating_csharp.md index 7a50918e..fd7a36b7 100644 --- a/docs/generating_csharp.md +++ b/docs/generating_csharp.md @@ -182,6 +182,12 @@ Eventually we might add translation from the Doxygen comment annotations to thos This should be enough to at least display those comments in IDEs, but any `@...` doxygen tags will be left as is, instead of being translated into their proper XML form. +## Exposed structs and Unity's IL2CPP on WebAssembly + +Structs exposed via `--expose-as-struct` (see [the C docs](/docs/generating_c.md#expose-simple-structs-as-structs)) are emitted as blittable C# structs with `LayoutKind.Explicit` and explicit field offsets, except structs with a single field, which use `LayoutKind.Sequential`. + +The exception exists because of Unity's IL2CPP on WebAssembly. IL2CPP compiles explicit-layout structs to a union with padding, and on wasm32 Clang only passes and returns *single-element* structs as plain scalars, which such a union isn't. So for a one-field struct the IL2CPP-compiled call site would return it through a hidden pointer and pass it by pointer, while the C library returns and accepts a plain scalar (`wasm-ld` reports `function signature mismatch`, and the calls trap or read garbage at runtime). A sequential one-field struct compiles to a plain C struct, which has the same ABI as the C side. Structs with more fields don't have this problem, since both sides pass those indirectly. + ## Distributing the C# bindings as a Nuget package This is not a full explanation, but a rought outline of what you need to do. diff --git a/src/generators/csharp/generator.cpp b/src/generators/csharp/generator.cpp index b4de8111..b23b1ecf 100644 --- a/src/generators/csharp/generator.cpp +++ b/src/generators/csharp/generator.cpp @@ -5952,13 +5952,27 @@ namespace mrbind::CSharp WriteComment(file, comment); } + // Exposed structs with a single field use `LayoutKind.Sequential` instead of `LayoutKind.Explicit` (for one field both layouts are trivially the same). + // This is for Unity's IL2CPP on wasm32: it compiles explicit-layout structs to a union with padding, and Clang's wasm32 ABI only passes and returns + // *single-element* structs as plain scalars, which that union isn't. So with `Explicit` the IL2CPP-compiled call site returns a one-field struct + // through a hidden pointer and passes it by pointer, while the C side returns and accepts a plain scalar (`wasm-ld` warns about `function signature mismatch`, + // and the calls trap or read garbage). A sequential one-field struct compiles to a plain C struct, which matches. Multi-field structs are indirect on both sides anyway. + const bool exposed_struct_is_sequential = is_exposed_struct_by_value && std::ranges::count_if(class_desc.fields, [](const CInterop::ClassField &field){return !field.is_static;}) == 1; + // The struct attributes. if (is_exposed_struct_by_value) { - // There's also `Pack = ...` parameter. It looks related to alignment, but the docs say that it only affects - // the automatic field layout if that's enabled, and not the alignment of the entire struct. - // Instead I'm going to assume that it aligns by the largest field size, and check that below. - file.WriteString("[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = " + std::to_string(class_desc.size_and_alignment.value().size) + ")]\n"); + if (exposed_struct_is_sequential) + { + file.WriteString("[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]\n"); + } + else + { + // There's also `Pack = ...` parameter. It looks related to alignment, but the docs say that it only affects + // the automatic field layout if that's enabled, and not the alignment of the entire struct. + // Instead I'm going to assume that it aligns by the largest field size, and check that below. + file.WriteString("[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = " + std::to_string(class_desc.size_and_alignment.value().size) + ")]\n"); + } } // The class header. @@ -6619,7 +6633,8 @@ namespace mrbind::CSharp // Write the field by value, if we're in the by-value part. if (is_exposed_struct_by_value) { - const std::string offset_attr = "[System.Runtime.InteropServices.FieldOffset(" + std::to_string(field.layout.value().byte_offset) + ")]\n"; + // No offsets in sequential structs, `FieldOffset` is only allowed with `LayoutKind.Explicit`. + const std::string offset_attr = exposed_struct_is_sequential ? "" : "[System.Runtime.InteropServices.FieldOffset(" + std::to_string(field.layout.value().byte_offset) + ")]\n"; // Write the field itself. if (is_bool) diff --git a/test/input/MR/test_csharp.h b/test/input/MR/test_csharp.h index f2eef0cf..15becc15 100644 --- a/test/input/MR/test_csharp.h +++ b/test/input/MR/test_csharp.h @@ -1065,6 +1065,9 @@ namespace MR::CSharp int x; }; + // Single-field exposed structs use `LayoutKind.Sequential` in C#. This tests passing and returning one by value. + inline ExposedLayoutC test_exposed_c(ExposedLayoutC a, ExposedLayoutC b = {}) {a.x += b.x; return a;} + // Test various array members. struct ArrayMembers diff --git a/test/output_c/include/MR/test_csharp.h b/test/output_c/include/MR/test_csharp.h index 40486b2a..f28d043d 100644 --- a/test/output_c/include/MR/test_csharp.h +++ b/test/output_c/include/MR/test_csharp.h @@ -6312,6 +6312,11 @@ MR_C_API MR_CSharp_ExposedLayoutB MR_CSharp_ExposedLayoutB_Construct_1(const MR_ /// Parameter `_2` can not be null. It is a single object. MR_C_API bool MR_C_equal_MR_CSharp_ExposedLayoutB(const MR_CSharp_ExposedLayoutB *_1, const MR_CSharp_ExposedLayoutB *_2); +// Single-field exposed structs use `LayoutKind.Sequential` in C#. This tests passing and returning one by value. +/// Generated from function `MR::CSharp::test_exposed_c`. +/// Parameter `b` has a default argument: `{}`, pass a null pointer to use it. +MR_C_API MR_CSharp_ExposedLayoutC MR_CSharp_test_exposed_c(MR_CSharp_ExposedLayoutC a, const MR_CSharp_ExposedLayoutC *b); + /// Returns a pointer to a member variable of class `MR::CSharp::ArrayMembers` named `i`. /// Parameter `_this` can not be null. It is a single object. /// The returned pointer will never be null. It is non-owning, do NOT destroy it. diff --git a/test/output_c/source/MR/test_csharp.cpp b/test/output_c/source/MR/test_csharp.cpp index 65968fc7..6c0a21e0 100644 --- a/test/output_c/source/MR/test_csharp.cpp +++ b/test/output_c/source/MR/test_csharp.cpp @@ -9550,6 +9550,18 @@ bool MR_C_equal_MR_CSharp_ExposedLayoutB(const MR_CSharp_ExposedLayoutB *_1, con ) // MRBINDC_TRY } +MR_CSharp_ExposedLayoutC MR_CSharp_test_exposed_c(MR_CSharp_ExposedLayoutC a, const MR_CSharp_ExposedLayoutC *b) +{ + MRBINDC_TRY( + using namespace MR; + using namespace CSharp; + return MRBINDC_BIT_CAST((MR_CSharp_ExposedLayoutC), ::MR::CSharp::test_exposed_c( + MRBINDC_BIT_CAST((MR::CSharp::ExposedLayoutC), a), + (b ? MRBINDC_BIT_CAST((MR::CSharp::ExposedLayoutC), *b) : MR::CSharp::ExposedLayoutC(MR::CSharp::ExposedLayoutC{})) + )); + ) // MRBINDC_TRY +} + const int *MR_CSharp_ArrayMembers_Get_i(const MR_CSharp_ArrayMembers *_this) { return std::addressof(((_this ? void() : MRBINDC_THROW("Parameter `_this` can not be null.", void)), *(const MR::CSharp::ArrayMembers *)(_this)).i); diff --git a/test/output_c_fixed_typedefs/include/MR/test_csharp.h b/test/output_c_fixed_typedefs/include/MR/test_csharp.h index 4fd33589..729965c3 100644 --- a/test/output_c_fixed_typedefs/include/MR/test_csharp.h +++ b/test/output_c_fixed_typedefs/include/MR/test_csharp.h @@ -6128,6 +6128,11 @@ MR_C_API MR_CSharp_ExposedLayoutB MR_CSharp_ExposedLayoutB_Construct_1(const MR_ // Parameter `_2` can not be null. It is a single object. MR_C_API bool MR_C_equal_MR_CSharp_ExposedLayoutB(const MR_CSharp_ExposedLayoutB *_1, const MR_CSharp_ExposedLayoutB *_2); +// Single-field exposed structs use `LayoutKind.Sequential` in C#. This tests passing and returning one by value. +// Generated from function `MR::CSharp::test_exposed_c`. +// Parameter `b` has a default argument: `{}`, pass a null pointer to use it. +MR_C_API MR_CSharp_ExposedLayoutC MR_CSharp_test_exposed_c(MR_CSharp_ExposedLayoutC a, const MR_CSharp_ExposedLayoutC *b); + // Returns a pointer to a member variable of class `MR::CSharp::ArrayMembers` named `i`. // Parameter `_this` can not be null. It is a single object. // The returned pointer will never be null. It is non-owning, do NOT destroy it. diff --git a/test/output_c_fixed_typedefs/source/MR/test_csharp.cpp b/test/output_c_fixed_typedefs/source/MR/test_csharp.cpp index 9f0434a2..df275891 100644 --- a/test/output_c_fixed_typedefs/source/MR/test_csharp.cpp +++ b/test/output_c_fixed_typedefs/source/MR/test_csharp.cpp @@ -6882,6 +6882,14 @@ bool MR_C_equal_MR_CSharp_ExposedLayoutB(const MR_CSharp_ExposedLayoutB *_1, con ); } +MR_CSharp_ExposedLayoutC MR_CSharp_test_exposed_c(MR_CSharp_ExposedLayoutC a, const MR_CSharp_ExposedLayoutC *b) +{ + return MRBINDC_BIT_CAST((MR_CSharp_ExposedLayoutC), ::MR::CSharp::test_exposed_c( + MRBINDC_BIT_CAST((MR::CSharp::ExposedLayoutC), a), + (b ? MRBINDC_BIT_CAST((MR::CSharp::ExposedLayoutC), *b) : MR::CSharp::ExposedLayoutC(MR::CSharp::ExposedLayoutC{})) + )); +} + const int32_t *MR_CSharp_ArrayMembers_Get_i(const MR_CSharp_ArrayMembers *_this) { return std::addressof(((_this ? void() : MRBINDC_THROW("Parameter `_this` can not be null.", void)), *(const MR::CSharp::ArrayMembers *)(_this)).i); diff --git a/test/output_c_fixed_typedefs_64_only/include/MR/test_csharp.h b/test/output_c_fixed_typedefs_64_only/include/MR/test_csharp.h index 691e4495..39bf266d 100644 --- a/test/output_c_fixed_typedefs_64_only/include/MR/test_csharp.h +++ b/test/output_c_fixed_typedefs_64_only/include/MR/test_csharp.h @@ -6311,6 +6311,11 @@ MR_C_API MR_CSharp_ExposedLayoutB MR_CSharp_ExposedLayoutB_Construct_1(const MR_ /// Parameter `_2` can not be null. It is a single object. MR_C_API bool MR_C_equal_MR_CSharp_ExposedLayoutB(const MR_CSharp_ExposedLayoutB *_1, const MR_CSharp_ExposedLayoutB *_2); +// Single-field exposed structs use `LayoutKind.Sequential` in C#. This tests passing and returning one by value. +/// Generated from function `MR::CSharp::test_exposed_c`. +/// Parameter `b` has a default argument: `{}`, pass a null pointer to use it. +MR_C_API MR_CSharp_ExposedLayoutC MR_CSharp_test_exposed_c(MR_CSharp_ExposedLayoutC a, const MR_CSharp_ExposedLayoutC *b); + /// Returns a pointer to a member variable of class `MR::CSharp::ArrayMembers` named `i`. /// Parameter `_this` can not be null. It is a single object. /// The returned pointer will never be null. It is non-owning, do NOT destroy it. diff --git a/test/output_c_fixed_typedefs_64_only/source/MR/test_csharp.cpp b/test/output_c_fixed_typedefs_64_only/source/MR/test_csharp.cpp index c6ba68ac..ea50349e 100644 --- a/test/output_c_fixed_typedefs_64_only/source/MR/test_csharp.cpp +++ b/test/output_c_fixed_typedefs_64_only/source/MR/test_csharp.cpp @@ -7138,6 +7138,14 @@ bool MR_C_equal_MR_CSharp_ExposedLayoutB(const MR_CSharp_ExposedLayoutB *_1, con ); } +MR_CSharp_ExposedLayoutC MR_CSharp_test_exposed_c(MR_CSharp_ExposedLayoutC a, const MR_CSharp_ExposedLayoutC *b) +{ + return MRBINDC_BIT_CAST((MR_CSharp_ExposedLayoutC), ::MR::CSharp::test_exposed_c( + MRBINDC_BIT_CAST((MR::CSharp::ExposedLayoutC), a), + (b ? MRBINDC_BIT_CAST((MR::CSharp::ExposedLayoutC), *b) : MR::CSharp::ExposedLayoutC(MR::CSharp::ExposedLayoutC{})) + )); +} + const int *MR_CSharp_ArrayMembers_Get_i(const MR_CSharp_ArrayMembers *_this) { return std::addressof(((_this ? void() : MRBINDC_THROW("Parameter `_this` can not be null.", void)), *(const MR::CSharp::ArrayMembers *)(_this)).i); diff --git a/test/output_csharp/src/MR/test_csharp.cs b/test/output_csharp/src/MR/test_csharp.cs index e810ba01..a42320b5 100644 --- a/test/output_csharp/src/MR/test_csharp.cs +++ b/test/output_csharp/src/MR/test_csharp.cs @@ -13697,10 +13697,9 @@ public unsafe Box_ConvCtorExposed(int _1) : this(null, is_owning: true) // A converting ctor in an exposed struct. /// Generated from class `MR::CSharp::ConvCtorExposed`. /// This is the by-value version of the struct. - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct ConvCtorExposed { - [System.Runtime.InteropServices.FieldOffset(0)] public int x; /// Copy contents from a wrapper class to this struct. @@ -15836,10 +15835,9 @@ public unsafe Box_ExposedLayoutB(MR.CS.CSharp.Const_A _1) : this(null, is_owning /// Generated from class `MR::CSharp::ExposedLayoutB`. /// This is the by-value version of the struct. - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct ExposedLayoutB : System.IEquatable { - [System.Runtime.InteropServices.FieldOffset(0)] public int x; /// Copy contents from a wrapper class to this struct. @@ -16067,10 +16065,9 @@ public unsafe Box_ExposedLayoutC(ConstBox_ExposedLayoutC _other) : this(null, is // Just a simple exposed struct to test other things. /// Generated from class `MR::CSharp::ExposedLayoutC`. /// This is the by-value version of the struct. - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct ExposedLayoutC { - [System.Runtime.InteropServices.FieldOffset(0)] public int x; /// Copy contents from a wrapper class to this struct. @@ -16967,10 +16964,9 @@ public unsafe Box_A(ConstBox_A _other) : this(null, is_owning: true) /// Generated from class `MR::CSharp::NameConflictsExposed::A`. /// This is the by-value version of the struct. - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct A { - [System.Runtime.InteropServices.FieldOffset(0)] public int x; /// Copy contents from a wrapper class to this struct. @@ -17564,10 +17560,9 @@ public unsafe void Foo(int _1) // Test that we don't produce the const and non-const overloads of the same function under the same name in C#, as that would be a compilation error in C#. /// Generated from class `MR::CSharp::ConstNonconstConflicts`. /// This is the by-value version of the struct. - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct ConstNonconstConflicts { - [System.Runtime.InteropServices.FieldOffset(0)] public int x; /// Copy contents from a wrapper class to this struct. @@ -25651,6 +25646,19 @@ public static unsafe ref readonly MR.CS.CSharp.ExposedLayoutSh TestExposedCref(i if (__c_ret is not null) return *__c_ret; else return null; } + // Single-field exposed structs use `LayoutKind.Sequential` in C#. This tests passing and returning one by value. + /// Generated from function `MR::CSharp::test_exposed_c`. + /// Parameter `b` defaults to `{}`. + public static unsafe MR.CS.CSharp.ExposedLayoutC TestExposedC(MR.CS.CSharp.ExposedLayoutC a, MR.CS.CSharp._InOpt_ExposedLayoutC b = default) + { + [System.Runtime.InteropServices.DllImport("bleh", EntryPoint = "MR_CSharp_test_exposed_c", ExactSpelling = true)] + extern static MR.CS.CSharp.ExposedLayoutC __MR_CSharp_test_exposed_c(MR.CS.CSharp.ExposedLayoutC a, MR.CS.CSharp.ExposedLayoutC *b); + MR.CS.Misc._Exceptions.Prepare(); + var __c_ret = __MR_CSharp_test_exposed_c(a, b.HasValue ? &b.Object : null); + MR.CS.Misc._Exceptions.ThrowIfNeeded(); + return __c_ret; + } + /// Generated from function `MR::CSharp::test_optint`. /// Parameter `b` defaults to `default_optint`. /// Parameter `c` defaults to `default_optint`. diff --git a/test/output_csharp/src/MR/test_declaration_order.cs b/test/output_csharp/src/MR/test_declaration_order.cs index 302cbef8..035e5097 100644 --- a/test/output_csharp/src/MR/test_declaration_order.cs +++ b/test/output_csharp/src/MR/test_declaration_order.cs @@ -108,10 +108,9 @@ public unsafe MR.CS.DeclOrder.A.E D() // Here all classes are whitelisted using `--expose-as-struct`. /// Generated from class `MR::DeclOrder::A`. /// This is the by-value version of the struct. - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct A { - [System.Runtime.InteropServices.FieldOffset(0)] public int blah; /// Copy contents from a wrapper class to this struct. @@ -249,10 +248,9 @@ public unsafe MR.CS.DeclOrder.A.E B_() /// Generated from class `MR::DeclOrder::A::B`. /// This is the by-value version of the struct. - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct B { - [System.Runtime.InteropServices.FieldOffset(0)] public int bleh; /// Copy contents from a wrapper class to this struct. @@ -523,10 +521,9 @@ public unsafe MR.CS.DeclOrder.C_True Blah() /// Generated from class `MR::DeclOrder::C`. /// This is the by-value version of the struct. - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct C_False { - [System.Runtime.InteropServices.FieldOffset(0)] public int bleh; /// Copy contents from a wrapper class to this struct. @@ -709,10 +706,9 @@ public unsafe MR.CS.DeclOrder.C_False Blah() /// Generated from class `MR::DeclOrder::C`. /// This is the by-value version of the struct. - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct C_True { - [System.Runtime.InteropServices.FieldOffset(0)] public int bleh; /// Copy contents from a wrapper class to this struct. diff --git a/test/output_csharp/src/std_array_int_43.cs b/test/output_csharp/src/std_array_int_43.cs index 40235109..6d5e0240 100644 --- a/test/output_csharp/src/std_array_int_43.cs +++ b/test/output_csharp/src/std_array_int_43.cs @@ -83,10 +83,9 @@ public unsafe Box_Array_Int_43(ConstBox_Array_Int_43 _other) : this(null, is_own /// A fixed-size array of `int` of size 43. /// This is the by-value version of the struct. - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 172)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct Array_Int_43 { - [System.Runtime.InteropServices.FieldOffset(0)] public MR.CS.ArrayInt43 elems; /// Copy contents from a wrapper class to this struct. diff --git a/test/output_csharp/src/std_array_int_array_4_array_3_5.cs b/test/output_csharp/src/std_array_int_array_4_array_3_5.cs index e89276d5..285f396c 100644 --- a/test/output_csharp/src/std_array_int_array_4_array_3_5.cs +++ b/test/output_csharp/src/std_array_int_array_4_array_3_5.cs @@ -83,10 +83,9 @@ public unsafe Box_Array_IntArray4Array3_5(ConstBox_Array_IntArray4Array3_5 _othe /// A fixed-size array of `int[3][4]` of size 5. /// This is the by-value version of the struct. - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 240)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct Array_IntArray4Array3_5 { - [System.Runtime.InteropServices.FieldOffset(0)] public MR.CS.ArrayInt5_3_4 elems; /// Copy contents from a wrapper class to this struct. diff --git a/test/output_csharp_fixed_typedefs/src/MR/test_csharp.cs b/test/output_csharp_fixed_typedefs/src/MR/test_csharp.cs index 479f7aad..12a00411 100644 --- a/test/output_csharp_fixed_typedefs/src/MR/test_csharp.cs +++ b/test/output_csharp_fixed_typedefs/src/MR/test_csharp.cs @@ -14134,10 +14134,9 @@ public unsafe Box_ConvCtorExposed(int _1) : this(null, is_owning: true) /// Generated from class `MR::CSharp::ConvCtorExposed`. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct ConvCtorExposed { - [System.Runtime.InteropServices.FieldOffset(0)] public int x; /// @@ -16413,10 +16412,9 @@ public unsafe Box_ExposedLayoutB(MR.CS.CSharp.Const_A _1) : this(null, is_owning /// Generated from class `MR::CSharp::ExposedLayoutB`. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct ExposedLayoutB : System.IEquatable { - [System.Runtime.InteropServices.FieldOffset(0)] public int x; /// @@ -16685,10 +16683,9 @@ public unsafe Box_ExposedLayoutC(ConstBox_ExposedLayoutC _other) : this(null, is /// Generated from class `MR::CSharp::ExposedLayoutC`. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct ExposedLayoutC { - [System.Runtime.InteropServices.FieldOffset(0)] public int x; /// @@ -17694,10 +17691,9 @@ public unsafe Box_A(ConstBox_A _other) : this(null, is_owning: true) /// Generated from class `MR::CSharp::NameConflictsExposed::A`. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct A { - [System.Runtime.InteropServices.FieldOffset(0)] public int x; /// @@ -18334,10 +18330,9 @@ public unsafe void foo(int _1) /// Generated from class `MR::CSharp::ConstNonconstConflicts`. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct ConstNonconstConflicts { - [System.Runtime.InteropServices.FieldOffset(0)] public int x; /// @@ -27133,6 +27128,18 @@ public static unsafe ref readonly MR.CS.CSharp.ExposedLayoutSh testExposedCref(i if (__c_ret is not null) return *__c_ret; else return null; } + // Single-field exposed structs use `LayoutKind.Sequential` in C#. This tests passing and returning one by value. + /// + /// Generated from function `MR::CSharp::test_exposed_c`. + /// Parameter `b` defaults to `{}`. + /// + public static unsafe MR.CS.CSharp.ExposedLayoutC testExposedC(MR.CS.CSharp.ExposedLayoutC a, MR.CS.CSharp._InOpt_ExposedLayoutC b = default) + { + [System.Runtime.InteropServices.DllImport("bleh", EntryPoint = "MR_CSharp_test_exposed_c", ExactSpelling = true)] + extern static MR.CS.CSharp.ExposedLayoutC __MR_CSharp_test_exposed_c(MR.CS.CSharp.ExposedLayoutC a, MR.CS.CSharp.ExposedLayoutC *b); + return __MR_CSharp_test_exposed_c(a, b.HasValue ? &b.Object : null); + } + /// /// Generated from function `MR::CSharp::test_optint`. /// Parameter `b` defaults to `MR::CSharp::default_optint`. diff --git a/test/output_csharp_fixed_typedefs/src/MR/test_declaration_order.cs b/test/output_csharp_fixed_typedefs/src/MR/test_declaration_order.cs index 99302f26..736c070c 100644 --- a/test/output_csharp_fixed_typedefs/src/MR/test_declaration_order.cs +++ b/test/output_csharp_fixed_typedefs/src/MR/test_declaration_order.cs @@ -132,10 +132,9 @@ public unsafe MR.CS.DeclOrder.A.E d() /// Generated from class `MR::DeclOrder::A`. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct A { - [System.Runtime.InteropServices.FieldOffset(0)] public int blah; /// @@ -299,10 +298,9 @@ public unsafe MR.CS.DeclOrder.A.E b() /// Generated from class `MR::DeclOrder::A::B`. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct B { - [System.Runtime.InteropServices.FieldOffset(0)] public int bleh; /// @@ -612,10 +610,9 @@ public unsafe MR.CS.DeclOrder.C_True blah() /// Generated from class `MR::DeclOrder::C<false>`. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct C_False { - [System.Runtime.InteropServices.FieldOffset(0)] public int bleh; /// @@ -832,10 +829,9 @@ public unsafe MR.CS.DeclOrder.C_False blah() /// Generated from class `MR::DeclOrder::C<true>`. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct C_True { - [System.Runtime.InteropServices.FieldOffset(0)] public int bleh; /// diff --git a/test/output_csharp_fixed_typedefs/src/std_array_int32_t_43.cs b/test/output_csharp_fixed_typedefs/src/std_array_int32_t_43.cs index 23bd1d44..3c862522 100644 --- a/test/output_csharp_fixed_typedefs/src/std_array_int32_t_43.cs +++ b/test/output_csharp_fixed_typedefs/src/std_array_int32_t_43.cs @@ -109,10 +109,9 @@ public unsafe Box_Array_Int32T_43(ConstBox_Array_Int32T_43 _other) : this(null, /// A fixed-size array of `int32_t` of size 43. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 172)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct Array_Int32T_43 { - [System.Runtime.InteropServices.FieldOffset(0)] public MR.CS.ArrayInt32T43 elems; /// diff --git a/test/output_csharp_fixed_typedefs/src/std_array_int32_t_array_4_array_3_5.cs b/test/output_csharp_fixed_typedefs/src/std_array_int32_t_array_4_array_3_5.cs index ecbb2ef3..933aa9e6 100644 --- a/test/output_csharp_fixed_typedefs/src/std_array_int32_t_array_4_array_3_5.cs +++ b/test/output_csharp_fixed_typedefs/src/std_array_int32_t_array_4_array_3_5.cs @@ -109,10 +109,9 @@ public unsafe Box_Array_Int32TArray4Array3_5(ConstBox_Array_Int32TArray4Array3_5 /// A fixed-size array of `int32_t[3][4]` of size 5. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 240)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct Array_Int32TArray4Array3_5 { - [System.Runtime.InteropServices.FieldOffset(0)] public MR.CS.ArrayInt32T5_3_4 elems; /// diff --git a/test/output_csharp_fixed_typedefs_64_only/src/MR/test_csharp.cs b/test/output_csharp_fixed_typedefs_64_only/src/MR/test_csharp.cs index 8175b4d6..b0d67fcd 100644 --- a/test/output_csharp_fixed_typedefs_64_only/src/MR/test_csharp.cs +++ b/test/output_csharp_fixed_typedefs_64_only/src/MR/test_csharp.cs @@ -15282,10 +15282,9 @@ public unsafe Box_ConvCtorExposed(int _1) : this(null, is_owning: true) /// Generated from class `MR::CSharp::ConvCtorExposed`. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct ConvCtorExposed { - [System.Runtime.InteropServices.FieldOffset(0)] public int x; /// @@ -17653,10 +17652,9 @@ public unsafe Box_ExposedLayoutB(MR.CS.CSharp.Const_A _1) : this(null, is_owning /// Generated from class `MR::CSharp::ExposedLayoutB`. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct ExposedLayoutB : System.IEquatable { - [System.Runtime.InteropServices.FieldOffset(0)] public int x; /// @@ -17925,10 +17923,9 @@ public unsafe Box_ExposedLayoutC(ConstBox_ExposedLayoutC _other) : this(null, is /// Generated from class `MR::CSharp::ExposedLayoutC`. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct ExposedLayoutC { - [System.Runtime.InteropServices.FieldOffset(0)] public int x; /// @@ -18934,10 +18931,9 @@ public unsafe Box_A(ConstBox_A _other) : this(null, is_owning: true) /// Generated from class `MR::CSharp::NameConflictsExposed::A`. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct A { - [System.Runtime.InteropServices.FieldOffset(0)] public int x; /// @@ -19574,10 +19570,9 @@ public unsafe void foo(int _1) /// Generated from class `MR::CSharp::ConstNonconstConflicts`. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct ConstNonconstConflicts { - [System.Runtime.InteropServices.FieldOffset(0)] public int x; /// @@ -28358,6 +28353,18 @@ public static unsafe ref readonly MR.CS.CSharp.ExposedLayoutSh testExposedCref(i if (__c_ret is not null) return *__c_ret; else return null; } + // Single-field exposed structs use `LayoutKind.Sequential` in C#. This tests passing and returning one by value. + /// + /// Generated from function `MR::CSharp::test_exposed_c`. + /// Parameter `b` defaults to `{}`. + /// + public static unsafe MR.CS.CSharp.ExposedLayoutC testExposedC(MR.CS.CSharp.ExposedLayoutC a, MR.CS.CSharp._InOpt_ExposedLayoutC b = default) + { + [System.Runtime.InteropServices.DllImport("bleh", EntryPoint = "MR_CSharp_test_exposed_c", ExactSpelling = true)] + extern static MR.CS.CSharp.ExposedLayoutC __MR_CSharp_test_exposed_c(MR.CS.CSharp.ExposedLayoutC a, MR.CS.CSharp.ExposedLayoutC *b); + return __MR_CSharp_test_exposed_c(a, b.HasValue ? &b.Object : null); + } + /// /// Generated from function `MR::CSharp::test_optint`. /// Parameter `b` defaults to `MR::CSharp::default_optint`. diff --git a/test/output_csharp_fixed_typedefs_64_only/src/MR/test_declaration_order.cs b/test/output_csharp_fixed_typedefs_64_only/src/MR/test_declaration_order.cs index 99302f26..736c070c 100644 --- a/test/output_csharp_fixed_typedefs_64_only/src/MR/test_declaration_order.cs +++ b/test/output_csharp_fixed_typedefs_64_only/src/MR/test_declaration_order.cs @@ -132,10 +132,9 @@ public unsafe MR.CS.DeclOrder.A.E d() /// Generated from class `MR::DeclOrder::A`. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct A { - [System.Runtime.InteropServices.FieldOffset(0)] public int blah; /// @@ -299,10 +298,9 @@ public unsafe MR.CS.DeclOrder.A.E b() /// Generated from class `MR::DeclOrder::A::B`. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct B { - [System.Runtime.InteropServices.FieldOffset(0)] public int bleh; /// @@ -612,10 +610,9 @@ public unsafe MR.CS.DeclOrder.C_True blah() /// Generated from class `MR::DeclOrder::C<false>`. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct C_False { - [System.Runtime.InteropServices.FieldOffset(0)] public int bleh; /// @@ -832,10 +829,9 @@ public unsafe MR.CS.DeclOrder.C_False blah() /// Generated from class `MR::DeclOrder::C<true>`. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 4)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct C_True { - [System.Runtime.InteropServices.FieldOffset(0)] public int bleh; /// diff --git a/test/output_csharp_fixed_typedefs_64_only/src/std_array_int_43.cs b/test/output_csharp_fixed_typedefs_64_only/src/std_array_int_43.cs index c3dd7241..6b45a828 100644 --- a/test/output_csharp_fixed_typedefs_64_only/src/std_array_int_43.cs +++ b/test/output_csharp_fixed_typedefs_64_only/src/std_array_int_43.cs @@ -109,10 +109,9 @@ public unsafe Box_Array_Int_43(ConstBox_Array_Int_43 _other) : this(null, is_own /// A fixed-size array of `int` of size 43. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 172)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct Array_Int_43 { - [System.Runtime.InteropServices.FieldOffset(0)] public MR.CS.ArrayInt43 elems; /// diff --git a/test/output_csharp_fixed_typedefs_64_only/src/std_array_int_array_4_array_3_5.cs b/test/output_csharp_fixed_typedefs_64_only/src/std_array_int_array_4_array_3_5.cs index 3a59d67f..6f6eeee2 100644 --- a/test/output_csharp_fixed_typedefs_64_only/src/std_array_int_array_4_array_3_5.cs +++ b/test/output_csharp_fixed_typedefs_64_only/src/std_array_int_array_4_array_3_5.cs @@ -109,10 +109,9 @@ public unsafe Box_Array_IntArray4Array3_5(ConstBox_Array_IntArray4Array3_5 _othe /// A fixed-size array of `int[3][4]` of size 5. /// This is the by-value version of the struct. /// - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit, Size = 240)] + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct Array_IntArray4Array3_5 { - [System.Runtime.InteropServices.FieldOffset(0)] public MR.CS.ArrayInt5_3_4 elems; ///