From 19bfe5af8453700a316327d4b3f642b09342eafc Mon Sep 17 00:00:00 2001 From: zillemarco Date: Thu, 12 Oct 2017 10:24:54 +0200 Subject: [PATCH] Added support for wchar_t and std::wstring for C# With the help of @tritao: - added support for wchar_t - added support for std::wstring - unified the way wide character strings are handled between C# and C++/CLI - changed the way strings are handled, using 'IntPtr' instead of 'string' with marshalling attributes on the P/Invokes --- build/Tests.lua | 1 + .../CSharp/i686-pc-win32-msvc/Std-symbols.cpp | 5 + .../Generators/CSharp/CSharpMarshal.cs | 98 +++++++++++-------- .../Generators/CSharp/CSharpSources.cs | 8 +- .../Generators/CSharp/CSharpTypePrinter.cs | 21 ++-- src/Generator/Passes/CheckAbiParameters.cs | 6 +- .../Passes/IgnoreSystemDeclarationsPass.cs | 17 +++- src/Generator/Types/Std/Stdlib.cs | 90 +++++++---------- src/Runtime/Helpers.cs | 10 ++ src/Runtime/WideChar.cs | 93 ++++++++++++++++++ tests/Common/Common.Tests.cs | 54 ++++++++++ tests/Common/Common.cpp | 23 +++++ tests/Common/Common.h | 12 +++ 13 files changed, 327 insertions(+), 111 deletions(-) create mode 100644 src/Runtime/WideChar.cs diff --git a/build/Tests.lua b/build/Tests.lua index 7fdf8042a8..8de51c3c67 100644 --- a/build/Tests.lua +++ b/build/Tests.lua @@ -219,6 +219,7 @@ function SetupTestProjectsCLI(name, extraFiles, suffix) dependson { name .. ".Native" } LinkNUnit() + links { "CppSharp.Runtime" } end function IncludeExamples() 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 84e4dc8a62..199e1276b4 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 @@ -5,3 +5,8 @@ template __declspec(dllexport) std::basic_string, s template __declspec(dllexport) std::basic_string, std::allocator>::~basic_string() noexcept; template __declspec(dllexport) const char* std::basic_string, std::allocator>::c_str() const noexcept; template __declspec(dllexport) std::allocator::allocator() noexcept; + +template __declspec(dllexport) std::basic_string, std::allocator>::basic_string(const wchar_t* const, const std::allocator&); +template __declspec(dllexport) std::basic_string, std::allocator>::~basic_string() noexcept; +template __declspec(dllexport) const wchar_t* std::basic_string, std::allocator>::c_str() const noexcept; +template __declspec(dllexport) std::allocator::allocator() noexcept; \ No newline at end of file diff --git a/src/Generator/Generators/CSharp/CSharpMarshal.cs b/src/Generator/Generators/CSharp/CSharpMarshal.cs index 4f172309c9..ed081edee5 100644 --- a/src/Generator/Generators/CSharp/CSharpMarshal.cs +++ b/src/Generator/Generators/CSharp/CSharpMarshal.cs @@ -106,7 +106,8 @@ public override bool VisitArrayType(ArrayType array, TypeQualifiers quals) Helpers.InternalStruct, Context.ReturnVarName); else { - if (arrayType.IsPrimitiveType(PrimitiveType.Char) && + if ((arrayType.IsPrimitiveType(PrimitiveType.Char) || + arrayType.IsPrimitiveType(PrimitiveType.WideChar)) && Context.Context.Options.MarshalCharAsManagedChar) { supportBefore.WriteLineIndent( @@ -130,7 +131,8 @@ public override bool VisitArrayType(ArrayType array, TypeQualifiers quals) break; case ArrayType.ArraySize.Incomplete: // const char* and const char[] are the same so we can use a string - if (array.Type.Desugar().IsPrimitiveType(PrimitiveType.Char) && + if ((array.Type.Desugar().IsPrimitiveType(PrimitiveType.Char) || + array.Type.Desugar().IsPrimitiveType(PrimitiveType.WideChar)) && array.QualifiedType.Qualifiers.IsConst) return VisitPointerType(new PointerType { @@ -155,17 +157,16 @@ public override bool VisitPointerType(PointerType pointer, TypeQualifiers quals) var isRefParam = param != null && (param.IsInOut || param.IsOut); var pointee = pointer.Pointee.Desugar(); - bool marshalPointeeAsString = CSharpTypePrinter.IsConstCharString(pointee) && isRefParam; - - if ((CSharpTypePrinter.IsConstCharString(pointer) && !MarshalsParameter) || - marshalPointeeAsString) + var finalPointee = pointer.GetFinalPointee(); + + if (CSharpTypePrinter.IsConstCharString(pointer) || + (CSharpTypePrinter.IsConstCharString(pointee) && isRefParam)) { Context.Return.Write(MarshalStringToManaged(Context.ReturnVarName, pointer.GetFinalPointee().Desugar() as BuiltinType)); return true; } - var finalPointee = pointer.GetFinalPointee(); PrimitiveType primitive; if (finalPointee.IsPrimitiveType(out primitive) || finalPointee.IsEnumType()) { @@ -237,11 +238,13 @@ public override bool VisitPrimitiveType(PrimitiveType primitive, TypeQualifiers // returned structs must be blittable and char isn't if (Context.Context.Options.MarshalCharAsManagedChar) { - Context.Return.Write("global::System.Convert.ToChar({0})", - Context.ReturnVarName); + Context.Return.Write($"global::System.Convert.ToChar({Context.ReturnVarName})"); return true; } goto default; + case PrimitiveType.WideChar: + Context.Return.Write($"new CppSharp.Runtime.WideChar({Context.ReturnVarName})"); + return true; case PrimitiveType.Char16: return false; case PrimitiveType.Bool: @@ -271,10 +274,10 @@ public override bool VisitTypedefType(TypedefType typedef, TypeQualifiers quals) var ptrName = Generator.GeneratedIdentifier("ptr") + Context.ParameterIndex; - Context.Before.WriteLine("var {0} = {1};", ptrName, - Context.ReturnVarName); + Context.Before.WriteLine($"var {ptrName} = {Context.ReturnVarName};"); - var res = $"{ptrName} == IntPtr.Zero? null : ({typedef})Marshal.GetDelegateForFunctionPointer({ptrName}, typeof({typedef}))"; + var res = $@"{ptrName} == IntPtr.Zero? null : ({typedef + })Marshal.GetDelegateForFunctionPointer({ptrName}, typeof({typedef}))"; Context.Return.Write(res); return true; } @@ -286,11 +289,10 @@ public override bool VisitFunctionType(FunctionType function, TypeQualifiers qua { var ptrName = Generator.GeneratedIdentifier("ptr") + Context.ParameterIndex; - Context.Before.WriteLine("var {0} = {1};", ptrName, - Context.ReturnVarName); + Context.Before.WriteLine($"var {ptrName} = {Context.ReturnVarName};"); - Context.Return.Write("({1})Marshal.GetDelegateForFunctionPointer({0}, typeof({1}))", - ptrName, function.ToString()); + Context.Return.Write($@"({function.ToString() + })Marshal.GetDelegateForFunctionPointer({ptrName}, typeof({function.ToString()}))"); return true; } @@ -316,7 +318,7 @@ public override bool VisitClassDecl(Class @class) public override bool VisitEnumDecl(Enumeration @enum) { - Context.Return.Write("{0}", Context.ReturnVarName); + Context.Return.Write($"{Context.ReturnVarName}"); return true; } @@ -340,8 +342,7 @@ public override bool VisitParameterDecl(Parameter parameter) if (!string.IsNullOrWhiteSpace(ctx.Return) && !parameter.Type.IsPrimitiveTypeConvertibleToRef()) { - Context.Before.WriteLine("var _{0} = {1};", parameter.Name, - ctx.Return); + Context.Before.WriteLine($"var _{parameter.Name} = {ctx.Return};"); } Context.Return.Write("{0}{1}", @@ -512,12 +513,16 @@ public override bool VisitArrayType(ArrayType array, TypeQualifiers quals) } else { - if (arrayType.IsPrimitiveType(PrimitiveType.Char) && + if ((arrayType.IsPrimitiveType(PrimitiveType.Char) || + arrayType.IsPrimitiveType(PrimitiveType.WideChar)) && Context.Context.Options.MarshalCharAsManagedChar) { - supportBefore.WriteLineIndent( - "{0}[i] = global::System.Convert.ToSByte({1}[i]);", - Context.ReturnVarName, Context.ArgName); + if(arrayType.IsPrimitiveType(PrimitiveType.Char)) + supportBefore.WriteLineIndent( + $"{Context.ReturnVarName}[i] = global::System.Convert.ToSByte({Context.ArgName}[i]);"); + else + supportBefore.WriteLineIndent( + $"{Context.ReturnVarName}[i] = global::System.Convert.ToChar({Context.ArgName}[i]);"); } else { @@ -591,6 +596,10 @@ public override bool VisitPointerType(PointerType pointer, TypeQualifiers quals) var param = Context.Parameter; var isRefParam = param != null && (param.IsInOut || param.IsOut); + var finalPointee = pointer.GetFinalPointee(); + + PrimitiveType primitive; + bool finalPointeeIsPrimitiveType = finalPointee.IsPrimitiveType(out primitive); if (CSharpTypePrinter.IsConstCharString(pointee) && isRefParam) { @@ -601,12 +610,12 @@ public override bool VisitPointerType(PointerType pointer, TypeQualifiers quals) } else if (param.IsInOut) { - Context.Return.Write(MarshalStringToUnmanaged(Context.Parameter.Name)); + Context.Return.Write(MarshalStringToUnmanaged(Context.Parameter.Name, primitive)); Context.ArgumentPrefix.Write("&"); } else { - Context.Return.Write(MarshalStringToUnmanaged(Context.Parameter.Name)); + Context.Return.Write(MarshalStringToUnmanaged(Context.Parameter.Name, primitive)); Context.Cleanup.WriteLine("Marshal.FreeHGlobal({0});", Context.ArgName); } return true; @@ -639,9 +648,7 @@ public override bool VisitPointerType(PointerType pointer, TypeQualifiers quals) } var marshalAsString = CSharpTypePrinter.IsConstCharString(pointer); - var finalPointee = pointer.GetFinalPointee(); - PrimitiveType primitive; - if (finalPointee.IsPrimitiveType(out primitive) || finalPointee.IsEnumType() || + if (finalPointeeIsPrimitiveType || finalPointee.IsEnumType() || marshalAsString) { // From MSDN: "note that a ref or out parameter is classified as a moveable @@ -667,13 +674,11 @@ public override bool VisitPointerType(PointerType pointer, TypeQualifiers quals) { if (!marshalAsString && Context.Context.Options.MarshalCharAsManagedChar && - primitive == PrimitiveType.Char) + (primitive == PrimitiveType.Char || primitive == PrimitiveType.WideChar)) Context.Return.Write($"({typePrinter.PrintNative(pointer)}) "); - if (marshalAsString && (Context.MarshalKind == MarshalKind.NativeField || - Context.MarshalKind == MarshalKind.VTableReturnValue || - Context.MarshalKind == MarshalKind.Variable)) - Context.Return.Write(MarshalStringToUnmanaged(Context.Parameter.Name)); + if (marshalAsString) + Context.Return.Write(MarshalStringToUnmanaged(Context.Parameter.Name, primitive)); else Context.Return.Write(Context.Parameter.Name); } @@ -684,19 +689,25 @@ public override bool VisitPointerType(PointerType pointer, TypeQualifiers quals) return pointer.QualifiedPointee.Visit(this); } - private string MarshalStringToUnmanaged(string varName) + private string MarshalStringToUnmanaged(string varName, PrimitiveType type) { - if (Equals(Context.Context.Options.Encoding, Encoding.ASCII)) + if (type == PrimitiveType.WideChar) { - return string.Format("Marshal.StringToHGlobalAnsi({0})", varName); + // Looks like Marshal.StringToHGlobalUni is already able + // to handle both Unicode and MBCS charsets + return $"Marshal.StringToHGlobalUni({varName})"; } + + if (Equals(Context.Context.Options.Encoding, Encoding.ASCII)) + return $"Marshal.StringToHGlobalAnsi({varName})"; + if (Equals(Context.Context.Options.Encoding, Encoding.Unicode) || Equals(Context.Context.Options.Encoding, Encoding.BigEndianUnicode)) { - return string.Format("Marshal.StringToHGlobalUni({0})", varName); + return $"Marshal.StringToHGlobalUni({varName})"; } - throw new NotSupportedException(string.Format("{0} is not supported yet.", - Context.Context.Options.Encoding.EncodingName)); + throw new NotSupportedException( + $"{Context.Context.Options.Encoding.EncodingName} is not supported yet."); } public override bool VisitPrimitiveType(PrimitiveType primitive, TypeQualifiers quals) @@ -714,6 +725,15 @@ public override bool VisitPrimitiveType(PrimitiveType primitive, TypeQualifiers return true; } goto default; + case PrimitiveType.WideChar: + // returned structs must be blittable and char isn't + if (Context.Context.Options.MarshalCharAsManagedChar) + { + Context.Return.Write("global::System.Convert.ToChar({0})", + Context.Parameter.Name); + return true; + } + goto default; case PrimitiveType.Char16: return false; case PrimitiveType.Bool: diff --git a/src/Generator/Generators/CSharp/CSharpSources.cs b/src/Generator/Generators/CSharp/CSharpSources.cs index ddb721470b..d9b0315204 100644 --- a/src/Generator/Generators/CSharp/CSharpSources.cs +++ b/src/Generator/Generators/CSharp/CSharpSources.cs @@ -947,17 +947,17 @@ private void GenerateIndexerSetter(Function function) Write(marshal.Context.Before); var internalFunction = GetFunctionNativeIdentifier(function); + var paramMarshal = GenerateFunctionParamMarshal( + function.Parameters[0], 0, function); if (type.IsPrimitiveType()) { WriteLine($@"*{@internal}.{internalFunction}({ - GetInstanceParam(function)}, {function.Parameters[0].Name}) = { - marshal.Context.Return};"); + GetInstanceParam(function)}, {(paramMarshal.Context == null ? + paramMarshal.Name : paramMarshal.Context.Return)}) = {marshal.Context.Return};"); } else { var typeInternal = TypePrinter.PrintNative(type); - var paramMarshal = GenerateFunctionParamMarshal( - function.Parameters[0], 0, function); WriteLine($@"*({typeInternal}*) {@internal}.{internalFunction}({ GetInstanceParam(function)}, {(paramMarshal.Context == null ? paramMarshal.Name : paramMarshal.Context.Return)}) = {marshal.Context.Return};"); diff --git a/src/Generator/Generators/CSharp/CSharpTypePrinter.cs b/src/Generator/Generators/CSharp/CSharpTypePrinter.cs index 5cae948706..6710fba39c 100644 --- a/src/Generator/Generators/CSharp/CSharpTypePrinter.cs +++ b/src/Generator/Generators/CSharp/CSharpTypePrinter.cs @@ -225,15 +225,7 @@ public override TypePrinterResult VisitPointerType(PointerType pointer, { if (isManagedContext) return "string"; - if (Parameter == null || Parameter.Name == Helpers.ReturnIdentifier) - return IntPtrType; - if (Options.Encoding == Encoding.ASCII) - return string.Format("[MarshalAs(UnmanagedType.LPStr)] string"); - if (Options.Encoding == Encoding.Unicode || - Options.Encoding == Encoding.BigEndianUnicode) - return string.Format("[MarshalAs(UnmanagedType.LPWStr)] string"); - throw new NotSupportedException(string.Format("{0} is not supported yet.", - Options.Encoding.EncodingName)); + return IntPtrType; } var desugared = pointee.Desugar(); @@ -442,6 +434,10 @@ public static void GetPrimitiveTypeWidth(PrimitiveType primitive, width = targetInfo?.CharWidth ?? 8; signed = true; break; + case PrimitiveType.WideChar: + width = targetInfo?.WCharWidth ?? 16; + signed = false; + break; case PrimitiveType.UChar: width = targetInfo?.CharWidth ?? 8; signed = false; @@ -516,8 +512,11 @@ public override TypePrinterResult VisitPrimitiveType(PrimitiveType primitive, "byte" : "bool"; case PrimitiveType.Void: return "void"; case PrimitiveType.Char16: - case PrimitiveType.Char32: - case PrimitiveType.WideChar: return "char"; + case PrimitiveType.Char32: return "char"; + case PrimitiveType.WideChar: + return (ContextKind == TypePrinterContextKind.Native) + ? GetIntString(primitive, Context.TargetInfo) + : "CppSharp.Runtime.WideChar"; case PrimitiveType.Char: // returned structs must be blittable and char isn't return Options.MarshalCharAsManagedChar && diff --git a/src/Generator/Passes/CheckAbiParameters.cs b/src/Generator/Passes/CheckAbiParameters.cs index f1bc913014..85bca509e1 100644 --- a/src/Generator/Passes/CheckAbiParameters.cs +++ b/src/Generator/Passes/CheckAbiParameters.cs @@ -68,7 +68,11 @@ public override bool VisitFunctionDecl(Function function) // Deleting destructors (default in v-table) accept an i32 bitfield as a // second parameter in MS ABI. - if (method != null && method.IsDestructor && Context.ParserOptions.IsMicrosoftAbi) + var @class = method != null ? method.Namespace as Class : null; + if (method != null && + method.IsDestructor && + @class.IsDynamic && + Context.ParserOptions.IsMicrosoftAbi) { method.Parameters.Add(new Parameter { diff --git a/src/Generator/Passes/IgnoreSystemDeclarationsPass.cs b/src/Generator/Passes/IgnoreSystemDeclarationsPass.cs index 988e6ddbcb..20c8a05779 100644 --- a/src/Generator/Passes/IgnoreSystemDeclarationsPass.cs +++ b/src/Generator/Passes/IgnoreSystemDeclarationsPass.cs @@ -62,9 +62,19 @@ public override bool VisitClassDecl(Class @class) { if (method.IsDestructor || method.OriginalName == "c_str" || (method.IsConstructor && method.Parameters.Count == 2 && - method.Parameters[0].Type.Desugar().IsPointerToPrimitiveType(PrimitiveType.Char) && - !method.Parameters[1].Type.Desugar().IsPrimitiveType())) + (( method.Parameters[0].Type.Desugar().IsPointerToPrimitiveType(PrimitiveType.Char) && + !method.Parameters[1].Type.Desugar().IsPrimitiveType()) || + ( method.Parameters[0].Type.Desugar().IsPointerToPrimitiveType(PrimitiveType.WideChar) && + !method.Parameters[1].Type.Desugar().IsPrimitiveType())))) { + if(method.OriginalName == "c_str") + { + if (basicString.Arguments[0].Type.Type.Desugar().IsPrimitiveType(PrimitiveType.WideChar)) + method.Name = method.Name + "W"; + else if(basicString.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; @@ -111,7 +121,8 @@ public override bool VisitClassDecl(Class @class) private static IEnumerable GetCharSpecializations(Class @class) { return @class.Specializations.Where(s => - s.Arguments[0].Type.Type.Desugar().IsPrimitiveType(PrimitiveType.Char)); + s.Arguments[0].Type.Type.Desugar().IsPrimitiveType(PrimitiveType.Char) || + s.Arguments[0].Type.Type.Desugar().IsPrimitiveType(PrimitiveType.WideChar)); } public override bool VisitFunctionDecl(Function function) diff --git a/src/Generator/Types/Std/Stdlib.cs b/src/Generator/Types/Std/Stdlib.cs index 55765e71b4..bf285e50ca 100644 --- a/src/Generator/Types/Std/Stdlib.cs +++ b/src/Generator/Types/Std/Stdlib.cs @@ -29,6 +29,7 @@ public override bool IsIgnored } [TypeMap("basic_string, allocator>")] + [TypeMap("basic_string, allocator>")] public class String : TypeMap { public override string CLISignature(TypePrinterContext ctx) @@ -38,14 +39,24 @@ public override string CLISignature(TypePrinterContext ctx) public override void CLIMarshalToNative(MarshalContext ctx) { - ctx.Return.Write("clix::marshalString({0})", - ctx.Parameter.Name); + var type = ctx.Parameter.Type.Desugar(); + ClassTemplateSpecialization basicString = GetBasicString(type); + + if(basicString.Arguments[0].Type.Type.IsPrimitiveType(PrimitiveType.Char)) + ctx.Return.Write($"clix::marshalString({ctx.Parameter.Name})"); + else + ctx.Return.Write($"clix::marshalString({ctx.Parameter.Name})"); } public override void CLIMarshalToManaged(MarshalContext ctx) { - ctx.Return.Write("clix::marshalString({0})", - ctx.ReturnVarName); + var type = ctx.ReturnType.Type.Desugar(); + ClassTemplateSpecialization basicString = GetBasicString(type); + + if (basicString.Arguments[0].Type.Type.IsPrimitiveType(PrimitiveType.Char)) + ctx.Return.Write($"clix::marshalString({ctx.ReturnVarName})"); + else + ctx.Return.Write($"clix::marshalString({ctx.ReturnVarName})"); } public override string CSharpSignature(TypePrinterContext ctx) @@ -63,30 +74,37 @@ public override void CSharpMarshalToNative(CSharpMarshalContext ctx) var type = ctx.Parameter.Type.Desugar(); ClassTemplateSpecialization basicString = GetBasicString(type); var typePrinter = new CSharpTypePrinter(ctx.Context); + if (!ctx.Parameter.Type.Desugar().IsAddress()) ctx.Return.Write($"*({typePrinter.PrintNative(basicString)}*) "); + + var charType = basicString.Arguments[0].Type; + var allocator = ctx.Context.ASTContext.FindClass("allocator", false, true).First( a => a.IsDependent && a.TranslationUnit.IsSystemHeader); - var allocatorChar = allocator.Specializations.First(s => !s.Ignore); + + var allocatorChar = allocator.Specializations.First( + s => !s.Ignore && s.Arguments[0].Type == charType); + string qualifiedBasicString = GetQualifiedBasicString(basicString); + if (type.IsPointer() || (type.IsReference() && ctx.Declaration is Field)) { ctx.Return.Write($@"{qualifiedBasicString}Extensions.{basicString.Name}({ - ctx.Parameter.Name}, new {allocatorChar.Visit(typePrinter)}()).{ - Helpers.InstanceIdentifier}"); + ctx.Parameter.Name}, new {allocatorChar.Visit(typePrinter)}()).{Helpers.InstanceIdentifier}"); } else { var varAllocator = $"__allocator{ctx.ParameterIndex}"; var varBasicString = $"__basicString{ctx.ParameterIndex}"; + ctx.Before.WriteLine($@"var {varAllocator} = new { allocatorChar.Visit(typePrinter)}();"); - ctx.Before.WriteLine($@"var {varBasicString} = { - qualifiedBasicString}Extensions.{basicString.Name}({ctx.Parameter.Name}, { - varAllocator});"); + ctx.Before.WriteLine($@"var {varBasicString} = {qualifiedBasicString}Extensions.{ + basicString.Name}({ctx.Parameter.Name}, {varAllocator});"); ctx.Return.Write($"{varBasicString}.{Helpers.InstanceIdentifier}"); - ctx.Cleanup.WriteLine($@"{varBasicString}.Dispose({ - (type.IsPointer() ? "true" : "false")});"); + + ctx.Cleanup.WriteLine($@"{varBasicString}.Dispose({(type.IsPointer() ? "true" : "false")});"); ctx.Cleanup.WriteLine($"{varAllocator}.Dispose();"); } } @@ -95,17 +113,19 @@ public override void CSharpMarshalToManaged(CSharpMarshalContext ctx) { var type = ctx.ReturnType.Type.Desugar(); ClassTemplateSpecialization basicString = GetBasicString(type); + var c_str = basicString.Methods.First(m => m.OriginalName == "c_str"); + var typePrinter = new CSharpTypePrinter(ctx.Context); + string qualifiedBasicString = GetQualifiedBasicString(basicString); const string varBasicString = "__basicStringRet"; - ctx.Before.WriteLine($@"var {varBasicString} = { - basicString.Visit(typePrinter)}.{Helpers.CreateInstanceIdentifier}({ - ctx.ReturnVarName});"); + ctx.Before.WriteLine($@"var {varBasicString} = {basicString.Visit(typePrinter) + }.{Helpers.CreateInstanceIdentifier}({ctx.ReturnVarName});"); + if (type.IsAddress()) { - ctx.Return.Write($@"{qualifiedBasicString}Extensions.{c_str.Name}({ - varBasicString})"); + ctx.Return.Write($@"{qualifiedBasicString}Extensions.{c_str.Name}({varBasicString})"); } else { @@ -143,42 +163,6 @@ private static ClassTemplateSpecialization GetBasicString(Type type) } } - [TypeMap("std::wstring", GeneratorKind = GeneratorKind.CLI)] - public class WString : TypeMap - { - public override string CLISignature(TypePrinterContext ctx) - { - return "System::String^"; - } - - public override void CLIMarshalToNative(MarshalContext ctx) - { - ctx.Return.Write("clix::marshalString({0})", - ctx.Parameter.Name); - } - - public override void CLIMarshalToManaged(MarshalContext ctx) - { - ctx.Return.Write("clix::marshalString({0})", - ctx.ReturnVarName); - } - - public override string CSharpSignature(TypePrinterContext ctx) - { - return "string"; - } - - public override void CSharpMarshalToNative(CSharpMarshalContext ctx) - { - ctx.Return.Write("new Std.WString()"); - } - - public override void CSharpMarshalToManaged(CSharpMarshalContext ctx) - { - ctx.Return.Write(ctx.ReturnVarName); - } - } - [TypeMap("std::vector", GeneratorKind = GeneratorKind.CLI)] public class Vector : TypeMap { diff --git a/src/Runtime/Helpers.cs b/src/Runtime/Helpers.cs index 0962c1a464..e49685e4cf 100644 --- a/src/Runtime/Helpers.cs +++ b/src/Runtime/Helpers.cs @@ -20,5 +20,15 @@ public static string MarshalEncodedString(IntPtr ptr, Encoding encoding) return encoding.GetString(buffer); } + + public static IntPtr StringToHGlobalMultiByteUni(string str) + { + byte[] bytes = Encoding.UTF8.GetBytes(str); + + IntPtr nativePtr = Marshal.AllocHGlobal(bytes.Length); + Marshal.Copy(bytes, 0, nativePtr, bytes.Length); + + return nativePtr; + } } } diff --git a/src/Runtime/WideChar.cs b/src/Runtime/WideChar.cs new file mode 100644 index 0000000000..04eb33c69c --- /dev/null +++ b/src/Runtime/WideChar.cs @@ -0,0 +1,93 @@ +namespace CppSharp.Runtime +{ + // Struct added to help map a the wchar_t C++ type to C# + // The data is stored as a 64 bit value so it could represent + // an UTF-32 character but C# only represents UTF-16 characters + // so beware of possible data loss when using it. + public struct WideChar + { + public long Value { get; set; } + + public WideChar(char c) + { + Value = c; + } + + public WideChar(long l) + { + Value = l; + } + + public static implicit operator char(WideChar c) + { + return System.Convert.ToChar(c.Value); + } + + public static implicit operator WideChar(long l) + { + return new WideChar { Value = l }; + } + + public static bool operator ==(WideChar wc, char c) + { + if (ReferenceEquals(null, c)) + return false; + + if (ReferenceEquals(null, wc)) + return false; + + return System.Convert.ToChar(wc.Value) == c; + } + + public static bool operator !=(WideChar wc, char c) + { + return !(wc == c); + } + + public static bool operator ==(WideChar wc, long l) + { + if (ReferenceEquals(null, l)) + return false; + + if (ReferenceEquals(null, wc)) + return false; + + return wc.Value == l; + } + + public static bool operator !=(WideChar wc, long l) + { + return !(wc == l); + } + + public bool Equals(WideChar obj) + { + if (ReferenceEquals(null, obj)) + return false; + + if (ReferenceEquals(this, obj)) + return true; + + return Value.Equals(obj.Value); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + return false; + + if (ReferenceEquals(this, obj)) + return true; + + return obj.GetType() == GetType() && Equals((WideChar)obj); + } + + public override int GetHashCode() + { + unchecked + { + return Value.GetHashCode(); + } + } + } +} diff --git a/tests/Common/Common.Tests.cs b/tests/Common/Common.Tests.cs index 92cec34daa..0858501845 100644 --- a/tests/Common/Common.Tests.cs +++ b/tests/Common/Common.Tests.cs @@ -765,6 +765,60 @@ This is a very long string. This is a very long string. This is a very long stri } } + [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 unicodeString1 = "你好"; + const string unicodeString2 = "Ÿ‰ϰ"; + + 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."; + + 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 TestWideCharCompatibility() + { + var c = new CppSharp.Runtime.WideChar('c'); + + Assert.That(c == 'a', Is.False); + Assert.That(Common.WideCharCompatibility('a') == 'c', Is.False); + + Assert.That(c == 'c', Is.True); + Assert.That(Common.WideCharCompatibility('a') == 'a', Is.True); + + Assert.That(c != 'c', Is.False); + Assert.That(Common.WideCharCompatibility('a') != 'a', Is.False); + + Assert.That(c != 'a', Is.True); + Assert.That(Common.WideCharCompatibility('a') != 'c', Is.True); + } + private class CustomDerivedFromVirtual : AbstractWithVirtualDtor { public override void Abstract() diff --git a/tests/Common/Common.cpp b/tests/Common/Common.cpp index 433e9e2ed5..d9779da6ee 100644 --- a/tests/Common/Common.cpp +++ b/tests/Common/Common.cpp @@ -499,6 +499,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::getStdWString() +{ + return s; +} + +wchar_t WideCharCompatibility(wchar_t c) +{ + return c; +} + TestProperties::TestProperties() : Field(0), _refToPrimitiveInSetter(0), _getterAndSetterWithTheSameName(0), _setterReturnsBoolean(0), _virtualSetterReturnsBoolean(0) { diff --git a/tests/Common/Common.h b/tests/Common/Common.h index 6fce4338b5..7dd7f9b400 100644 --- a/tests/Common/Common.h +++ b/tests/Common/Common.h @@ -851,6 +851,18 @@ class DLL_API HasStdString std::string& getStdString(); }; +class DLL_API HasStdWString +{ +public: + HasStdWString(); + ~HasStdWString(); + std::wstring testStdWString(const std::wstring& s); + std::wstring s; + std::wstring& getStdWString(); +}; + +DLL_API wchar_t WideCharCompatibility(wchar_t c); + class DLL_API InternalCtorAmbiguity { public: