diff --git a/src/Bridge/Type.cs b/src/Bridge/Type.cs index fc4a2e2b..f3fe41a0 100644 --- a/src/Bridge/Type.cs +++ b/src/Bridge/Type.cs @@ -138,6 +138,11 @@ namespace Cxxi /// public struct QualifiedType { + public QualifiedType(Type type) : this() + { + Type = type; + } + public Type Type { get; set; } public TypeQualifiers Qualifiers { get; set; } @@ -402,7 +407,8 @@ namespace Cxxi Int64, UInt64, Float, - Double + Double, + IntPtr } /// diff --git a/src/Generator/Generators/CSharp/CSharpMarshal.cs b/src/Generator/Generators/CSharp/CSharpMarshal.cs new file mode 100644 index 00000000..4decb793 --- /dev/null +++ b/src/Generator/Generators/CSharp/CSharpMarshal.cs @@ -0,0 +1,555 @@ +using System.Text; +using Cxxi.Generators.CLI; +using Cxxi.Types; + +namespace Cxxi.Generators.CSharp +{ + public class CSharpMarshalContext : MarshalContext + { + public CSharpMarshalContext(Driver driver) + : base(driver) + { + Cleanup = new TextGenerator(); + } + + 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) + { + + } + } + + public class CSharpMarshalNativeToManagedPrinter : CSharpMarshalPrinter + { + public CSharpMarshalNativeToManagedPrinter(CSharpMarshalContext context) + : base(context) + { + Context.MarshalToManaged = this; + } + + public override bool VisitTagType(TagType tag, TypeQualifiers quals) + { + var decl = tag.Declaration; + return decl.Visit(this); + } + + public override bool VisitArrayType(ArrayType array, TypeQualifiers quals) + { + switch (array.SizeType) + { + case ArrayType.ArraySize.Constant: + Context.Return.Write("null"); + break; + case ArrayType.ArraySize.Variable: + Context.Return.Write("null"); + break; + } + + return true; + } + + public override bool VisitFunctionType(FunctionType function, TypeQualifiers quals) + { + var returnType = function.ReturnType; + return returnType.Visit(this, quals); + } + + public override bool VisitPointerType(PointerType pointer, TypeQualifiers quals) + { + var pointee = pointer.Pointee; + + if (pointee.IsPrimitiveType(PrimitiveType.Void, walkTypedefs: true)) + { + Context.Return.Write("new IntPtr({0})", Context.ReturnVarName); + return true; + } + + if (pointee.IsPrimitiveType(PrimitiveType.Char)) + { + Context.Return.Write("Marshal.StringToHGlobalAnsi({0})", + Context.ReturnVarName); + return true; + } + + PrimitiveType primitive; + if (pointee.IsPrimitiveType(out primitive, walkTypedefs: true)) + { + Context.Return.Write("new IntPtr({0})", Context.ReturnVarName); + return true; + } + + if (!pointee.Visit(this, quals)) + return false; + + return true; + } + + public override bool VisitMemberPointerType(MemberPointerType member, + TypeQualifiers quals) + { + return false; + } + + public override bool VisitBuiltinType(BuiltinType builtin, TypeQualifiers quals) + { + return VisitPrimitiveType(builtin.Type); + } + + public bool VisitPrimitiveType(PrimitiveType primitive) + { + 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.WideChar: + return false; + } + + return false; + } + + public override bool VisitTypedefType(TypedefType typedef, TypeQualifiers quals) + { + var decl = typedef.Declaration; + + TypeMap typeMap = null; + if (Context.Driver.TypeDatabase.FindTypeMap(decl, out typeMap)) + { + typeMap.Type = typedef; + typeMap.CLIMarshalToManaged(Context); + return typeMap.IsValueType; + } + + FunctionType function; + if (decl.Type.IsPointerTo(out function)) + { + Context.Return.Write("safe_cast<{0}>(", typedef); + Context.Return.Write("System::Runtime::InteropServices::Marshal::"); + Context.Return.Write("GetDelegateForFunctionPointer("); + Context.Return.Write("IntPtr({0}), {1}::typeid))", Context.ReturnVarName, + typedef.ToString().TrimEnd('^')); + return true; + } + + return decl.Type.Visit(this); + } + + public override bool VisitTemplateSpecializationType(TemplateSpecializationType template, + TypeQualifiers quals) + { + TypeMap typeMap; + if (Context.Driver.TypeDatabase.FindTypeMap(template, out typeMap)) + { + typeMap.Type = template; + typeMap.CSharpMarshalToManaged(Context); + return true; + } + + return template.Template.Visit(this); + } + + public override bool VisitClassDecl(Class @class) + { + var instance = string.Empty; + + if (!Context.ReturnType.IsPointer()) + instance += "&"; + + instance += Context.ReturnVarName; + + WriteClassInstance(@class, instance); + return true; + } + + public string QualifiedIdentifier(Declaration decl) + { + if (Context.Driver.Options.GenerateLibraryNamespace) + return string.Format("{0}::{1}", Context.Driver.Options.OutputNamespace, + decl.QualifiedName); + return string.Format("{0}", decl.QualifiedName); + } + + public void WriteClassInstance(Class @class, string instance) + { + if (@class.IsRefType) + Context.Return.Write("gcnew "); + + Context.Return.Write("{0}(", QualifiedIdentifier(@class)); + Context.Return.Write("(::{0}*)", @class.QualifiedOriginalName); + Context.Return.Write("{0})", instance); + } + + public override bool VisitFieldDecl(Field field) + { + return field.Type.Visit(this); + } + + public override bool VisitParameterDecl(Parameter parameter) + { + return parameter.Type.Visit(this, parameter.QualifiedType.Qualifiers); + } + + public override bool VisitEnumDecl(Enumeration @enum) + { + Context.Return.Write("({0}){1}", ToCLITypeName(@enum), + Context.ReturnVarName); + return true; + } + + public override bool VisitVariableDecl(Variable variable) + { + return variable.Type.Visit(this, variable.QualifiedType.Qualifiers); + } + + private string ToCLITypeName(Declaration decl) + { + var typePrinter = new CLITypePrinter(Context.Driver); + return typePrinter.VisitDeclaration(decl); + } + + public override bool VisitClassTemplateDecl(ClassTemplate template) + { + return template.TemplatedClass.Visit(this); + } + } + + public class CSharpMarshalManagedToNativePrinter : CSharpMarshalPrinter + { + public CSharpMarshalManagedToNativePrinter(CSharpMarshalContext context) + : base(context) + { + Context.MarshalToNative = this; + } + + public override bool VisitFunctionType(FunctionType function, TypeQualifiers quals) + { + var returnType = function.ReturnType; + return returnType.Visit(this, quals); + } + + public bool VisitDelegateType(FunctionType function, string type) + { + // We marshal function pointer types by calling + // GetFunctionPointerForDelegate to get a native function + // pointer ouf of the delegate. Then we can pass it in the + // native call. Since references are not tracked in the + // native side, we need to be careful and protect it with an + // explicit GCHandle if necessary. + + var sb = new StringBuilder(); + sb.AppendFormat("static_cast<::{0}>(", type); + sb.Append("System::Runtime::InteropServices::Marshal::"); + sb.Append("GetFunctionPointerForDelegate("); + sb.AppendFormat("{0}).ToPointer())", Context.Parameter.Name); + Context.Return.Write(sb.ToString()); + + return true; + } + + public override bool VisitPointerType(PointerType pointer, TypeQualifiers quals) + { + var pointee = pointer.Pointee; + + var isVoidPtr = pointee.IsPrimitiveType(PrimitiveType.Void, + walkTypedefs: true); + + var isUInt8Ptr = pointee.IsPrimitiveType(PrimitiveType.UInt8, + walkTypedefs: true); + + if (isVoidPtr || isUInt8Ptr) + { + if (isUInt8Ptr) + Context.Return.Write("({0})", "uint8*"); + Context.Return.Write("{0}.ToPointer()", Context.Parameter.Name); + return true; + } + + if (pointee.IsPrimitiveType(PrimitiveType.Char) || + pointee.IsPrimitiveType(PrimitiveType.WideChar)) + { + Context.Return.Write("Marshal.StringToHGlobalAnsi({0})", + Context.Parameter.Name); + CSharpContext.Cleanup.WriteLine("Marshal.FreeHGlobal({0});", + Context.ArgName); + return true; + } + + if (pointee is FunctionType) + { + var function = pointee as FunctionType; + // TODO: We have to translate the function type typedef to C/C++ + return VisitDelegateType(function, function.ToString()); + } + + Class @class; + if (pointee.IsTagDecl(out @class)) + { + if (@class.IsRefType) + Context.Return.Write("{0}.Instance", Context.Parameter.Name); + else + Context.Return.Write("new IntPtr(&{0})", Context.Parameter.Name); + return true; + } + + return pointee.Visit(this, quals); + } + + public override bool VisitMemberPointerType(MemberPointerType member, + TypeQualifiers quals) + { + return false; + } + + public override bool VisitBuiltinType(BuiltinType builtin, TypeQualifiers quals) + { + return VisitPrimitiveType(builtin.Type); + } + + public bool VisitPrimitiveType(PrimitiveType primitive) + { + 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.WideChar: + return false; + } + + return false; + } + + public override bool VisitTypedefType(TypedefType typedef, TypeQualifiers quals) + { + var decl = typedef.Declaration; + + TypeMap typeMap = null; + if (Context.Driver.TypeDatabase.FindTypeMap(decl, out typeMap)) + { + typeMap.CSharpMarshalToNative(Context); + return typeMap.IsValueType; + } + + FunctionType func; + if (decl.Type.IsPointerTo(out func)) + { + VisitDelegateType(func, typedef.Declaration.OriginalName); + return true; + } + + PrimitiveType primitive; + if (decl.Type.IsPrimitiveType(out primitive, walkTypedefs: true)) + { + Context.Return.Write("({0})", typedef.Declaration.Name); + } + + return decl.Type.Visit(this); + } + + public override bool VisitTemplateSpecializationType(TemplateSpecializationType template, + TypeQualifiers quals) + { + TypeMap typeMap = null; + if (Context.Driver.TypeDatabase.FindTypeMap(template, out typeMap)) + { + typeMap.Type = template; + typeMap.CSharpMarshalToNative(Context); + return true; + } + + return template.Template.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 (@class.IsValueType) + { + MarshalValueClass(@class); + } + else + { + MarshalRefClass(@class); + } + + return true; + } + + private void MarshalRefClass(Class @class) + { + TypeMap typeMap = null; + if (Context.Driver.TypeDatabase.FindTypeMap(@class, out typeMap)) + { + typeMap.CLIMarshalToNative(Context); + return; + } + + var method = Context.Function as Method; + if (method != null + && method.Conversion == MethodConversionKind.FunctionToInstanceMethod + && Context.ParameterIndex == 0) + { + Context.Return.Write("Instance"); + return; + } + + if (Context.Parameter.Type.IsPointer()) + Context.Return.Write("{0}.Instance", Context.Parameter.Name); + else + Context.Return.Write("{0}", Context.Parameter.Name); + } + + private void MarshalValueClass(Class @class) + { + + var marshalVar = "_marshal" + Context.ParameterIndex++; + + Context.SupportBefore.WriteLine("auto {0} = ::{1}();", marshalVar, + @class.QualifiedOriginalName); + + MarshalValueClassFields(@class, marshalVar); + + Context.Return.Write(marshalVar); + } + + private void MarshalValueClassFields(Class @class, string marshalVar) + { + foreach (var @base in @class.Bases) + { + if (!@base.IsClass || @base.Class.Ignore) + continue; + + var baseClass = @base.Class; + MarshalValueClassFields(baseClass, marshalVar); + } + + foreach (var field in @class.Fields) + { + if (field.Ignore) + continue; + + MarshalValueClassField(field, marshalVar); + } + } + + private void MarshalValueClassField(Field field, string marshalVar) + { + var fieldRef = string.Format("{0}.{1}", Context.Parameter.Name, + field.Name); + + var marshalCtx = new MarshalContext(Context.Driver) + { + ArgName = fieldRef, + ParameterIndex = Context.ParameterIndex++ + }; + + var marshal = new CLIMarshalManagedToNativePrinter(marshalCtx); + field.Visit(marshal); + + Context.ParameterIndex = marshalCtx.ParameterIndex; + + if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore)) + Context.SupportBefore.Write(marshal.Context.SupportBefore); + + if (field.Type.IsPointer()) + { + Context.SupportBefore.WriteLine("if ({0} != nullptr)", fieldRef); + Context.SupportBefore.PushIndent(); + } + + Context.SupportBefore.WriteLine("{0}.{1} = {2};", marshalVar, field.OriginalName, + marshal.Context.Return); + + if (field.Type.IsPointer()) + Context.SupportBefore.PopIndent(); + } + + public override bool VisitFieldDecl(Field field) + { + Context.Parameter = new Parameter + { + Name = Context.ArgName, + QualifiedType = field.QualifiedType + }; + + return field.Type.Visit(this); + } + + public override bool VisitParameterDecl(Parameter parameter) + { + var paramType = parameter.Type; + + Class @class; + if (paramType.Desugar().IsTagDecl(out @class)) + { + if (@class.IsRefType) + { + Context.Return.Write( + "*({0}.Internal*){1}.Instance.ToPointer()", + @class.Name, parameter.Name); + return true; + } + else + { + Context.Return.Write("*({0}.Internal*)&{1}", + @class.Name, parameter.Name); + return true; + } + } + + return paramType.Visit(this); + } + + public override bool VisitEnumDecl(Enumeration @enum) + { + Context.Return.Write("(::{0}){1}", @enum.QualifiedOriginalName, + Context.Parameter.Name); + return true; + } + + public override bool VisitClassTemplateDecl(ClassTemplate template) + { + return template.TemplatedClass.Visit(this); + } + } +} diff --git a/src/Generator/Generators/CSharp/CSharpTextTemplate.cs b/src/Generator/Generators/CSharp/CSharpTextTemplate.cs index d93b0ca8..14c91e82 100644 --- a/src/Generator/Generators/CSharp/CSharpTextTemplate.cs +++ b/src/Generator/Generators/CSharp/CSharpTextTemplate.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Cxxi.Types; @@ -9,12 +10,19 @@ namespace Cxxi.Generators.CSharp { public ITypePrinter TypePrinter { get; set; } + public override string FileExtension + { + get { return "cs"; } + } + public CSharpTextTemplate(Driver driver, TranslationUnit unit) : base(driver, unit) { TypePrinter = new CSharpTypePrinter(driver.TypeDatabase, driver.Library); } + #region Identifiers + // from https://github.com/mono/mono/blob/master/mcs/class/System/Microsoft.CSharp/CSharpCodeGenerator.cs private static string[] keywords = new string[] { @@ -32,6 +40,11 @@ namespace Cxxi.Generators.CSharp "partial", "yield", "where" }; + public string GeneratedIdentifier(string id) + { + return "__" + id; + } + public static string SafeIdentifier(string proposedName) { proposedName = @@ -39,17 +52,41 @@ namespace Cxxi.Generators.CSharp return keywords.Contains(proposedName) ? "@" + proposedName : proposedName; } - public override string FileExtension + public string QualifiedIdentifier(Declaration decl) { - get { return "cs"; } + if (Options.GenerateLibraryNamespace) + return string.Format("{0}::{1}", Options.OutputNamespace, decl.QualifiedName); + return string.Format("{0}", decl.QualifiedName); } + public static string ToCSharpCallConv(CallingConvention convention) + { + switch (convention) + { + case CallingConvention.Default: + return "Winapi"; + case CallingConvention.C: + return "Cdecl"; + case CallingConvention.StdCall: + return "StdCall"; + case CallingConvention.ThisCall: + return "ThisCall"; + case CallingConvention.FastCall: + return "FastCall"; + } + + return "Winapi"; + } + + #endregion + public override void Generate() { GenerateStart(); WriteLine("using System;"); WriteLine("using System.Runtime.InteropServices;"); + WriteLine("using System.Security;"); NewLine(); if (Options.GenerateLibraryNamespace) @@ -58,7 +95,7 @@ namespace Cxxi.Generators.CSharp WriteStartBraceIndent(); } - GenerateDeclarations(); + GenerateNamespace(TranslationUnit); if (Options.GenerateLibraryNamespace) WriteCloseBraceIndent(); @@ -78,11 +115,6 @@ namespace Cxxi.Generators.CSharp WriteLine("//----------------------------------------------------------------------------"); } - public void GenerateDeclarations() - { - GenerateNamespace(TranslationUnit); - } - private void GenerateNamespace(Namespace @namespace) { bool isGlobalNamespace = @namespace is TranslationUnit; @@ -142,26 +174,70 @@ namespace Cxxi.Generators.CSharp WriteCloseBraceIndent(); } - public void GenerateDeclarationCommon(Declaration T) + public void GenerateDeclarationCommon(Declaration decl) { - GenerateSummary(T.BriefComment); - GenerateDebug(T); + GenerateSummary(decl.BriefComment); + GenerateDebug(decl); + } + + public void GenerateDebug(Declaration decl) + { + if (Options.OutputDebug && !String.IsNullOrWhiteSpace(decl.DebugText)) + WriteLine("// DEBUG: " + decl.DebugText); + } + + public void GenerateSummary(string comment) + { + if (String.IsNullOrWhiteSpace(comment)) + return; + + WriteLine("/// "); + WriteLine("/// {0}", comment); + WriteLine("/// "); + } + + public void GenerateInlineSummary(string comment) + { + if (String.IsNullOrWhiteSpace(comment)) + return; + + WriteLine("/// {0}", comment); } #region Classes public void GenerateClass(Class @class) { - if (@class.Ignore) return; + if (@class.Ignore || @class.IsIncomplete) + return; + GenerateDeclarationCommon(@class); + if (@class.IsUnion) + { + // TODO: How to do wrapping of unions? + throw new NotImplementedException(); + } + GenerateClassProlog(@class); NewLine(); WriteStartBraceIndent(); + WriteLine("const string DllName = \"{0}.dll\";", Library.Name); + NewLine(); + if (!@class.IsOpaque) { + GenerateClassInternals(@class); + + if (ShouldGenerateClassNativeField(@class)) + { + WriteLine("public System.IntPtr Instance { get; protected set; }"); + NewLine(); + } + + GenerateClassConstructors(@class); GenerateClassFields(@class); GenerateClassMethods(@class); } @@ -169,6 +245,123 @@ namespace Cxxi.Generators.CSharp WriteCloseBraceIndent(); } + public void GenerateClassInternals(Class @class) + { + var typePrinter = Type.TypePrinter as CSharpTypePrinter; + typePrinter.PushContext(CSharpTypePrinterContextKind.Native); + + WriteLine("[StructLayout(LayoutKind.Explicit, Size = {0})]", + @class.Layout.Size); + + WriteLine("internal new struct Internal"); + WriteStartBraceIndent(); + + ResetNewLine(); + + foreach (var field in @class.Fields) + { + NewLineIfNeeded(); + + WriteLine("[FieldOffset({0})]", field.OffsetInBytes); + + WriteLine("public {0} {1};", field.Type, + SafeIdentifier(field.OriginalName)); + NeedNewLine(); + } + + foreach (var ctor in @class.Constructors) + { + if (ctor.IsCopyConstructor || ctor.IsMoveConstructor) + continue; + + NewLineIfNeeded(); + + GeneratePInvokeMethod(ctor, @class); + NeedNewLine(); + } + + foreach (var method in @class.Methods) + { + if (CheckIgnoreMethod(@class, method)) + continue; + + if (method.IsConstructor) + continue; + + NewLineIfNeeded(); + + GeneratePInvokeMethod(method, @class); + NeedNewLine(); + } + + WriteCloseBraceIndent(); + NewLine(); + + typePrinter.PopContext(); + } + + private void GeneratePInvokeMethod(Method method, Class @class) + { + GenerateFunction(method, @class); + } + + private void GenerateStructMarshaling(Class @class) + { + GenerateStructMarshalingFields(@class); + } + + private void GenerateStructMarshalingFields(Class @class) + { + foreach (var @base in @class.Bases) + { + if (!@base.IsClass || @base.Class.Ignore) + continue; + + GenerateStructMarshalingFields(@base.Class); + } + + foreach (var field in @class.Fields) + { + if (CheckIgnoreField(@class, field)) continue; + + var nativeField = string.Format("*({0}*) (native + {1})", + field.Type, field.OffsetInBytes); + + var ctx = new CSharpMarshalContext(Driver) + { + ArgName = field.Name, + ReturnVarName = nativeField, + ReturnType = field.Type + }; + + var marshal = new CSharpMarshalNativeToManagedPrinter(ctx); + field.Visit(marshal); + + if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore)) + Write(marshal.Context.SupportBefore); + + WriteLine("{0} = {1};", field.Name, marshal.Context.Return); + } + } + + public bool ShouldGenerateClassNativeField(Class @class) + { + if (!@class.IsRefType) + return false; + + Class baseClass = null; + + if (@class.HasBaseClass) + baseClass = @class.Bases[0].Class; + + var hasRefBase = baseClass != null && baseClass.IsRefType + && !baseClass.Ignore; + + var hasIgnoredBase = baseClass != null && baseClass.Ignore; + + return !@class.HasBase || !hasRefBase || hasIgnoredBase; + } + public void GenerateClassProlog(Class @class) { if (@class.IsUnion) @@ -179,34 +372,546 @@ namespace Cxxi.Generators.CSharp if (@class.IsAbstract) Write("abstract "); - Write("class {0}", SafeIdentifier(@class.Name)); + Write(@class.IsValueType ? "struct " : "class "); + Write("{0}", SafeIdentifier(@class.Name)); + + var needsBase = @class.HasBase && !@class.IsValueType + && !@class.Bases[0].Class.IsValueType + && !@class.Bases[0].Class.Ignore; + + if (needsBase || @class.IsRefType) + Write(" : "); - if (@class.HasBase) - Write(" : {0}", SafeIdentifier(@class.Bases[0].Class.Name)); + if (needsBase) + { + Write("{0}", SafeIdentifier(@class.Bases[0].Class.Name)); + + if (@class.IsRefType) + Write(", "); + } + + if (@class.IsRefType) + Write("IDisposable"); } public void GenerateClassFields(Class @class) { + // Handle value-type inheritance + if (@class.IsValueType) + { + foreach (var @base in @class.Bases) + { + Class baseClass; + if (!@base.Type.IsTagDecl(out baseClass)) + continue; + + if (!baseClass.IsValueType || baseClass.Ignore) + continue; + + GenerateClassFields(baseClass); + } + } + + ResetNewLine(); + foreach (var field in @class.Fields) { - if (field.Ignore) continue; + if (CheckIgnoreField(@class, field)) continue; + + NewLineIfNeeded(); + + if (@class.IsRefType) + { + GenerateFieldProperty(field); + } + else + { + GenerateDeclarationCommon(field); + if (@class.IsUnion) + WriteLine("[FieldOffset({0})]", field.Offset); + WriteLine("public {0} {1};", field.Type, SafeIdentifier(field.Name)); + } + + NeedNewLine(); + } + } + + private void GenerateFieldProperty(Field field) + { + var @class = field.Class; + + GenerateDeclarationCommon(field); + WriteLine("public {0} {1}", field.Type, SafeIdentifier(field.Name)); + WriteStartBraceIndent(); + + GeneratePropertyGetter(field, @class); + NewLine(); - GenerateDeclarationCommon(field); - if (@class.IsUnion) - WriteLine("[FieldOffset({0})]", field.Offset); - WriteLine("public {0} {1};", field.Type, SafeIdentifier(field.Name)); + GeneratePropertySetter(field, @class); + + WriteCloseBraceIndent(); + } + + private string GetPropertyLocation(T decl, Class @class) + where T : Declaration, ITypedDecl + { + if (decl is Variable) + { + return string.Format("::{0}::{1}", + @class.QualifiedOriginalName, decl.OriginalName); } + + var field = decl as Field; + return string.Format("*({0}*) (Instance + {1})", field.Type, + field.OffsetInBytes); + } + + private void GeneratePropertySetter(T decl, Class @class) + where T : Declaration, ITypedDecl + { + WriteLine("set"); + WriteStartBraceIndent(); + + var param = new Parameter + { + Name = "value", + QualifiedType = decl.QualifiedType + }; + + var ctx = new CSharpMarshalContext(Driver) + { + Parameter = param, + ArgName = param.Name, + }; + + var marshal = new CSharpMarshalManagedToNativePrinter(ctx); + param.Visit(marshal); + + var variable = GetPropertyLocation(decl, @class); + + if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore)) + Write(marshal.Context.SupportBefore); + + WriteLine("{0} = {1};", variable, marshal.Context.Return); + + WriteCloseBraceIndent(); + } + + private void GeneratePropertyGetter(T decl, Class @class) + where T : Declaration, ITypedDecl + { + WriteLine("get"); + WriteStartBraceIndent(); + + var variable = GetPropertyLocation(decl, @class); + + var ctx = new CSharpMarshalContext(Driver) + { + ArgName = decl.Name, + ReturnVarName = variable, + ReturnType = decl.Type + }; + + var marshal = new CSharpMarshalNativeToManagedPrinter(ctx); + decl.Visit(marshal); + + if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore)) + Write(marshal.Context.SupportBefore); + + WriteLine("return {0};", marshal.Context.Return); + + WriteCloseBraceIndent(); } public void GenerateClassMethods(Class @class) { + ResetNewLine(); + + var staticMethods = new List(); foreach (var method in @class.Methods) - GenerateClassMethod(method, @class); + { + if (CheckIgnoreMethod(@class, method)) + continue; + + if (method.IsConstructor) + continue; + + if (method.IsStatic) + { + staticMethods.Add(method); + continue; + } + + NewLineIfNeeded(); + GenerateMethod(method, @class); + NeedNewLine(); + } + + ResetNewLine(); + + foreach (var method in staticMethods) + { + NewLineIfNeeded(); + GenerateMethod(method, @class); + NeedNewLine(); + } + } + + #endregion + + #region Constructors + + public void GenerateClassConstructors(Class @class) + { + // Output a default constructor that takes the native pointer. + WriteLine("{0}(System.IntPtr native)", SafeIdentifier(@class.Name)); + WriteStartBraceIndent(); + + if (@class.IsRefType) + { + if (ShouldGenerateClassNativeField(@class)) + WriteLine("Instance = native;"); + } + else + { + GenerateStructMarshaling(@class); + } + + WriteCloseBraceIndent(); + NewLine(); + + foreach (var ctor in @class.Constructors) + { + if (ctor.IsCopyConstructor || ctor.IsMoveConstructor) + continue; + + // Default constructors are not supported in .NET value types. + if (ctor.Parameters.Count == 0 && @class.IsValueType) + continue; + + GenerateMethod(ctor, @class); + NewLine(); + } + + if (@class.IsRefType) + { + WriteLine("public void Dispose()"); + WriteStartBraceIndent(); + + if (ShouldGenerateClassNativeField(@class)) + WriteLine("Marshal.FreeHGlobal(Instance);"); + + WriteCloseBraceIndent(); + NewLine(); + } + } + + private bool GenerateClassConstructorBase(Class @class, Method method) + { + var hasBase = @class.HasBase && !@class.Bases[0].Class.Ignore; + + if (hasBase && !@class.IsValueType) + { + PushIndent(); + Write(": this(", QualifiedIdentifier(@class.Bases[0].Class)); + + if (method != null) + Write("IntPtr.Zero"); + else + Write("native"); + + WriteLine(")"); + PopIndent(); + } + + return hasBase; + } + + #endregion + + #region Methods / Functions + + public void GenerateMethod(Method method, Class @class) + { + GenerateDeclarationCommon(method); + + Write("public "); + + if (method.Kind == CXXMethodKind.Constructor || method.Kind == CXXMethodKind.Destructor) + Write("{0}(", SafeIdentifier(method.Name)); + else + Write("{0} {1}(", method.ReturnType, SafeIdentifier(method.Name)); + + GenerateMethodParameters(method); + + WriteLine(")"); + + if (method.Kind == CXXMethodKind.Constructor) + GenerateClassConstructorBase(@class, method); + + WriteStartBraceIndent(); + + if (@class.IsRefType) + { + if (method.Kind == CXXMethodKind.Constructor) + { + if (!@class.IsAbstract) + { + var @params = GenerateFunctionParamsMarshal(method.Parameters, method); + + WriteLine("Instance = Marshal.AllocHGlobal({0});", @class.Layout.Size); + Write("Internal.{0}(Instance", method.Name); + GenerateFunctionParams(@params); + WriteLine(");"); + } + } + else + { + GenerateFunctionCall(method, @class); + } + } + else if (@class.IsValueType) + { + //if (method.Kind != CXXMethodKind.Constructor) + // GenerateFunctionCall(method, @class); + //else + // GenerateValueTypeConstructorCall(method, @class); + } + + WriteCloseBraceIndent(); + } + + public void GenerateFunctionCall(Function function, Class @class = null) + { + var retType = function.ReturnType; + var needsReturn = !retType.IsPrimitiveType(PrimitiveType.Void); + + var isValueType = @class != null && @class.IsValueType; + //if (isValueType) + //{ + // WriteLine("auto this0 = (::{0}*) 0;", @class.QualifiedOriginalName); + //} + + if (function.HasHiddenStructParameter) + { + Class retClass; + function.ReturnType.IsTagDecl(out retClass); + + WriteLine("var {0} = new {1}.Internal();", GeneratedIdentifier("udt"), + retClass.OriginalName); + + retType = new BuiltinType(PrimitiveType.Void); + needsReturn = false; + } + + var @params = GenerateFunctionParamsMarshal(function.Parameters, function); + + var names = (from param in @params + where !param.Param.Ignore + select param.Name).ToList(); + + if (function.HasHiddenStructParameter) + { + var name = string.Format("new IntPtr(&{0})", GeneratedIdentifier("udt")); + names.Insert(0, name); + } + + var method = function as Method; + + if (method != null && !method.IsStatic) + names.Insert(0, "Instance"); + + if (needsReturn) + Write("var ret = "); + + WriteLine("Internal.{0}({1});", SafeIdentifier(function.OriginalName), + string.Join(", ", names)); + + var cleanups = new List(); + GenerateFunctionCallOutParams(@params, cleanups); + + foreach (var param in @params) + { + var context = param.Context; + if (context == null) continue; + + if (!string.IsNullOrWhiteSpace(context.Cleanup)) + cleanups.Add(context.Cleanup); + } + + foreach (var cleanup in cleanups) + { + Write(cleanup); + } + + if (needsReturn) + { + var ctx = new CSharpMarshalContext(Driver) + { + ArgName = "ret", + ReturnVarName = "ret", + ReturnType = retType + }; + + var marshal = new CSharpMarshalNativeToManagedPrinter(ctx); + function.ReturnType.Visit(marshal); + + if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore)) + Write(marshal.Context.SupportBefore); + + WriteLine("return {0};", marshal.Context.Return); + } + + if (function.HasHiddenStructParameter) + { + Class retClass; + function.ReturnType.IsTagDecl(out retClass); + + WriteLine("var ret = new {0}();", retClass.Name); + + if (isValueType) + throw new NotImplementedException(); + else + WriteLine("*({0}.Internal*) ret.Instance.ToPointer() = {1};", + retClass.Name, GeneratedIdentifier("udt")); + + WriteLine("return ret;"); + } + } + + private void GenerateFunctionCallOutParams(IEnumerable @params, + ICollection cleanups) + { + foreach (var paramInfo in @params) + { + var param = paramInfo.Param; + if (param.Usage != ParameterUsage.Out && param.Usage != ParameterUsage.InOut) + continue; + + var nativeVarName = paramInfo.Name; + + var ctx = new CSharpMarshalContext(Driver) + { + ArgName = nativeVarName, + ReturnVarName = nativeVarName, + ReturnType = param.Type + }; + + var marshal = new CSharpMarshalNativeToManagedPrinter(ctx); + param.Visit(marshal); + + if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore)) + Write(marshal.Context.SupportBefore); + + WriteLine("{0} = {1};", param.Name, marshal.Context.Return); + + if (!string.IsNullOrWhiteSpace(marshal.CSharpContext.Cleanup)) + cleanups.Add(marshal.CSharpContext.Cleanup); + } + } + + private static bool IsInstanceFunction(Function function) + { + var isInstanceFunction = false; + + var method = function as Method; + if (method != null) + isInstanceFunction = method.Conversion == MethodConversionKind.None; + return isInstanceFunction; + } + + public struct ParamMarshal + { + public string Name; + public Parameter Param; + public CSharpMarshalContext Context; } - public void GenerateClassMethod(Method method, Class @class) + public void GenerateFunctionParams(List @params) { - GenerateFunction(method); + var names = @params.Select(param => param.Name).ToList(); + Write(string.Join(", ", names)); + } + + public List GenerateFunctionParamsMarshal(IEnumerable @params, + Function function = null) + { + var marshals = new List(); + + var paramIndex = 0; + foreach (var param in @params) + { + marshals.Add(GenerateFunctionParamMarshal(param, paramIndex, function)); + paramIndex++; + } + + return marshals; + } + + private ParamMarshal GenerateFunctionParamMarshal(Parameter param, int paramIndex, + Function function = null) + { + if (param.Type is BuiltinType) + { + return new ParamMarshal { Name = param.Name, Param = param }; + } + + var argName = "arg" + paramIndex.ToString(CultureInfo.InvariantCulture); + var paramMarshal = new ParamMarshal { Name = argName, Param = param }; + + if (param.Usage == ParameterUsage.Out) + { + //var paramType = param.Type; + //if (paramType.IsReference()) + // paramType = (paramType as PointerType).Pointee; + + //var typePrinter = new CppTypePrinter(Driver.TypeDatabase); + //var type = paramType.Visit(typePrinter); + + //WriteLine("{0} {1};", type, argName); + } + else + { + var ctx = new CSharpMarshalContext(Driver) + { + Parameter = param, + ParameterIndex = paramIndex, + ArgName = argName, + Function = function + }; + + paramMarshal.Context = ctx; + + var marshal = new CSharpMarshalManagedToNativePrinter(ctx); + param.Visit(marshal); + + if (string.IsNullOrEmpty(marshal.Context.Return)) + throw new Exception("Cannot marshal argument of function"); + + if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore)) + Write(marshal.Context.SupportBefore); + + WriteLine("var {0} = {1};", argName, marshal.Context.Return); + } + + return paramMarshal; + } + + private void GenerateMethodParameters(Method method) + { + var @params = new List(); + + for (var i = 0; i < method.Parameters.Count; ++i) + { + var param = method.Parameters[i]; + + if (param.Kind == ParameterKind.HiddenStructureReturn) + continue; + + @params.Add(string.Format("{0} {1}", param.Type, SafeIdentifier(param.Name))); + } + + Write(string.Join(", ", @params)); } #endregion @@ -281,70 +986,55 @@ namespace Cxxi.Generators.CSharp WriteCloseBraceIndent(); } - public void GenerateFunction(Function function) + public void GenerateFunction(Function function, Class @class = null) { if(function.Ignore) return; GenerateDeclarationCommon(function); - Write("[DllImport(\"{0}.dll\", ", SafeIdentifier(Library.SharedLibrary)); - Write("CallingConvention = CallingConvention.{0}, ", ToCSharpCallConv(function.CallingConvention)); - WriteLine("EntryPoint=\"{0}\")]", function.Mangled); + WriteLine("[SuppressUnmanagedCodeSecurity]"); + + Write("[DllImport(\"{0}.dll\", ", Library.SharedLibrary); + WriteLine("CallingConvention = CallingConvention.{0}, ", + ToCSharpCallConv(function.CallingConvention)); + WriteLineIndent("EntryPoint=\"{0}\")]", function.Mangled); - Write("public unsafe static extern {0} {1}(", function.ReturnType, - SafeIdentifier(function.Name)); + if (function.ReturnType.Desugar().IsPrimitiveType(PrimitiveType.Bool)) + WriteLine("[return: MarshalAsAttribute(UnmanagedType.I1)]"); var @params = new List(); - for(var i = 0; i < function.Parameters.Count; ++i) - { - var param = function.Parameters[i]; - @params.Add(string.Format("{0} {1}", param.Type, SafeIdentifier(param.Name))); - } - Write(string.Join(", ", @params)); - WriteLine(");"); - } + var typePrinter = Type.TypePrinter as CSharpTypePrinter; + var retType = typePrinter.VisitParameterDecl(new Parameter() + { + QualifiedType = new QualifiedType() { Type = function.ReturnType } + }); - public static string ToCSharpCallConv(CallingConvention convention) - { - switch (convention) + var method = function as Method; + if (method != null) { - case CallingConvention.Default: - return "Winapi"; - case CallingConvention.C: - return "Cdecl"; - case CallingConvention.StdCall: - return "StdCall"; - case CallingConvention.ThisCall: - return "ThisCall"; - case CallingConvention.FastCall: - return "FastCall"; + @params.Add("System.IntPtr instance"); + + if (method.IsConstructor && Options.IsMicrosoftAbi) + retType = "System.IntPtr"; } - return "Winapi"; - } + for(var i = 0; i < function.Parameters.Count; ++i) + { + var param = function.Parameters[i]; + var typeName = param.Visit(typePrinter); - public void GenerateDebug(Declaration decl) - { - if(Options.OutputDebug && !String.IsNullOrWhiteSpace(decl.DebugText)) - WriteLine("// DEBUG: " + decl.DebugText); - } + var paramName = param.IsSynthetized ? + GeneratedIdentifier(param.Name) : SafeIdentifier(param.Name); - public void GenerateSummary(string comment) - { - if(String.IsNullOrWhiteSpace(comment)) - return; - - WriteLine("/// "); - WriteLine("/// {0}", comment); - WriteLine("/// "); - } + @params.Add(string.Format("{0} {1}", typeName, paramName)); + } - public void GenerateInlineSummary(string comment) - { - if(String.IsNullOrWhiteSpace(comment)) - return; + if (method != null && method.IsConstructor) + if (Options.IsMicrosoftAbi && @class.Layout.HasVirtualBases) + @params.Add("int " + GeneratedIdentifier("forBases")); - WriteLine("/// {0}", comment); + WriteLine("public unsafe static extern {0} {1}({2});", retType, + SafeIdentifier(function.Name), string.Join(", ", @params)); } } } \ No newline at end of file diff --git a/src/Generator/Generators/CSharp/CSharpTypePrinter.cs b/src/Generator/Generators/CSharp/CSharpTypePrinter.cs index 92570245..b2a773b5 100644 --- a/src/Generator/Generators/CSharp/CSharpTypePrinter.cs +++ b/src/Generator/Generators/CSharp/CSharpTypePrinter.cs @@ -4,15 +4,55 @@ using Cxxi.Types; namespace Cxxi.Generators.CSharp { + public enum CSharpTypePrinterContextKind + { + Native, + Managed + } + + public class CSharpTypePrinterContext : TypePrinterContext + { + public CSharpTypePrinterContext() + { + + } + + public CSharpTypePrinterContext(TypePrinterContextKind kind) + : base(kind) + { + + } + } + public class CSharpTypePrinter : ITypePrinter, IDeclVisitor { public Library Library { get; set; } private readonly ITypeMapDatabase TypeMapDatabase; + private readonly Stack contexts; + + public CSharpTypePrinterContextKind ContextKind + { + get { return contexts.Peek(); } + } + public CSharpTypePrinter(ITypeMapDatabase database, Library library) { TypeMapDatabase = database; Library = library; + + contexts = new Stack(); + PushContext(CSharpTypePrinterContextKind.Managed); + } + + public void PushContext(CSharpTypePrinterContextKind contextKind) + { + contexts.Push(contextKind); + } + + public CSharpTypePrinterContextKind PopContext() + { + return contexts.Pop(); } public string VisitTagType(TagType tag, TypeQualifiers quals) @@ -63,15 +103,24 @@ namespace Cxxi.Generators.CSharp return string.Format("{0}", function.Visit(this, quals)); } + var isManagedContext = ContextKind == CSharpTypePrinterContextKind.Managed; + if (pointee.IsPrimitiveType(PrimitiveType.Void, walkTypedefs: true) || pointee.IsPrimitiveType(PrimitiveType.UInt8, walkTypedefs: true)) { - return "IntPtr"; + return isManagedContext ? "string" : "System.IntPtr"; } if (pointee.IsPrimitiveType(PrimitiveType.Char) && quals.IsConst) { - return "string"; + return isManagedContext ? "string" : "System.IntPtr"; + } + + Class @class; + if (pointee.IsTagDecl(out @class) + && ContextKind == CSharpTypePrinterContextKind.Native) + { + return "System.IntPtr"; } return pointee.Visit(this, quals); @@ -95,7 +144,9 @@ namespace Cxxi.Generators.CSharp TypeMap typeMap; if (TypeMapDatabase.FindTypeMap(decl, out typeMap)) { - return typeMap.CSharpSignature(); + typeMap.Type = typedef; + var ctx = new CSharpTypePrinterContext {Type = typedef}; + return typeMap.CSharpSignature(ctx); } FunctionType func; @@ -108,12 +159,24 @@ namespace Cxxi.Generators.CSharp return decl.Type.Visit(this); } - public string VisitTemplateSpecializationType(TemplateSpecializationType template, TypeQualifiers quals) + public string VisitTemplateSpecializationType(TemplateSpecializationType template, + TypeQualifiers quals) { - throw new NotImplementedException(); + var decl = template.Template.TemplatedDecl; + + TypeMap typeMap = null; + if (TypeMapDatabase.FindTypeMap(template, out typeMap)) + { + typeMap.Declaration = decl; + typeMap.Type = template; + return typeMap.CSharpSignature(new CSharpTypePrinterContext()); + } + + return decl.Name; } - public string VisitTemplateParameterType(TemplateParameterType param, TypeQualifiers quals) + public string VisitTemplateParameterType(TemplateParameterType param, + TypeQualifiers quals) { throw new NotImplementedException(); } @@ -135,6 +198,7 @@ namespace Cxxi.Generators.CSharp case PrimitiveType.UInt64: return "ulong"; case PrimitiveType.Float: return "float"; case PrimitiveType.Double: return "double"; + case PrimitiveType.IntPtr: return "System.IntPtr"; } throw new NotSupportedException(); @@ -173,7 +237,16 @@ namespace Cxxi.Generators.CSharp public string VisitParameterDecl(Parameter parameter) { - throw new NotImplementedException(); + var paramType = parameter.Type; + + Class @class; + if (paramType.Desugar().IsTagDecl(out @class) + && ContextKind == CSharpTypePrinterContextKind.Native) + { + return string.Format("{0}.Internal", @class.Name); + } + + return paramType.Visit(this); } public string VisitTypedefDecl(TypedefDecl typedef) diff --git a/src/Generator/Types/TypeMap.cs b/src/Generator/Types/TypeMap.cs index 41aca89c..91b12b4f 100644 --- a/src/Generator/Types/TypeMap.cs +++ b/src/Generator/Types/TypeMap.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Cxxi.Generators; using Cxxi.Generators.CLI; +using Cxxi.Generators.CSharp; namespace Cxxi.Types { @@ -38,7 +39,7 @@ namespace Cxxi.Types #region C# backend - public virtual string CSharpSignature() + public virtual string CSharpSignature(CSharpTypePrinterContext ctx) { throw new NotImplementedException(); }