From 587f466111553c3618833993a5392169fe92b5cf Mon Sep 17 00:00:00 2001 From: strentler Date: Mon, 30 Aug 2021 16:56:08 +0200 Subject: [PATCH 1/3] std::wstring support - In IgnoreSystemDeclarationsPass: Explicitly renamed basic_string:: assign and data methods becasue otherwise some following implicit renamings make the code not working (this issue can be observer when a std::string and std::wstring is present in the C++ input classes) - Removed the resetting of method names in SpecializationMethodsWithDependentPointersPass becasue this reverts changes which are made in the pass IgnoreSystemDeclarationsPass - added wstring type map - added a hacky flag be able to run tests when the option MarshalCharAsManagedChar is set to false --- .../Passes/IgnoreSystemDeclarationsPass.cs | 26 ++++++- ...izationMethodsWithDependentPointersPass.cs | 5 +- src/Generator/Types/Std/Stdlib.CSharp.cs | 1 + tests/Common/Common.Gen.cs | 7 +- tests/Common/Common.Tests.cs | 74 ++++++++++++++++++- tests/Common/Common.cpp | 23 ++++++ tests/Common/Common.h | 11 +++ 7 files changed, 141 insertions(+), 6 deletions(-) diff --git a/src/Generator/Passes/IgnoreSystemDeclarationsPass.cs b/src/Generator/Passes/IgnoreSystemDeclarationsPass.cs index 80f34b1cc2..7ef408f2b8 100644 --- a/src/Generator/Passes/IgnoreSystemDeclarationsPass.cs +++ b/src/Generator/Passes/IgnoreSystemDeclarationsPass.cs @@ -42,12 +42,36 @@ public override bool VisitClassDecl(Class @class) switch (@class.Name) { case "basic_string": + @class.GenerationKind = GenerationKind.Generate; + foreach (var specialization in from s in @class.Specializations + let arg = s.Arguments[0].Type.Type.Desugar() + where arg.IsPrimitiveType(PrimitiveType.Char) || arg.IsPrimitiveType(PrimitiveType.WideChar) + select s) + { + specialization.GenerationKind = GenerationKind.Generate; + foreach (var method in specialization.Methods) + { + if (method.OriginalName == "assign" || method.OriginalName == "data") + { + if (specialization.Arguments[0].Type.Type.Desugar().IsPrimitiveType(PrimitiveType.WideChar)) + method.Name = method.Name + "W"; + else if (specialization.Arguments[0].Type.Type.Desugar().IsPrimitiveType(PrimitiveType.Char)) + method.Name = method.Name + "A"; + + method.GenerationKind = GenerationKind.Generate; + method.Namespace.GenerationKind = GenerationKind.Generate; + method.InstantiatedFrom.GenerationKind = GenerationKind.Generate; + method.InstantiatedFrom.Namespace.GenerationKind = GenerationKind.Generate; + } + } + } + break; case "allocator": case "char_traits": @class.GenerationKind = GenerationKind.Generate; foreach (var specialization in from s in @class.Specializations let arg = s.Arguments[0].Type.Type.Desugar() - where arg.IsPrimitiveType(PrimitiveType.Char) + where arg.IsPrimitiveType(PrimitiveType.Char) || arg.IsPrimitiveType(PrimitiveType.WideChar) select s) { specialization.GenerationKind = GenerationKind.Generate; diff --git a/src/Generator/Passes/SpecializationMethodsWithDependentPointersPass.cs b/src/Generator/Passes/SpecializationMethodsWithDependentPointersPass.cs index 97a71fd58a..c527f5e8d5 100644 --- a/src/Generator/Passes/SpecializationMethodsWithDependentPointersPass.cs +++ b/src/Generator/Passes/SpecializationMethodsWithDependentPointersPass.cs @@ -113,8 +113,9 @@ private static Method GetExtensionMethodForDependentPointer(Method specializedMe } } - specializedMethod.Name = specializedMethod.OriginalName; - extensionMethod.Name = extensionMethod.OriginalName; + // If we change the Name in IgnoreSystemDeclarationsPass.cs we should not revert this change here. Otherwise we get Assign_1 instead of AssignW or AssignA for string / wstring + //specializedMethod.Name = specializedMethod.OriginalName; + //extensionMethod.Name = extensionMethod.OriginalName; extensionMethod.OriginalFunction = specializedMethod; extensionMethod.Kind = CXXMethodKind.Normal; extensionMethod.IsStatic = true; diff --git a/src/Generator/Types/Std/Stdlib.CSharp.cs b/src/Generator/Types/Std/Stdlib.CSharp.cs index 42f0dc058f..35cf587d1d 100644 --- a/src/Generator/Types/Std/Stdlib.CSharp.cs +++ b/src/Generator/Types/Std/Stdlib.CSharp.cs @@ -302,6 +302,7 @@ public partial class ConstChar32TPointer : ConstCharPointer } [TypeMap("basic_string, allocator>", GeneratorKind = GeneratorKind.CSharp)] + [TypeMap("basic_string, allocator>", GeneratorKind = GeneratorKind.CSharp)] public partial class String : TypeMap { public override Type CSharpSignatureType(TypePrinterContext ctx) diff --git a/tests/Common/Common.Gen.cs b/tests/Common/Common.Gen.cs index 0f1c47d10f..a8e8efba4b 100644 --- a/tests/Common/Common.Gen.cs +++ b/tests/Common/Common.Gen.cs @@ -62,7 +62,7 @@ public override void Setup(Driver driver) public override void SetupPasses(Driver driver) { - driver.Options.MarshalCharAsManagedChar = true; + driver.Options.MarshalCharAsManagedChar = false; // c++ char is mapped to sbyte. c++ wchar_t is mapped to char driver.Options.GenerateDefaultValuesForArguments = true; } @@ -72,8 +72,11 @@ public override void Preprocess(Driver driver, ASTContext ctx) ctx.SetClassAsValueType("Bar2"); ctx.IgnoreClassWithName("IgnoredType"); - ctx.FindCompleteClass("Foo").Enums.First( + if (ctx.FindCompleteClass("Foo") != null) + { + ctx.FindCompleteClass("Foo").Enums.First( e => string.IsNullOrEmpty(e.Name)).Name = "RenamedEmptyEnum"; + } } public override void Postprocess(Driver driver, ASTContext ctx) diff --git a/tests/Common/Common.Tests.cs b/tests/Common/Common.Tests.cs index 08a464fe02..7d481cd0f9 100644 --- a/tests/Common/Common.Tests.cs +++ b/tests/Common/Common.Tests.cs @@ -1,4 +1,6 @@ -using System; +#define MarshalCharAsManagedSByte // Tests need to behave differently when char is mapped to sbyte + +using System; using System.Reflection; using CommonTest; using NUnit.Framework; @@ -396,8 +398,13 @@ public void TestNestedAnonymousTypes() Assert.That(testNestedTypes.ToVerifyCorrectLayoutBefore, Is.EqualTo(5)); testNestedTypes.I = 10; Assert.That(testNestedTypes.I, Is.EqualTo(10)); +#if MarshalCharAsManagedSByte + testNestedTypes.C = Convert.ToSByte('D'); + Assert.That(Convert.ToChar(testNestedTypes.C), Is.EqualTo('D')); +#else testNestedTypes.C = 'D'; Assert.That(testNestedTypes.C, Is.EqualTo('D')); +#endif testNestedTypes.ToVerifyCorrectLayoutAfter = 15; Assert.That(testNestedTypes.ToVerifyCorrectLayoutAfter, Is.EqualTo(15)); } @@ -459,9 +466,19 @@ public void TestCharMarshalling() { using (Foo2 foo2 = new Foo2()) { +#if MarshalCharAsManagedSByte + for (Int32 c = sbyte.MinValue; c <= sbyte.MaxValue; c++) + { + sbyte cSbyte = Convert.ToSByte(c); + sbyte charMarshalled = foo2.TestCharMarshalling(cSbyte); + Int32 charBackConverted = Convert.ToInt32(charMarshalled); + Assert.That(charBackConverted, Is.EqualTo(c)); + } +#else for (char c = char.MinValue; c <= sbyte.MaxValue; c++) Assert.That(foo2.TestCharMarshalling(c), Is.EqualTo(c)); Assert.Catch(() => foo2.TestCharMarshalling('ж')); +#endif } } @@ -511,7 +528,11 @@ public void TestOperators() { using (var @class = new ClassWithOverloadedOperators()) { +#if MarshalCharAsManagedSByte + sbyte @char = @class; +#else char @char = @class; +#endif Assert.That(@char, Is.EqualTo(1)); short @short = @class; Assert.That(@short, Is.EqualTo(3)); @@ -573,7 +594,12 @@ public void TestProperties() Assert.That(prop.StartWithVerb, Is.EqualTo(25)); prop.StartWithVerb = 5; +#if MarshalCharAsManagedSByte + sbyte cSByte = Convert.ToSByte('a'); + Assert.That(prop.Contains(cSByte), Is.EqualTo(prop.Contains("a"))); +#else Assert.That(prop.Contains('a'), Is.EqualTo(prop.Contains("a"))); +#endif Assert.That(prop.conflict, Is.EqualTo(CommonTest.TestProperties.Conflict.Value1)); prop.conflict = CommonTest.TestProperties.Conflict.Value2; @@ -849,8 +875,14 @@ public void TestVirtualReturningClassWithCharField() hasProblematicFields.B = true; Assert.That(hasProblematicFields.B, Is.EqualTo(true)); Assert.That(hasProblematicFields.C, Is.EqualTo(char.MinValue)); +#if MarshalCharAsManagedSByte + hasProblematicFields.C = Convert.ToSByte('a'); + Assert.That(Convert.ToChar(hasProblematicFields.C), Is.EqualTo('a')); +#else hasProblematicFields.C = 'a'; Assert.That(hasProblematicFields.C, Is.EqualTo('a')); +#endif + } } @@ -897,10 +929,17 @@ public void TestFixedCharArray() { using (var foo = new Foo()) { +#if MarshalCharAsManagedSByte + foo.FixedCharArray = new sbyte[] { Convert.ToSByte('a'), Convert.ToSByte('b'), Convert.ToSByte('c') }; + Assert.That(foo.FixedCharArray[0], Is.EqualTo('a')); + Assert.That(foo.FixedCharArray[1], Is.EqualTo('b')); + Assert.That(foo.FixedCharArray[2], Is.EqualTo('c')); +#else foo.FixedCharArray = new char[] { 'a', 'b', 'c' }; Assert.That(foo.FixedCharArray[0], Is.EqualTo('a')); Assert.That(foo.FixedCharArray[1], Is.EqualTo('b')); Assert.That(foo.FixedCharArray[2], Is.EqualTo('c')); +#endif } } @@ -964,6 +1003,39 @@ public void TestNullStdString() } } + [Test] + public void TestStdWString() + { + // when C++ memory is deleted, it's only marked as free but not immediadely freed + // this can hide memory bugs while marshalling + // so let's use a long string to increase the chance of a crash right away + const string t = @"This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. +This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. +This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. +This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. +This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. +This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string. This is a very long string."; + const string unicodeString1 = "你好"; + const string unicodeString2 = "Ÿ‰ϰ"; + + using (var hasStdWString = new HasStdWString()) + { + Assert.That(hasStdWString.TestStdWString(t), Is.EqualTo(t + "_test")); + hasStdWString.S = t; + Assert.That(hasStdWString.S, Is.EqualTo(t)); + Assert.That(hasStdWString.StdWString, Is.EqualTo(t)); + Assert.That(hasStdWString.StdWString, Is.EqualTo(t) + Assert.That(hasStdWString.TestStdWString(unicodeString1), Is.EqualTo(unicodeString1 + "_test")); + hasStdWString.S = unicodeString1; + Assert.That(hasStdWString.S, Is.EqualTo(unicodeString1)); + Assert.That(hasStdWString.StdWString, Is.EqualTo(unicodeString1) + Assert.That(hasStdWString.TestStdWString(unicodeString2), Is.EqualTo(unicodeString2 + "_test")); + hasStdWString.S = unicodeString2; + Assert.That(hasStdWString.S, Is.EqualTo(unicodeString2)); + Assert.That(hasStdWString.StdWString, Is.EqualTo(unicodeString2)); + } + } + [Test] public void TestUTF8() { diff --git a/tests/Common/Common.cpp b/tests/Common/Common.cpp index 54110905b6..7ac7225ba0 100644 --- a/tests/Common/Common.cpp +++ b/tests/Common/Common.cpp @@ -607,6 +607,29 @@ std::string& HasStdString::getStdString() return s; } +HasStdWString::HasStdWString() +{ +} + +HasStdWString::~HasStdWString() +{ +} + +std::wstring HasStdWString::testStdWString(const std::wstring& s) +{ + return s + L"_test"; +} + +std::wstring HasStdWString::testStdWStringPassedByValue(std::wstring s) +{ + return s + L"_test"; +} + +std::wstring& HasStdWString::getStdWString() +{ + return s; +} + SomeNamespace::AbstractClass::~AbstractClass() { } diff --git a/tests/Common/Common.h b/tests/Common/Common.h index 124d067a9c..4dad6c268a 100644 --- a/tests/Common/Common.h +++ b/tests/Common/Common.h @@ -817,6 +817,17 @@ class DLL_API HasStdString std::string& getStdString(); }; +class DLL_API HasStdWString +{ +public: + HasStdWString(); + ~HasStdWString(); + std::wstring testStdWString(const std::wstring& s); + std::wstring testStdWStringPassedByValue(std::wstring s); + std::wstring s; + std::wstring& getStdWString(); +}; + class DLL_API InternalCtorAmbiguity { public: From f1743e9899a2f47275115e43718b0104241dc422 Mon Sep 17 00:00:00 2001 From: strentler Date: Tue, 14 Sep 2021 13:28:50 +0200 Subject: [PATCH 2/3] Adjustments for std:wstring support for CLI - Adjusted TypeMap for wstring in Stdlib.CLI.cs - Fixed typo in Common.Test.cs - Added check in test - Added wstring exports in Std-symbols.cpp --- .../Bindings/CSharp/x86_64-pc-win32-msvc/Std-symbols.cpp | 6 ++++++ src/Generator/Types/Std/Stdlib.CLI.cs | 2 +- tests/Common/Common.Tests.cs | 5 +++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/CppParser/Bindings/CSharp/x86_64-pc-win32-msvc/Std-symbols.cpp b/src/CppParser/Bindings/CSharp/x86_64-pc-win32-msvc/Std-symbols.cpp index 682e399117..9647c2f875 100644 --- a/src/CppParser/Bindings/CSharp/x86_64-pc-win32-msvc/Std-symbols.cpp +++ b/src/CppParser/Bindings/CSharp/x86_64-pc-win32-msvc/Std-symbols.cpp @@ -9,3 +9,9 @@ template __declspec(dllexport) std::basic_string, s template __declspec(dllexport) std::basic_string, std::allocator>::~basic_string() noexcept; template __declspec(dllexport) std::basic_string, std::allocator>& std::basic_string, std::allocator>::assign(const char* const); template __declspec(dllexport) const char* std::basic_string, std::allocator>::data() const noexcept; + +template __declspec(dllexport) std::allocator::allocator() noexcept; +template __declspec(dllexport) std::basic_string, std::allocator>::basic_string() noexcept(true); +template __declspec(dllexport) std::basic_string, std::allocator>::~basic_string() noexcept; +template __declspec(dllexport) std::basic_string, std::allocator>& std::basic_string, std::allocator>::assign(const wchar_t* const); +template __declspec(dllexport) const wchar_t* std::basic_string, std::allocator>::data() const noexcept; diff --git a/src/Generator/Types/Std/Stdlib.CLI.cs b/src/Generator/Types/Std/Stdlib.CLI.cs index c68981b287..67b66ce1eb 100644 --- a/src/Generator/Types/Std/Stdlib.CLI.cs +++ b/src/Generator/Types/Std/Stdlib.CLI.cs @@ -103,7 +103,7 @@ public override void CLIMarshalToManaged(MarshalContext ctx) } } - [TypeMap("std::wstring", GeneratorKind = GeneratorKind.CLI)] + [TypeMap("basic_string, allocator>", GeneratorKind = GeneratorKind.CLI)] public partial class WString : TypeMap { public override Type CLISignatureType(TypePrinterContext ctx) diff --git a/tests/Common/Common.Tests.cs b/tests/Common/Common.Tests.cs index 7d481cd0f9..28e468c2f6 100644 --- a/tests/Common/Common.Tests.cs +++ b/tests/Common/Common.Tests.cs @@ -1020,15 +1020,16 @@ public void TestStdWString() using (var hasStdWString = new HasStdWString()) { + Assert.That(hasStdWString.TestStdWStringPassedByValue(t), Is.EqualTo(t + "_test")); Assert.That(hasStdWString.TestStdWString(t), Is.EqualTo(t + "_test")); hasStdWString.S = t; Assert.That(hasStdWString.S, Is.EqualTo(t)); Assert.That(hasStdWString.StdWString, Is.EqualTo(t)); - Assert.That(hasStdWString.StdWString, Is.EqualTo(t) + Assert.That(hasStdWString.StdWString, Is.EqualTo(t)); Assert.That(hasStdWString.TestStdWString(unicodeString1), Is.EqualTo(unicodeString1 + "_test")); hasStdWString.S = unicodeString1; Assert.That(hasStdWString.S, Is.EqualTo(unicodeString1)); - Assert.That(hasStdWString.StdWString, Is.EqualTo(unicodeString1) + Assert.That(hasStdWString.StdWString, Is.EqualTo(unicodeString1)); Assert.That(hasStdWString.TestStdWString(unicodeString2), Is.EqualTo(unicodeString2 + "_test")); hasStdWString.S = unicodeString2; Assert.That(hasStdWString.S, Is.EqualTo(unicodeString2)); From 6b3c4d0c02419889f597eed859ff52759c1e98ff Mon Sep 17 00:00:00 2001 From: strentler Date: Tue, 14 Sep 2021 13:50:55 +0200 Subject: [PATCH 3/3] Adjusted exports for Std-symbols.cpp x86 msvc --- .../Bindings/CSharp/i686-pc-win32-msvc/Std-symbols.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/CppParser/Bindings/CSharp/i686-pc-win32-msvc/Std-symbols.cpp b/src/CppParser/Bindings/CSharp/i686-pc-win32-msvc/Std-symbols.cpp index 682e399117..9647c2f875 100644 --- a/src/CppParser/Bindings/CSharp/i686-pc-win32-msvc/Std-symbols.cpp +++ b/src/CppParser/Bindings/CSharp/i686-pc-win32-msvc/Std-symbols.cpp @@ -9,3 +9,9 @@ template __declspec(dllexport) std::basic_string, s template __declspec(dllexport) std::basic_string, std::allocator>::~basic_string() noexcept; template __declspec(dllexport) std::basic_string, std::allocator>& std::basic_string, std::allocator>::assign(const char* const); template __declspec(dllexport) const char* std::basic_string, std::allocator>::data() const noexcept; + +template __declspec(dllexport) std::allocator::allocator() noexcept; +template __declspec(dllexport) std::basic_string, std::allocator>::basic_string() noexcept(true); +template __declspec(dllexport) std::basic_string, std::allocator>::~basic_string() noexcept; +template __declspec(dllexport) std::basic_string, std::allocator>& std::basic_string, std::allocator>::assign(const wchar_t* const); +template __declspec(dllexport) const wchar_t* std::basic_string, std::allocator>::data() const noexcept;