Skip to content

Commit

Permalink
Added support for wchar_t and std::wstring for C#
Browse files Browse the repository at this point in the history
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
  • Loading branch information
zillemarco committed Oct 12, 2017
1 parent c818f08 commit 41de24c
Show file tree
Hide file tree
Showing 13 changed files with 327 additions and 115 deletions.
1 change: 1 addition & 0 deletions build/Tests.lua
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ function SetupTestProjectsCLI(name, extraFiles, suffix)
dependson { name .. ".Native" }

LinkNUnit()
links { "CppSharp.Runtime" }
end

function IncludeExamples()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ 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) const char* std::basic_string<char, std::char_traits<char>, std::allocator<char>>::c_str() const noexcept;
template __declspec(dllexport) std::allocator<char>::allocator() noexcept;

template __declspec(dllexport) std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t>>::basic_string(const wchar_t* const, const std::allocator<wchar_t>&);
template __declspec(dllexport) std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t>>::~basic_string() noexcept;
template __declspec(dllexport) const wchar_t* std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t>>::c_str() const noexcept;
template __declspec(dllexport) std::allocator<wchar_t>::allocator() noexcept;
102 changes: 59 additions & 43 deletions src/Generator/Generators/CSharp/CSharpMarshal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
{
Expand All @@ -155,19 +157,17 @@ 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())
if (finalPointee.IsPrimitiveType(out PrimitiveType primitive) || finalPointee.IsEnumType())
{
if (isRefParam)
{
Expand Down Expand Up @@ -237,11 +237,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:
Expand All @@ -265,16 +267,15 @@ public override bool VisitTypedefType(TypedefType typedef, TypeQualifiers quals)

var decl = typedef.Declaration;

var functionType = decl.Type as FunctionType;
if (functionType != null || decl.Type.IsPointerTo(out functionType))
if (decl.Type is FunctionType functionType || decl.Type.IsPointerTo(out functionType))
{
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;
}
Expand All @@ -286,11 +287,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;
}

Expand All @@ -316,7 +316,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;
}

Expand All @@ -340,8 +340,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}",
Expand Down Expand Up @@ -512,12 +511,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
{
Expand Down Expand Up @@ -591,6 +594,8 @@ public override bool VisitPointerType(PointerType pointer, TypeQualifiers quals)

var param = Context.Parameter;
var isRefParam = param != null && (param.IsInOut || param.IsOut);
var finalPointee = pointer.GetFinalPointee();
bool finalPointeeIsPrimitiveType = finalPointee.IsPrimitiveType(out PrimitiveType primitive);

if (CSharpTypePrinter.IsConstCharString(pointee) && isRefParam)
{
Expand All @@ -601,12 +606,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;
Expand Down Expand Up @@ -639,9 +644,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
Expand All @@ -667,13 +670,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);
}
Expand All @@ -684,19 +685,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)
Expand All @@ -714,6 +721,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:
Expand Down
8 changes: 4 additions & 4 deletions src/Generator/Generators/CSharp/CSharpSources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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};");
Expand Down
21 changes: 10 additions & 11 deletions src/Generator/Generators/CSharp/CSharpTypePrinter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,15 +225,7 @@ public static bool IsConstCharString(QualifiedType qualType)
{
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();
Expand Down Expand Up @@ -442,6 +434,10 @@ public override TypePrinterResult VisitCILType(CILType type, TypeQualifiers qual
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;
Expand Down Expand Up @@ -516,8 +512,11 @@ static string GetIntString(PrimitiveType primitive, ParserTargetInfo targetInfo)
"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 &&
Expand Down
6 changes: 5 additions & 1 deletion src/Generator/Passes/CheckAbiParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
Loading

0 comments on commit 41de24c

Please sign in to comment.