Tools and libraries to glue C/C++ APIs to high-level languages
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

678 lines
23 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CppSharp.AST;
using CppSharp.AST.Extensions;
using CppSharp.Types;
using Type = CppSharp.AST.Type;
namespace CppSharp.Generators.CSharp
{
public enum CSharpMarshalKind
{
Unknown,
NativeField
}
public class CSharpMarshalContext : MarshalContext
{
public CSharpMarshalContext(Driver driver)
: base(driver)
{
Kind = CSharpMarshalKind.Unknown;
ArgumentPrefix = new TextGenerator();
Cleanup = new TextGenerator();
}
public CSharpMarshalKind Kind { get; set; }
public QualifiedType FullType;
public TextGenerator ArgumentPrefix { get; private set; }
public TextGenerator Cleanup { get; private set; }
}
public abstract class CSharpMarshalPrinter : MarshalPrinter
{
public CSharpMarshalContext CSharpContext
{
get { return Context as CSharpMarshalContext; }
}
protected CSharpMarshalPrinter(CSharpMarshalContext context)
: base(context)
{
Options.VisitFunctionParameters = false;
Options.VisitTemplateArguments = false;
}
public override bool VisitMemberPointerType(MemberPointerType member,
TypeQualifiers quals)
{
return false;
}
}
public class CSharpMarshalNativeToManagedPrinter : CSharpMarshalPrinter
{
public CSharpMarshalNativeToManagedPrinter(CSharpMarshalContext context)
: base(context)
{
Context.MarshalToManaged = this;
}
public int VarSuffix { get; set; }
public static string QualifiedIdentifier(Declaration decl)
{
var names = new List<string> { decl.Name };
var ctx = decl.Namespace;
while (ctx != null)
{
if (!string.IsNullOrWhiteSpace(ctx.Name))
names.Add(ctx.Name);
ctx = ctx.Namespace;
}
//if (Options.GenerateLibraryNamespace)
// names.Add(Options.OutputNamespace);
names.Reverse();
return string.Join(".", names);
}
public override bool VisitType(Type type, TypeQualifiers quals)
{
TypeMap typeMap;
if (Context.Driver.TypeDatabase.FindTypeMap(type, out typeMap) && typeMap.DoesMarshalling)
{
typeMap.Type = type;
typeMap.CSharpMarshalToManaged(Context);
return false;
}
return true;
}
public override bool VisitDeclaration(Declaration decl)
{
TypeMap typeMap;
if (Context.Driver.TypeDatabase.FindTypeMap(decl, out typeMap) && typeMap.DoesMarshalling)
{
typeMap.Declaration = decl;
typeMap.CSharpMarshalToManaged(Context);
return false;
}
return true;
}
public override bool VisitArrayType(ArrayType array, TypeQualifiers quals)
{
if (!VisitType(array, quals))
return false;
switch (array.SizeType)
{
case ArrayType.ArraySize.Constant:
var supportBefore = Context.SupportBefore;
string value = Generator.GeneratedIdentifier("value");
supportBefore.WriteLine("{0}[] {1} = null;", array.Type, value, array.Size);
supportBefore.WriteLine("if ({0} != null)", Context.ReturnVarName);
supportBefore.WriteStartBraceIndent();
supportBefore.WriteLine("{0} = new {1}[{2}];", value, array.Type, array.Size);
supportBefore.WriteLine("for (int i = 0; i < {0}; i++)", array.Size);
supportBefore.WriteLineIndent("{0}[i] = {1}[i];", value, Context.ReturnVarName);
supportBefore.WriteCloseBraceIndent();
Context.Return.Write(value);
break;
case ArrayType.ArraySize.Variable:
Context.Return.Write("null");
break;
}
return true;
}
public override bool VisitPointerType(PointerType pointer, TypeQualifiers quals)
{
if (!VisitType(pointer, quals))
return false;
var pointee = pointer.Pointee.Desugar();
if (CSharpTypePrinter.IsConstCharString(pointer))
{
Context.Return.Write(MarshalStringToManaged(Context.ReturnVarName));
return true;
}
PrimitiveType primitive;
if (pointee.IsPrimitiveType(out primitive))
{
var param = Context.Parameter;
if (param != null && (param.IsOut || param.IsInOut))
{
Context.Return.Write("_{0}", param.Name);
return true;
}
Context.Return.Write(Context.ReturnVarName);
return true;
}
return pointer.Pointee.Visit(this, quals);
}
private string MarshalStringToManaged(string varName)
{
if (Equals(Context.Driver.Options.Encoding, Encoding.ASCII))
{
return string.Format("Marshal.PtrToStringAnsi({0})", varName);
}
if (Equals(Context.Driver.Options.Encoding, Encoding.Unicode) ||
Equals(Context.Driver.Options.Encoding, Encoding.BigEndianUnicode))
{
return string.Format("Marshal.PtrToStringUni({0})", varName);
}
throw new NotSupportedException(string.Format("{0} is not supported yet.",
Context.Driver.Options.Encoding.EncodingName));
}
public override bool VisitPrimitiveType(PrimitiveType primitive, TypeQualifiers quals)
{
switch (primitive)
{
case PrimitiveType.Void:
return true;
case PrimitiveType.Bool:
case PrimitiveType.Int8:
case PrimitiveType.UInt8:
case PrimitiveType.Int16:
case PrimitiveType.UInt16:
case PrimitiveType.Int32:
case PrimitiveType.UInt32:
case PrimitiveType.Int64:
case PrimitiveType.UInt64:
case PrimitiveType.Float:
case PrimitiveType.Double:
Context.Return.Write(Context.ReturnVarName);
return true;
case PrimitiveType.Char16:
case PrimitiveType.WideChar:
return false;
}
throw new NotImplementedException();
}
public override bool VisitTypedefType(TypedefType typedef, TypeQualifiers quals)
{
if (!VisitType(typedef, quals))
return false;
var decl = typedef.Declaration;
FunctionType function;
if (decl.Type.IsPointerTo(out function))
{
var ptrName = Generator.GeneratedIdentifier("ptr") +
Context.ParameterIndex;
Context.SupportBefore.WriteLine("var {0} = {1};", ptrName,
Context.ReturnVarName);
Context.Return.Write("({1})Marshal.GetDelegateForFunctionPointer({0}, typeof({1}))",
ptrName, typedef.ToString());
return true;
}
return decl.Type.Visit(this);
}
public override bool VisitClassDecl(Class @class)
{
var ctx = Context as CSharpMarshalContext;
string instance = Context.ReturnVarName;
if (@class.IsRefType &&
(Context.ReturnType.Qualifiers.IsConst || !Context.ReturnType.Type.IsAddress()) &&
(!Context.Driver.Options.GenerateAbstractImpls || !@class.IsAbstract))
{
var instanceName = Generator.GeneratedIdentifier("instance");
if (VarSuffix > 0)
instanceName += VarSuffix;
if (@class.HasNonTrivialCopyConstructor)
{
// Find a valid copy constructor overload.
var copyCtorMethod = @class.Methods.FirstOrDefault(method =>
method.IsCopyConstructor);
if (copyCtorMethod == null)
throw new NotSupportedException("Expected a valid copy constructor");
// Call the copy constructor.
TypeMap typeMap;
if (copyCtorMethod.Ignore && FindTypeMap(ctx.Driver.TypeDatabase, @class, out typeMap))
{
typeMap.CSharpMarshalCopyCtorToManaged(Context);
}
else
{
// Allocate memory for a new native object and call the ctor.
Context.SupportBefore.WriteLine("var {0} = Marshal.AllocHGlobal({1});",
instanceName, @class.Layout.Size);
Context.SupportBefore.WriteLine("{0}.Internal.{1}({2}, new global::System.IntPtr(&{3}));",
QualifiedIdentifier(@class),
CSharpTextTemplate.GetFunctionNativeIdentifier(copyCtorMethod),
instanceName, instance);
}
}
else
{
// Allocate memory for a new native object and call the ctor.
Context.SupportBefore.WriteLine("var {0} = Marshal.AllocHGlobal({1});",
instanceName, @class.Layout.Size);
instance = instance.Trim('*');
Context.SupportBefore.WriteLine(
"CppSharp.Runtime.Helpers.memcpy({0}, new IntPtr(&{1}), new UIntPtr({2}));",
instanceName, instance, @class.Layout.Size);
}
instance = instanceName;
}
if (@class.IsRefType)
Context.Return.Write("({0} == IntPtr.Zero) ? null : ",
instance);
Context.Return.Write("new {0}({1})",
QualifiedIdentifier(@class.OriginalClass ?? @class) +
(Context.Driver.Options.GenerateAbstractImpls && @class.IsAbstract ?
"Internal" : ""),
instance);
return true;
}
private static bool FindTypeMap(ITypeMapDatabase typeMapDatabase,
Class @class, out TypeMap typeMap)
{
return typeMapDatabase.FindTypeMap(@class, out typeMap) ||
(@class.HasBase && FindTypeMap(typeMapDatabase, @class.Bases[0].Class, out typeMap));
}
public override bool VisitEnumDecl(Enumeration @enum)
{
Context.Return.Write("{0}", Context.ReturnVarName);
return true;
}
public override bool VisitParameterDecl(Parameter parameter)
{
if (parameter.Usage == ParameterUsage.Unknown || parameter.IsIn)
return base.VisitParameterDecl(parameter);
var ctx = new CSharpMarshalContext(base.Context.Driver)
{
ReturnType = base.Context.ReturnType,
ReturnVarName = base.Context.ReturnVarName
};
var marshal = new CSharpMarshalNativeToManagedPrinter(ctx);
parameter.Type.Visit(marshal);
if (!string.IsNullOrWhiteSpace(ctx.SupportBefore))
Context.SupportBefore.WriteLine(ctx.SupportBefore);
if (!string.IsNullOrWhiteSpace(ctx.Return))
{
Context.SupportBefore.WriteLine("var _{0} = {1};", parameter.Name,
ctx.Return);
}
Context.Return.Write("_{0}", parameter.Name);
return true;
}
}
public class CSharpMarshalManagedToNativePrinter : CSharpMarshalPrinter
{
public CSharpMarshalManagedToNativePrinter(CSharpMarshalContext context)
: base(context)
{
Context.MarshalToNative = this;
}
public override bool VisitType(Type type, TypeQualifiers quals)
{
TypeMap typeMap;
if (Context.Driver.TypeDatabase.FindTypeMap(type, out typeMap) && typeMap.DoesMarshalling)
{
typeMap.Type = type;
typeMap.CSharpMarshalToNative(Context);
return false;
}
return true;
}
public override bool VisitDeclaration(Declaration decl)
{
TypeMap typeMap;
if (Context.Driver.TypeDatabase.FindTypeMap(decl, out typeMap) && typeMap.DoesMarshalling)
{
typeMap.Declaration = decl;
typeMap.CSharpMarshalToNative(Context);
return false;
}
return true;
}
public override bool VisitArrayType(ArrayType array, TypeQualifiers quals)
{
if (!VisitType(array, quals))
return false;
switch (array.SizeType)
{
case ArrayType.ArraySize.Constant:
var supportBefore = Context.SupportBefore;
supportBefore.WriteLine("if ({0} != null)", Context.ArgName);
supportBefore.WriteStartBraceIndent();
supportBefore.WriteLine("for (int i = 0; i < {0}; i++)", array.Size);
supportBefore.WriteLineIndent("{0}[i] = {1}[i];",
Context.ReturnVarName, Context.ArgName);
supportBefore.WriteCloseBraceIndent();
break;
default:
Context.Return.Write("null");
break;
}
return true;
}
public bool VisitDelegateType(FunctionType function, string type)
{
Context.Return.Write("Marshal.GetFunctionPointerForDelegate({0})",
Context.Parameter.Name);
return true;
}
public override bool VisitPointerType(PointerType pointer, TypeQualifiers quals)
{
if (!VisitType(pointer, quals))
return false;
var pointee = pointer.Pointee.Desugar();
if ((pointee.IsPrimitiveType(PrimitiveType.Char) ||
pointee.IsPrimitiveType(PrimitiveType.WideChar)) &&
pointer.QualifiedPointee.Qualifiers.IsConst)
{
Context.Return.Write(MarshalStringToUnmanaged(Context.Parameter.Name));
CSharpContext.Cleanup.WriteLine("Marshal.FreeHGlobal({0});", Context.ArgName);
return true;
}
if (pointee is FunctionType)
{
var function = pointee as FunctionType;
return VisitDelegateType(function, function.ToString());
}
Class @class;
if (pointee.IsTagDecl(out @class) && @class.IsValueType)
{
if (Context.Parameter.Usage == ParameterUsage.Out)
{
Context.SupportBefore.WriteLine("var {0} = new {1}.Internal();",
Generator.GeneratedIdentifier(Context.ArgName), @class.Name);
}
else
{
Context.SupportBefore.WriteLine("var {0} = {1}.ToInternal();",
Generator.GeneratedIdentifier(Context.ArgName),
Context.Parameter.Name);
}
Context.Return.Write("new global::System.IntPtr(&{0})",
Generator.GeneratedIdentifier(Context.ArgName));
return true;
}
PrimitiveType primitive;
if (pointee.IsPrimitiveType(out primitive))
{
var param = Context.Parameter;
// From MSDN: "note that a ref or out parameter is classified as a moveable
// variable". This means we must create a local variable to hold the result
// and then assign this value to the parameter.
if (param.IsOut || param.IsInOut)
{
var typeName = Type.TypePrinterDelegate(pointee);
Context.SupportBefore.WriteLine("{0} _{1};", typeName, param.Name);
Context.Return.Write("&_{0}", param.Name);
}
else
Context.Return.Write(Context.Parameter.Name);
return true;
}
return pointer.Pointee.Visit(this, quals);
}
private string MarshalStringToUnmanaged(string varName)
{
if (Equals(Context.Driver.Options.Encoding, Encoding.ASCII))
{
return string.Format("Marshal.StringToHGlobalAnsi({0})", varName);
}
if (Equals(Context.Driver.Options.Encoding, Encoding.Unicode) ||
Equals(Context.Driver.Options.Encoding, Encoding.BigEndianUnicode))
{
return string.Format("Marshal.StringToHGlobalUni({0})", varName);
}
throw new NotSupportedException(string.Format("{0} is not supported yet.",
Context.Driver.Options.Encoding.EncodingName));
}
public override bool VisitPrimitiveType(PrimitiveType primitive, TypeQualifiers quals)
{
switch (primitive)
{
case PrimitiveType.Void:
return true;
case PrimitiveType.Bool:
case PrimitiveType.Int8:
case PrimitiveType.UInt8:
case PrimitiveType.Int16:
case PrimitiveType.UInt16:
case PrimitiveType.Int32:
case PrimitiveType.UInt32:
case PrimitiveType.Int64:
case PrimitiveType.UInt64:
case PrimitiveType.Float:
case PrimitiveType.Double:
Context.Return.Write(Context.Parameter.Name);
return true;
case PrimitiveType.Char16:
case PrimitiveType.WideChar:
return false;
}
return false;
}
public override bool VisitTypedefType(TypedefType typedef, TypeQualifiers quals)
{
if (!VisitType(typedef, quals))
return false;
var decl = typedef.Declaration;
FunctionType func;
if (decl.Type.IsPointerTo<FunctionType>(out func))
{
VisitDelegateType(func, typedef.Declaration.OriginalName);
return true;
}
return decl.Type.Visit(this);
}
public override bool VisitTemplateParameterType(TemplateParameterType param, TypeQualifiers quals)
{
Context.Return.Write(param.Parameter.Name);
return true;
}
public override bool VisitClassDecl(Class @class)
{
if (!VisitDeclaration(@class))
return false;
if (@class.IsValueType)
{
MarshalValueClass();
}
else
{
MarshalRefClass(@class);
}
return true;
}
private void MarshalRefClass(Class @class)
{
var method = Context.Function as Method;
if (method != null
&& method.Conversion == MethodConversionKind.FunctionToInstanceMethod
&& Context.ParameterIndex == 0)
{
Context.Return.Write("{0}", Helpers.InstanceIdentifier);
return;
}
string param = Context.Parameter.Name;
Type type = Context.Parameter.Type.Desugar();
if (type.IsAddress())
{
Class decl;
if (type.IsTagDecl(out decl) && decl.IsValueType)
Context.Return.Write("{0}.{1}", param, Helpers.InstanceIdentifier);
else
Context.Return.Write("{0} == ({2}) null ? global::System.IntPtr.Zero : {0}.{1}", param,
Helpers.InstanceIdentifier, type);
return;
}
var qualifiedIdentifier = CSharpMarshalNativeToManagedPrinter.QualifiedIdentifier(
@class.OriginalClass ?? @class);
Context.Return.Write(
"ReferenceEquals({0}, null) ? new {1}.Internal() : *({1}.Internal*) ({0}.{2})", param,
qualifiedIdentifier, Helpers.InstanceIdentifier);
}
private void MarshalValueClass()
{
Context.Return.Write("{0}.ToInternal()", Context.Parameter.Name);
}
public override bool VisitFieldDecl(Field field)
{
if (!VisitDeclaration(field))
return false;
Context.Parameter = new Parameter
{
Name = Context.ArgName,
QualifiedType = field.QualifiedType
};
return field.Type.Visit(this, field.QualifiedType.Qualifiers);
}
public override bool VisitProperty(Property property)
{
if (!VisitDeclaration(property))
return false;
Context.Parameter = new Parameter
{
Name = Context.ArgName,
QualifiedType = property.QualifiedType
};
return base.VisitProperty(property);
}
public override bool VisitEnumDecl(Enumeration @enum)
{
Context.Return.Write(Context.Parameter.Name);
return true;
}
public override bool VisitClassTemplateDecl(ClassTemplate template)
{
return VisitClassDecl(template.TemplatedClass);
}
public override bool VisitFunctionTemplateDecl(FunctionTemplate template)
{
return template.TemplatedFunction.Visit(this);
}
}
public static class CSharpMarshalExtensions
{
public static void CSharpMarshalToNative(this QualifiedType type,
CSharpMarshalManagedToNativePrinter printer)
{
(printer.Context as CSharpMarshalContext).FullType = type;
type.Visit(printer);
}
public static void CSharpMarshalToNative(this Type type,
CSharpMarshalManagedToNativePrinter printer)
{
CSharpMarshalToNative(new QualifiedType(type), printer);
}
public static void CSharpMarshalToNative(this ITypedDecl decl,
CSharpMarshalManagedToNativePrinter printer)
{
CSharpMarshalToNative(decl.QualifiedType, printer);
}
public static void CSharpMarshalToManaged(this QualifiedType type,
CSharpMarshalNativeToManagedPrinter printer)
{
(printer.Context as CSharpMarshalContext).FullType = type;
type.Visit(printer);
}
public static void CSharpMarshalToManaged(this Type type,
CSharpMarshalNativeToManagedPrinter printer)
{
CSharpMarshalToManaged(new QualifiedType(type), printer);
}
public static void CSharpMarshalToManaged(this ITypedDecl decl,
CSharpMarshalNativeToManagedPrinter printer)
{
CSharpMarshalToManaged(decl.QualifiedType, printer);
}
}
}