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

std::wstring support for C# #1632

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ template __declspec(dllexport) std::basic_string<char, std::char_traits<char>, s
template __declspec(dllexport) std::basic_string<char, std::char_traits<char>, std::allocator<char>>::~basic_string() noexcept;
template __declspec(dllexport) std::basic_string<char, std::char_traits<char>, std::allocator<char>>& std::basic_string<char, std::char_traits<char>, std::allocator<char>>::assign(const char* const);
template __declspec(dllexport) const char* std::basic_string<char, std::char_traits<char>, std::allocator<char>>::data() const noexcept;

template __declspec(dllexport) std::allocator<wchar_t>::allocator() noexcept;
template __declspec(dllexport) std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t>>::basic_string() noexcept(true);
template __declspec(dllexport) std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t>>::~basic_string() noexcept;
template __declspec(dllexport) std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t>>& std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t>>::assign(const wchar_t* const);
template __declspec(dllexport) const wchar_t* std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t>>::data() const noexcept;
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ template __declspec(dllexport) std::basic_string<char, std::char_traits<char>, s
template __declspec(dllexport) std::basic_string<char, std::char_traits<char>, std::allocator<char>>::~basic_string() noexcept;
template __declspec(dllexport) std::basic_string<char, std::char_traits<char>, std::allocator<char>>& std::basic_string<char, std::char_traits<char>, std::allocator<char>>::assign(const char* const);
template __declspec(dllexport) const char* std::basic_string<char, std::char_traits<char>, std::allocator<char>>::data() const noexcept;

template __declspec(dllexport) std::allocator<wchar_t>::allocator() noexcept;
template __declspec(dllexport) std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t>>::basic_string() noexcept(true);
template __declspec(dllexport) std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t>>::~basic_string() noexcept;
template __declspec(dllexport) std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t>>& std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t>>::assign(const wchar_t* const);
template __declspec(dllexport) const wchar_t* std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t>>::data() const noexcept;
26 changes: 25 additions & 1 deletion src/Generator/Passes/IgnoreSystemDeclarationsPass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/Generator/Types/Std/Stdlib.CLI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public override void CLIMarshalToManaged(MarshalContext ctx)
}
}

[TypeMap("std::wstring", GeneratorKind = GeneratorKind.CLI)]
[TypeMap("basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t>>", GeneratorKind = GeneratorKind.CLI)]
public partial class WString : TypeMap
{
public override Type CLISignatureType(TypePrinterContext ctx)
Expand Down
1 change: 1 addition & 0 deletions src/Generator/Types/Std/Stdlib.CSharp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ public partial class ConstChar32TPointer : ConstCharPointer
}

[TypeMap("basic_string<char, char_traits<char>, allocator<char>>", GeneratorKind = GeneratorKind.CSharp)]
[TypeMap("basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t>>", GeneratorKind = GeneratorKind.CSharp)]
public partial class String : TypeMap
{
public override Type CSharpSignatureType(TypePrinterContext ctx)
Expand Down
7 changes: 5 additions & 2 deletions tests/Common/Common.Gen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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)
Expand Down
75 changes: 74 additions & 1 deletion tests/Common/Common.Tests.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -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<OverflowException>(() => foo2.TestCharMarshalling('ж'));
#endif
}
}

Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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

}
}

Expand Down Expand Up @@ -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
}
}

Expand Down Expand Up @@ -964,6 +1003,40 @@ 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.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.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()
{
Expand Down
23 changes: 23 additions & 0 deletions tests/Common/Common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
}
Expand Down
11 changes: 11 additions & 0 deletions tests/Common/Common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down