using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Web.Util; using CppSharp.AST; using CppSharp.AST.Extensions; using CppSharp.Passes; using CppSharp.Types; using CppSharp.Utils; using Microsoft.CodeAnalysis.CSharp.Syntax; using Attribute = CppSharp.AST.Attribute; using Type = CppSharp.AST.Type; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.CSharp; namespace CppSharp.Generators.CSharp { public static class Helpers { // from https://github.com/mono/mono/blob/master/mcs/class/System/Microsoft.CSharp/CSharpCodeGenerator.cs private static readonly string[] Keywords = { "abstract", "event", "new", "struct", "as", "explicit", "null", "switch", "base", "extern", "this", "false", "operator", "throw", "break", "finally", "out", "true", "fixed", "override", "try", "case", "params", "typeof", "catch", "for", "private", "foreach", "protected", "checked", "goto", "public", "unchecked", "class", "if", "readonly", "unsafe", "const", "implicit", "ref", "continue", "in", "return", "using", "virtual", "default", "interface", "sealed", "volatile", "delegate", "internal", "do", "is", "sizeof", "while", "lock", "stackalloc", "else", "static", "enum", "namespace", "object", "bool", "byte", "float", "uint", "char", "ulong", "ushort", "decimal", "int", "sbyte", "short", "double", "long", "string", "void", "partial", "yield", "where" }; public static string SafeIdentifier(string id) { if (id.All(char.IsLetterOrDigit)) return Keywords.Contains(id) ? "@" + id : id; return new string(id.Select(c => char.IsLetterOrDigit(c) ? c : '_').ToArray()); } public static readonly string InstanceField = Generator.GeneratedIdentifier("instance"); public static readonly string InstanceIdentifier = Generator.GeneratedIdentifier("Instance"); public static readonly string PointerAdjustmentIdentifier = Generator.GeneratedIdentifier("PointerAdjustment"); public static readonly string ReturnIdentifier = Generator.GeneratedIdentifier("ret"); public static readonly string DummyIdentifier = Generator.GeneratedIdentifier("dummy"); public static readonly string TargetIdentifier = Generator.GeneratedIdentifier("target"); public static readonly string SlotIdentifier = Generator.GeneratedIdentifier("slot"); public static readonly string OwnsNativeInstanceIdentifier = Generator.GeneratedIdentifier("ownsNativeInstance"); public static readonly string CreateInstanceIdentifier = Generator.GeneratedIdentifier("CreateInstance"); public static SyntaxKind GetAccess(AccessSpecifier accessSpecifier) { switch (accessSpecifier) { case AccessSpecifier.Private: case AccessSpecifier.Internal: return SyntaxKind.InternalKeyword; case AccessSpecifier.Protected: return SyntaxKind.ProtectedKeyword; default: return SyntaxKind.PublicKeyword; } } } public class CSharpBlockKind { private const int FIRST = BlockKind.LAST + 1000; public const int Usings = FIRST + 1; public const int Namespace = FIRST + 2; public const int Enum = FIRST + 3; public const int Typedef = FIRST + 4; public const int Class = FIRST + 5; public const int InternalsClass = FIRST + 6; public const int InternalsClassMethod = FIRST + 7; public const int InternalsClassField = FIRST + 15; public const int Functions = FIRST + 8; public const int Function = FIRST + 9; public const int Method = FIRST + 10; public const int Event = FIRST + 11; public const int Variable = FIRST + 12; public const int Property = FIRST + 13; public const int Field = FIRST + 14; public const int VTableDelegate = FIRST + 16; public const int Region = FIRST + 17; public const int Interface = FIRST + 18; public const int Finalizer = FIRST + 19; } public class CSharpTextTemplate : Template { public CSharpTypePrinter TypePrinter { get; private set; } public CSharpExpressionPrinter ExpressionPrinter { get; private set; } public override string FileExtension { get { return "cs"; } } public CSharpTextTemplate(Driver driver, IEnumerable units, CSharpTypePrinter typePrinter, CSharpExpressionPrinter expressionPrinter) : base(driver, units) { TypePrinter = typePrinter; ExpressionPrinter = expressionPrinter; } #region Identifiers public static string GeneratedIdentifier(string id) { return Generator.GeneratedIdentifier(id); } #endregion public override void Process() { compilationUnit = SyntaxFactory.CompilationUnit(); compilationUnit = compilationUnit.AddUsings( SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName("System")), SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName("System.Runtime.InteropServices")), SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName("System.Security"))); foreach (var customUsingStatement in Options.DependentNameSpaces) { compilationUnit = compilationUnit.AddUsings( SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(customUsingStatement))); } var members = TranslationUnits.SelectMany(unit => GenerateDeclContext(unit)).ToArray(); if (Options.GenerateLibraryNamespace) { var root = SyntaxFactory.NamespaceDeclaration( SyntaxFactory.IdentifierName(Driver.Options.OutputNamespace)); root = AddMembers(root, members); compilationUnit = AddMembers(compilationUnit, new [] { root }); } else { compilationUnit = AddMembers(compilationUnit, members); } GenerateHeader(); } public override void Generate(TextWriter textWriter) { using (var workspace = new AdhocWorkspace()) { var formattedNode = Formatter.Format(compilationUnit, workspace); formattedNode.WriteTo(textWriter); } } private void GenerateHeader() { compilationUnit = compilationUnit.WithLeadingTrivia( SyntaxFactory.Comment("//----------------------------------------------------------------------------"), SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.Comment("// This is autogenerated code by CppSharp."), SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.Comment("// Do not edit this file or all your changes will be lost after re-generation."), SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.Comment("//----------------------------------------------------------------------------"), SyntaxFactory.CarriageReturnLineFeed); } private MemberDeclarationSyntax[] GenerateDeclContext(DeclarationContext context) { var isNamespace = context is Namespace; var isTranslationUnit = context is TranslationUnit; var members = new List(); members.AddRange( from @enum in context.Enums where @enum.IsGenerated && !@enum.IsIncomplete select GenerateEnum(@enum)); members.AddRange(context.Typedefs.Select(t => GenerateTypedef(t)).Where(t => t != null)); foreach (var @class in context.Classes.Where(c => !c.IsIncomplete)) { if (@class.IsInterface) members.Add(GenerateInterface(@class)); else members.Add(GenerateClass(@class)); } //if (context.HasFunctions) //{ // PushBlock(CSharpBlockKind.Functions); // WriteLine("public unsafe partial class {0}", // context.TranslationUnit.FileNameWithoutExtension); // WriteStartBraceIndent(); // PushBlock(CSharpBlockKind.InternalsClass); // GenerateClassInternalHead(); // WriteStartBraceIndent(); // // Generate all the internal function declarations. // foreach (var function in context.Functions) // { // if ((!function.IsGenerated && !function.IsInternal) || function.IsSynthetized) continue; // GenerateInternalFunction(function); // } // WriteCloseBraceIndent(); // PopBlock(NewLineKind.BeforeNextBlock); // foreach (var function in context.Functions) // { // if (!function.IsGenerated) continue; // GenerateFunction(function); // } // WriteCloseBraceIndent(); // PopBlock(NewLineKind.BeforeNextBlock); //} //foreach (var @event in context.Events) //{ // if (!@event.IsGenerated) continue; // GenerateEvent(@event); //} members.AddRange(context.Namespaces.SelectMany(n => GenerateDeclContext(n))); if (isNamespace && !isTranslationUnit) { var @namespace = SyntaxFactory.NamespaceDeclaration( SyntaxFactory.IdentifierName(context.Name)); return new [] { AddMembers(@namespace, members.ToArray()) }; } return members.ToArray(); } public void GenerateDeclarationCommon(Declaration decl) { if (decl.Comment != null) { GenerateComment(decl.Comment); GenerateDebug(decl); } foreach (Attribute attribute in decl.Attributes) WriteLine("[{0}({1})]", attribute.Type.FullName, attribute.Value); } public void GenerateDebug(Declaration decl) { if (Options.OutputDebug && !string.IsNullOrWhiteSpace(decl.DebugText)) WriteLine("// DEBUG: " + decl.DebugText); } public void GenerateComment(RawComment comment) { if (comment.FullComment != null) { PushBlock(BlockKind.BlockComment); WriteLine(comment.FullComment.CommentToString()); PopBlock(); } else { if (string.IsNullOrWhiteSpace(comment.BriefText)) return; PushBlock(BlockKind.BlockComment); WriteLine(""); foreach (string line in HtmlEncoder.HtmlEncode(comment.BriefText).Split( Environment.NewLine.ToCharArray())) WriteLine("{0}", line); WriteLine(""); PopBlock(); } } public void GenerateInlineSummary(RawComment comment) { if (comment == null) return; if (string.IsNullOrWhiteSpace(comment.BriefText)) return; PushBlock(BlockKind.InlineComment); if (comment.BriefText.Contains("\n")) { WriteLine("{0} ", Options.CommentPrefix); foreach (string line in HtmlEncoder.HtmlEncode(comment.BriefText).Split( Environment.NewLine.ToCharArray())) WriteLine("{0} {1}", Options.CommentPrefix, line); WriteLine("{0} ", Options.CommentPrefix); } else { WriteLine("{0} {1}", Options.CommentPrefix, comment.BriefText); } PopBlock(); } #region Classes public MemberDeclarationSyntax GenerateClass(Class @class) { //GenerateDeclarationCommon(@class); MemberDeclarationSyntax type = GenerateClassProlog(@class); if (@class.IsOpaque) return type; System.Type typeMap = null; if (Driver.TypeDatabase.TypeMaps.ContainsKey(@class.Name)) { typeMap = Driver.TypeDatabase.TypeMaps[@class.Name]; // disable the type map for the mapped class itself so that operator params are not mapped Driver.TypeDatabase.TypeMaps.Remove(@class.Name); } if (!@class.IsAbstractImpl) type = AddMembers(type, GenerateClassInternals(@class)); type = AddMembers(type, GenerateDeclContext(@class)); if (@class.IsDependent || !@class.IsGenerated) goto exit; if (ShouldGenerateClassNativeField(@class)) { if (@class.IsValueType) { type = AddMembers(type, GenerateStructNativeField(@class)); } else { type = AddMembers(type, GenerateClassNativeField(@class)); } } //if (Options.GenerateClassMarshals) //{ // GenerateClassMarshals(@class); //} if (!@class.IsStatic) type = AddMembers(type, GenerateClassConstructors(@class)); //GenerateClassMethods(@class.Methods); //GenerateClassVariables(@class); //GenerateClassProperties(@class); //if (@class.IsDynamic) // GenerateVTable(@class); exit: if (typeMap != null) Driver.TypeDatabase.TypeMaps.Add(@class.Name, typeMap); return type; } private MemberDeclarationSyntax[] GenerateStructNativeField(Class @class) { var members = new List(); var internalType = string.Format("{0}.Internal", @class.Name); members.Add(SyntaxFactory.FieldDeclaration(SyntaxFactory.VariableDeclaration( SyntaxFactory.ParseTypeName(internalType)) .AddVariables(SyntaxFactory.VariableDeclarator(Helpers.InstanceField))) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword))); members.Add(SyntaxFactory.PropertyDeclaration( SyntaxFactory.ParseTypeName(internalType), Helpers.InstanceIdentifier) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)) .AddAccessorListAccessors(SyntaxFactory.AccessorDeclaration( SyntaxKind.GetAccessorDeclaration, SyntaxFactory.Block(SyntaxFactory.ReturnStatement( SyntaxFactory.IdentifierName(Helpers.InstanceField)))))); return members.ToArray(); } private MemberDeclarationSyntax[] GenerateClassNativeField(Class @class) { var members = new List(); members.Add(SyntaxFactory.PropertyDeclaration( SyntaxFactory.ParseTypeName("global::System.IntPtr"), Helpers.InstanceIdentifier) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)) .AddAccessorListAccessors(SyntaxFactory.AccessorDeclaration( SyntaxKind.GetAccessorDeclaration).WithSemicolonToken( SyntaxFactory.Token(SyntaxKind.SemicolonToken)), SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) .AddModifiers(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)) .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)))); members.Add(SyntaxFactory.FieldDeclaration(SyntaxFactory.VariableDeclaration( SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.IntKeyword))) .AddVariables(SyntaxFactory.VariableDeclarator( Helpers.PointerAdjustmentIdentifier))) .AddModifiers(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword))); // use interfaces if any - derived types with a secondary base this class must be compatible with the map var name = (@class.Namespace.Classes.Find(c => c.OriginalClass == @class) ?? @class).Name; var dictionaryType = string.Format( "System.Collections.Concurrent.ConcurrentDictionary", name); members.Add(SyntaxFactory.FieldDeclaration(SyntaxFactory.VariableDeclaration( SyntaxFactory.ParseTypeName(dictionaryType)) .AddVariables(SyntaxFactory.VariableDeclarator("NativeToManagedMap") .WithInitializer( SyntaxFactory.EqualsValueClause(SyntaxFactory.ObjectCreationExpression( SyntaxFactory.ParseTypeName(dictionaryType), SyntaxFactory.ArgumentList(), null))))) .AddModifiers( SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword))); members.Add(SyntaxFactory.FieldDeclaration(SyntaxFactory.VariableDeclaration( SyntaxFactory.ArrayType( SyntaxFactory.PointerType( SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword))) .WithAsteriskToken(SyntaxFactory.Token(SyntaxKind.AsteriskToken))) .AddRankSpecifiers(SyntaxFactory.ArrayRankSpecifier())) .AddVariables(SyntaxFactory.VariableDeclarator( SyntaxFactory.Identifier("__OriginalVTables")))) .AddModifiers(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword))); return members.ToArray(); } private void GenerateClassMarshals(Class @class) { WriteLine("int CppSharp.Runtime.ICppMarshal.NativeDataSize"); WriteStartBraceIndent(); WriteLine("get {{ return {0}; }}", @class.Layout.DataSize); WriteCloseBraceIndent(); NewLine(); WriteLine("void CppSharp.Runtime.ICppMarshal.MarshalManagedToNative(global::System.IntPtr instance)"); WriteStartBraceIndent(); WriteCloseBraceIndent(); NewLine(); WriteLine("void CppSharp.Runtime.ICppMarshal.MarshalNativeToManaged(global::System.IntPtr instance)"); WriteStartBraceIndent(); WriteCloseBraceIndent(); NewLine(); } private TypeDeclarationSyntax GenerateInterface(Class @class) { //GenerateDeclarationCommon(@class); return GenerateClassProlog(@class); //foreach (var method in @class.Methods.Where(m => // !ASTUtils.CheckIgnoreMethod(m, Options) && m.Access == AccessSpecifier.Public)) //{ // PushBlock(CSharpBlockKind.Method); // GenerateDeclarationCommon(method); // var functionName = GetMethodIdentifier(method); // Write("{0} {1}(", method.OriginalReturnType, functionName); // Write(FormatMethodParameters(method.Parameters)); // WriteLine(");"); // PopBlock(NewLineKind.BeforeNextBlock); //} //foreach (var prop in @class.Properties.Where(p => p.IsGenerated && p.Access == AccessSpecifier.Public)) //{ // PushBlock(CSharpBlockKind.Property); // var type = prop.Type; // if (prop.Parameters.Count > 0 && prop.Type.IsPointerToPrimitiveType()) // type = ((PointerType) prop.Type).Pointee; // GenerateDeclarationCommon(prop); // Write("{0} {1} {{ ", type, GetPropertyName(prop)); // if (prop.HasGetter) // Write("get; "); // if (prop.HasSetter) // Write("set; "); // WriteLine("}"); // PopBlock(NewLineKind.BeforeNextBlock); //} } public MemberDeclarationSyntax GenerateClassInternals(Class @class) { var @struct = SyntaxFactory.StructDeclaration("Internal").AddModifiers( SyntaxFactory.Token(SyntaxKind.PublicKeyword)) .AddAttributeLists(SyntaxFactory.AttributeList().AddAttributes( SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("StructLayout"), SyntaxFactory.AttributeArgumentList().AddArguments( SyntaxFactory.AttributeArgument( SyntaxFactory.IdentifierName("LayoutKind.Explicit")), SyntaxFactory.AttributeArgument( SyntaxFactory.IdentifierName("Size = " + @class.Layout.Size)))))); if (@class != null && @class.NeedsBase && !@class.BaseClass.IsInterface) @struct = @struct.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); @struct = @struct.AddModifiers(SyntaxFactory.Token(SyntaxKind.PartialKeyword)); TypePrinter.PushContext(CSharpTypePrinterContextKind.Native); @struct = AddMembers(@struct, GenerateClassFields(@class, @class, GenerateClassInternalsField, true)); if (@class.IsGenerated) { if (@class.IsDynamic) @struct = AddMembers(@struct, GenerateVTablePointers(@class)); var functions = GatherClassInternalFunctions(@class); @struct = AddMembers(@struct, functions.Where(f => !f.IsPure).Select(f => GenerateInternalFunction(f)).ToArray()); } TypePrinter.PopContext(); return @struct; } private IEnumerable GatherClassInternalFunctions(Class @class, bool includeCtors = true) { var functions = new List(); if (@class.IsValueType) foreach (var @base in @class.Bases.Where(b => b.IsClass && !b.Class.Ignore)) functions.AddRange(GatherClassInternalFunctions(@base.Class, false)); Action tryAddOverload = method => { if (method.IsSynthetized && method.SynthKind != FunctionSynthKind.AdjustedMethod) return; if (method.IsProxy || (method.IsVirtual && !method.IsOperator && // virtual destructors in abstract classes may lack a pointer in the v-table // so they have to be called by symbol and therefore not ignored !(method.IsDestructor && @class.IsAbstract))) return; functions.Add(method); }; if (includeCtors) { foreach (var ctor in @class.Constructors) { if (@class.IsStatic || ctor.IsMoveConstructor) continue; if (!ctor.IsGenerated) continue; if (ctor.IsDefaultConstructor && !@class.HasNonTrivialDefaultConstructor) continue; tryAddOverload(ctor); } } if (@class.HasNonTrivialDestructor && !@class.IsStatic) foreach (var dtor in @class.Destructors) tryAddOverload(dtor); foreach (var method in @class.Methods) { if (ASTUtils.CheckIgnoreMethod(method, Options)) continue; if (method.IsConstructor) continue; tryAddOverload(method); } foreach (var prop in @class.Properties) { if (prop.GetMethod != null) tryAddOverload(prop.GetMethod); if (prop.SetMethod != null && prop.SetMethod != prop.GetMethod) tryAddOverload(prop.SetMethod); } return functions; } private IEnumerable GatherInternalParams(Function function, out CSharpTypePrinterResult retType) { TypePrinter.PushContext(CSharpTypePrinterContextKind.Native); var retParam = new Parameter { QualifiedType = function.ReturnType }; retType = retParam.CSharpType(TypePrinter); var @params = function.GatherInternalParams(Driver.Options.IsItaniumLikeAbi).Select(p => string.Format("{0} {1}", p.CSharpType(TypePrinter), p.Name)).ToList(); TypePrinter.PopContext(); return @params; } public static bool ShouldGenerateClassNativeField(Class @class) { if (@class.IsStatic) return false; return @class.IsValueType || !@class.HasBase || !@class.HasRefBase(); } public TypeDeclarationSyntax GenerateClassProlog(Class @class) { TypeDeclarationSyntax type; if (@class.IsInterface) type = SyntaxFactory.InterfaceDeclaration(@class.Name); else if (@class.IsValueType) type = SyntaxFactory.StructDeclaration(@class.Name); else type = SyntaxFactory.ClassDeclaration(@class.Name); type = AddModifier(type, @class.IsInternal ? SyntaxKind.InternalKeyword : Helpers.GetAccess(@class.Access)); if (@class.Access == AccessSpecifier.Protected) type = AddModifier(type, SyntaxKind.InternalKeyword); type = AddModifier(type, SyntaxKind.UnsafeKeyword); if (@class.IsAbstract) type = AddModifier(type, SyntaxKind.AbstractKeyword); if (@class.IsStatic) type = AddModifier(type, SyntaxKind.StaticKeyword); // This token needs to directly precede the "class" token. if (Options.GeneratePartialClasses) type = AddModifier(type, SyntaxKind.PartialKeyword); var bases = new List(); if (@class.NeedsBase) { bases.AddRange( from @base in @class.Bases where @base.IsClass select @base.Class.Visit(TypePrinter).Type); } if (!@class.IsStatic) foreach (var @base in bases) type = AddBase(type, @base); if (@class.IsGenerated) { if (@class.IsRefType) type = AddBase(type,"IDisposable"); if (Options.GenerateClassMarshals) type = AddBase(type, "CppSharp.Runtime.ICppMarshal"); } return type; } public MemberDeclarationSyntax[] GenerateClassFields(Class owner, Class @class, Func function, bool nativeFields = false) { var members = new List(); foreach (var @base in @class.Bases.Where(b => b.Class != null)) { TypeMap typeMap; if ((!Driver.TypeDatabase.FindTypeMap(@base.Type, out typeMap) && !@base.Class.IsDeclared) || @base.Class.OriginalClass == @class) continue; members.AddRange(GenerateClassFields(owner, @base.Class, function, nativeFields)); } foreach (var field in @class.Fields) { if (ASTUtils.CheckIgnoreField(field, nativeFields)) continue; members.AddRange(function(owner, field)); } return members.ToArray(); } private MemberDeclarationSyntax[] GenerateClassInternalsField(Class owner, Field field) { // we do not support dependent fields yet, see https://github.com/mono/CppSharp/issues/197 Class @class; field.Type.TryGetClass(out @class); if ((field.Type.IsDependent && !field.Type.IsPointer() && !(@class != null && @class.IsUnion)) || (@class != null && @class.TranslationUnit.IsSystemHeader)) return new MemberDeclarationSyntax[0]; var members = new List(); var safeIdentifier = Helpers.SafeIdentifier(field.InternalName); if(safeIdentifier.All(c => c.Equals('_'))) { safeIdentifier = Helpers.SafeIdentifier(field.Name); } TypePrinter.PushMarshalKind(CSharpMarshalKind.NativeField); var fieldTypePrinted = field.QualifiedType.CSharpType(TypePrinter); TypePrinter.PopMarshalKind(); var fieldName = safeIdentifier; if (!string.IsNullOrWhiteSpace(fieldTypePrinted.NameSuffix)) fieldName += fieldTypePrinted.NameSuffix; var offset = (int) field.OffsetInBytes + owner.ComputeNonVirtualBaseClassOffsetTo((Class) field.Namespace); var access = @class != null && !@class.IsGenerated ? SyntaxKind.InternalKeyword : SyntaxKind.PublicKeyword; var variable = SyntaxFactory.VariableDeclarator(fieldName); if (field.Expression != null) { var fieldValuePrinted = field.Expression.CSharpValue(ExpressionPrinter).Value; variable = variable.WithInitializer( SyntaxFactory.EqualsValueClause(SyntaxFactory.IdentifierName(fieldValuePrinted))); } var fieldDeclaration = (SyntaxFactory.FieldDeclaration( SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName(fieldTypePrinted.Type)) .AddVariables(variable)) .AddModifiers(SyntaxFactory.Token(access)) .AddAttributeLists(SyntaxFactory.AttributeList().AddAttributes( SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("FieldOffset")) .AddArgumentListArguments(SyntaxFactory.AttributeArgument( SyntaxFactory.LiteralExpression( SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(offset))))))); // Workaround a bug in Mono when handling fixed arrays in P/Invoke declarations. // https://bugzilla.xamarin.com/show_bug.cgi?id=33571 var arrayType = field.Type.Desugar() as ArrayType; if (arrayType != null && arrayType.SizeType == ArrayType.ArraySize.Constant) { fieldDeclaration = fieldDeclaration.AddModifiers( SyntaxFactory.Token(SyntaxKind.FixedKeyword)); for (var i = 1; i < arrayType.Size; ++i) { var dummy = new Field { Name = string.Format("{0}_{1}_{2}", Helpers.DummyIdentifier, safeIdentifier, i), QualifiedType = new QualifiedType(arrayType.Type), Offset = (uint) (field.Offset + i * arrayType.ElementSize), Namespace = owner }; members.AddRange(GenerateClassInternalsField(owner, dummy)); } } members.Insert(0, fieldDeclaration); return members.ToArray(); } private void GenerateClassField(Field field, bool @public = false) { PushBlock(CSharpBlockKind.Field); GenerateDeclarationCommon(field); WriteLine("{0} {1} {2};", @public ? "public" : "private", field.Type, field.Name); PopBlock(NewLineKind.BeforeNextBlock); } #endregion private Tuple GetDeclarationLibrarySymbol(IMangledDecl decl) { var library = Options.SharedLibraryName; if (!Options.CheckSymbols) goto Out; NativeLibrary nativeLib; if (!Driver.Symbols.FindLibraryBySymbol(decl.Mangled, out nativeLib)) goto Out; library = Path.GetFileNameWithoutExtension(nativeLib.FileName); Out: return Tuple.Create(library, decl.Mangled); } private void GeneratePropertySetter(T decl, Class @class, bool isAbstract = false, Property property = null) where T : Declaration, ITypedDecl { if (!(decl is Function || decl is Field) ) { return; } PushBlock(CSharpBlockKind.Method); Write("set"); var param = new Parameter { Name = "value", QualifiedType = decl.QualifiedType }; var ctx = new CSharpMarshalContext(Driver) { Parameter = param, ArgName = param.Name, }; if (decl is Function) { var function = decl as Function; if (isAbstract) { Write(";"); PopBlock(NewLineKind.BeforeNextBlock); return; } NewLine(); WriteStartBraceIndent(); if (function.Parameters.Count == 0) throw new NotSupportedException("Expected at least one parameter in setter"); param.QualifiedType = function.Parameters[0].QualifiedType; var method = function as Method; if (function.SynthKind == FunctionSynthKind.AbstractImplCall) GenerateVirtualPropertyCall(method, @class.BaseClass, property, new List { param }); else if (method != null && method.IsVirtual) GenerateVirtualPropertyCall(method, @class, property, new List { param }); else { if (method != null && method.OperatorKind == CXXOperatorKind.Subscript) { if (method.OperatorKind == CXXOperatorKind.Subscript) { GenerateIndexerSetter(method); } else { GenerateInternalFunctionCall(function, new List { param }); } } else { GenerateInternalFunctionCall(function, new List { param }); } } WriteCloseBraceIndent(); } else { var field = decl as Field; if (WrapSetterArrayOfPointers(decl.Name, field.Type)) return; NewLine(); WriteStartBraceIndent(); ctx.Kind = CSharpMarshalKind.NativeField; var marshal = new CSharpMarshalManagedToNativePrinter(ctx); var arrayType = field.Type as ArrayType; if (arrayType != null && @class.IsValueType) { ctx.ReturnVarName = HandleValueArray(arrayType, field); } else { ctx.ReturnVarName = string.Format("{0}{1}{2}", @class.IsValueType ? Helpers.InstanceField : string.Format("((Internal*) {0})", Helpers.InstanceIdentifier), @class.IsValueType ? "." : "->", Helpers.SafeIdentifier(field.InternalName)); } param.Visit(marshal); if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore)) Write(marshal.Context.SupportBefore); if (marshal.Context.Return.StringBuilder.Length > 0) { WriteLine("{0} = {1};", ctx.ReturnVarName, marshal.Context.Return); } if (arrayType != null && @class.IsValueType) WriteCloseBraceIndent(); WriteCloseBraceIndent(); } PopBlock(NewLineKind.BeforeNextBlock); } private string HandleValueArray(ArrayType arrayType, Field field) { var originalType = new PointerType(new QualifiedType(arrayType.Type)); var arrPtrIden = Generator.GeneratedIdentifier("arrPtr"); var arrPtr = arrPtrIden; var finalElementType = (arrayType.Type.GetFinalPointee() ?? arrayType.Type); var isChar = finalElementType.IsPrimitiveType(PrimitiveType.Char); string type; if (Driver.Options.MarshalCharAsManagedChar && isChar) { var typePrinter = new CSharpTypePrinter(Driver); typePrinter.PushContext(CSharpTypePrinterContextKind.Native); type = originalType.Visit(typePrinter).Type; arrPtrIden = Generator.GeneratedIdentifier(arrPtrIden); } else { type = originalType.ToString(); } WriteLine(string.Format("fixed ({0} {1} = {2}.{3})", type, arrPtrIden, Helpers.InstanceField, Helpers.SafeIdentifier(field.InternalName))); WriteStartBraceIndent(); if (Driver.Options.MarshalCharAsManagedChar && isChar) WriteLine("var {0} = ({1}) {2};", arrPtr, originalType, arrPtrIden); return arrPtr; } private bool WrapSetterArrayOfPointers(string name, Type fieldType) { var arrayType = fieldType as ArrayType; if (arrayType == null || !arrayType.Type.IsPointerToPrimitiveType()) return false; NewLine(); WriteStartBraceIndent(); WriteLine("{0} = value;", name); WriteLine("if (!{0}{1})", name, "Initialised"); WriteStartBraceIndent(); WriteLine("{0}{1} = true;", name, "Initialised"); WriteCloseBraceIndent(); WriteCloseBraceIndent(); PopBlock(NewLineKind.BeforeNextBlock); return true; } private void GenerateIndexerSetter(Function function) { Type type; function.Type.IsPointerTo(out type); PrimitiveType primitiveType; var internalFunction = GetFunctionNativeIdentifier(function); if (type.IsPrimitiveType(out primitiveType)) { WriteLine("*Internal.{0}({1}, {2}) = value;", internalFunction, GetInstanceParam(function), function.Parameters[0].Name); } else { var typeString = type.ToString(); Class @class; var isValueType = (type.GetFinalPointee() ?? type).TryGetClass(out @class) && @class.IsValueType; var paramMarshal = GenerateFunctionParamMarshal(function.Parameters[0], 0, function); WriteLine("*({0}.Internal*) Internal.{1}({2}, {3}) = {4}value.{5};", typeString, internalFunction, GetInstanceParam(function), paramMarshal.Context == null ? paramMarshal.Name : paramMarshal.Context.Return, isValueType ? string.Empty : string.Format("*({0}.Internal*) ", typeString), Helpers.InstanceIdentifier); } } private void GeneratePropertyGetter(QualifiedType returnType, T decl, Class @class, bool isAbstract = false, Property property = null) where T : Declaration, ITypedDecl { PushBlock(CSharpBlockKind.Method); Write("get"); if (decl is Function) { var function = decl as Function; if (isAbstract) { Write(";"); PopBlock(NewLineKind.BeforeNextBlock); return; } NewLine(); WriteStartBraceIndent(); var method = function as Method; if (function.SynthKind == FunctionSynthKind.AbstractImplCall) GenerateVirtualPropertyCall(method, @class.BaseClass, property); else if (method != null && method.IsVirtual) GenerateVirtualPropertyCall(method, @class, property); else GenerateInternalFunctionCall(function, function.Parameters, returnType.Type); } else if (decl is Field) { var field = decl as Field; if (WrapGetterArrayOfPointers(decl.Name, field.Type)) return; NewLine(); WriteStartBraceIndent(); var ctx = new CSharpMarshalContext(Driver) { Kind = CSharpMarshalKind.NativeField, ArgName = decl.Name, ReturnVarName = string.Format("{0}{1}{2}", @class.IsValueType ? Helpers.InstanceField : string.Format("((Internal*) {0})", Helpers.InstanceIdentifier), @class.IsValueType ? "." : "->", Helpers.SafeIdentifier(field.InternalName)), ReturnType = decl.QualifiedType }; var arrayType = field.Type as ArrayType; if (arrayType != null && @class.IsValueType) ctx.ReturnVarName = HandleValueArray(arrayType, field); var marshal = new CSharpMarshalNativeToManagedPrinter(ctx); decl.CSharpMarshalToManaged(marshal); if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore)) Write(marshal.Context.SupportBefore); WriteLine("return {0};", marshal.Context.Return); if (arrayType != null && @class.IsValueType) WriteCloseBraceIndent(); } else if (decl is Variable) { NewLine(); WriteStartBraceIndent(); var @var = decl as Variable; var libSymbol = GetDeclarationLibrarySymbol(@var); TypePrinter.PushContext(CSharpTypePrinterContextKind.Native); var location = string.Format("CppSharp.SymbolResolver.ResolveSymbol(\"{0}\", \"{1}\")", libSymbol.Item1, libSymbol.Item2); var arrayType = decl.Type as ArrayType; var isRefTypeArray = arrayType != null && @class != null && @class.IsRefType; if (isRefTypeArray) WriteLine("var {0} = {1}{2};", Generator.GeneratedIdentifier("ptr"), arrayType.Type.IsPrimitiveType(PrimitiveType.Char) && arrayType.QualifiedType.Qualifiers.IsConst ? string.Empty : "(byte*)", location); else WriteLine("var {0} = ({1}*){2};", Generator.GeneratedIdentifier("ptr"), @var.Type, location); TypePrinter.PopContext(); var ctx = new CSharpMarshalContext(Driver) { ArgName = decl.Name, ReturnVarName = (isRefTypeArray ? string.Empty : "*") + Generator.GeneratedIdentifier("ptr"), ReturnType = new QualifiedType(var.Type) }; var marshal = new CSharpMarshalNativeToManagedPrinter(ctx); decl.CSharpMarshalToManaged(marshal); if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore)) Write(marshal.Context.SupportBefore); WriteLine("return {0};", marshal.Context.Return); } WriteCloseBraceIndent(); PopBlock(NewLineKind.BeforeNextBlock); } private bool WrapGetterArrayOfPointers(string name, Type fieldType) { var arrayType = fieldType as ArrayType; if (arrayType != null && arrayType.Type.IsPointerToPrimitiveType()) { NewLine(); WriteStartBraceIndent(); WriteLine("if (!{0}{1})", name, "Initialised"); WriteStartBraceIndent(); WriteLine("{0} = null;", name); WriteLine("{0}{1} = true;", name, "Initialised"); WriteCloseBraceIndent(); WriteLine("return {0};", name); WriteCloseBraceIndent(); PopBlock(NewLineKind.BeforeNextBlock); return true; } return false; } public void GenerateClassMethods(IList methods) { if (methods.Count == 0) return; var @class = (Class) methods[0].Namespace; if (@class.IsValueType) foreach (var @base in @class.Bases.Where(b => b.IsClass && !b.Class.Ignore)) GenerateClassMethods(@base.Class.Methods.Where(m => !m.IsOperator).ToList()); var staticMethods = new List(); foreach (var method in methods) { if (ASTUtils.CheckIgnoreMethod(method, Options)) continue; if (method.IsConstructor) continue; if (method.IsStatic) { staticMethods.Add(method); continue; } GenerateMethod(method, @class); } foreach (var method in staticMethods) { GenerateMethod(method, @class); } } public void GenerateClassVariables(Class @class) { if (@class.IsValueType) foreach (var @base in @class.Bases.Where(b => b.IsClass && !b.Class.Ignore)) GenerateClassVariables(@base.Class); foreach (var variable in @class.Variables) { if (!variable.IsGenerated) continue; if (variable.Access != AccessSpecifier.Public) continue; var type = variable.Type; GenerateVariable(@class, type, variable); } } private void GenerateClassProperties(Class @class) { if (@class.IsValueType) foreach (var @base in @class.Bases.Where(b => b.IsClass && !b.Class.Ignore && b.Class.IsDeclared)) GenerateClassProperties(@base.Class); GenerateProperties(@class); } private void GenerateProperties(Class @class) { foreach (var prop in @class.Properties.Where(p => p.IsGenerated)) { if (prop.IsInRefTypeAndBackedByValueClassField()) { GenerateClassField(prop.Field, true); continue; } PushBlock(CSharpBlockKind.Property); ArrayType arrayType = prop.Type as ArrayType; if (arrayType != null && arrayType.Type.IsPointerToPrimitiveType() && prop.Field != null) { GenerateClassField(prop.Field); WriteLine("private bool {0};", GeneratedIdentifier(string.Format("{0}Initialised", prop.Field.InternalName))); } GenerateDeclarationCommon(prop); if (prop.ExplicitInterfaceImpl == null) { //Write(Helpers.GetAccess(GetValidPropertyAccess(prop))); if (prop.IsStatic) Write("static "); // check if overriding a property from a secondary base Property rootBaseProperty; var isOverride = prop.IsOverride && (rootBaseProperty = @class.GetBaseProperty(prop, true, true)) != null && (rootBaseProperty.IsVirtual || rootBaseProperty.IsPure); if (isOverride) Write("override "); else if (prop.IsPure) Write("abstract "); if (prop.IsVirtual && !isOverride && !prop.IsPure) Write("virtual "); WriteLine("{0} {1}", prop.Type, GetPropertyName(prop)); } else { WriteLine("{0} {1}.{2}", prop.Type, prop.ExplicitInterfaceImpl.Name, GetPropertyName(prop)); } WriteStartBraceIndent(); if (prop.Field != null) { if (prop.HasGetter) GeneratePropertyGetter(prop.QualifiedType, prop.Field, @class); if (prop.HasSetter) GeneratePropertySetter(prop.Field, @class); } else { if (prop.HasGetter) GeneratePropertyGetter(prop.QualifiedType, prop.GetMethod, @class, prop.IsPure, prop); if (prop.HasSetter) GeneratePropertySetter(prop.SetMethod, @class, prop.IsPure, prop); } WriteCloseBraceIndent(); PopBlock(NewLineKind.BeforeNextBlock); } } private string GetPropertyName(Property prop) { return prop.Parameters.Count == 0 ? prop.Name : string.Format("this[{0}]", FormatMethodParameters(prop.Parameters)); } private void GenerateVariable(Class @class, Type type, Variable variable) { PushBlock(CSharpBlockKind.Variable); GenerateDeclarationCommon(variable); WriteLine("public static {0} {1}", type, variable.Name); WriteStartBraceIndent(); GeneratePropertyGetter(variable.QualifiedType, variable, @class); if (!variable.QualifiedType.Qualifiers.IsConst) GeneratePropertySetter(variable, @class); WriteCloseBraceIndent(); PopBlock(NewLineKind.BeforeNextBlock); } #region Virtual Tables public List GetUniqueVTableMethodEntries(Class @class) { var uniqueEntries = new OrderedSet(); var vTableMethodEntries = VTables.GatherVTableMethodEntries(@class); foreach (var entry in vTableMethodEntries.Where(e => !e.IsIgnored() && !e.Method.IsOperator)) uniqueEntries.Add(entry); return uniqueEntries.ToList(); } public void GenerateVTable(Class @class) { var wrappedEntries = GetUniqueVTableMethodEntries(@class); if (wrappedEntries.Count == 0) return; PushBlock(CSharpBlockKind.Region); WriteLine("#region Virtual table interop"); NewLine(); // Generate a delegate type for each method. foreach (var method in wrappedEntries.Select(e => e.Method)) { GenerateVTableMethodDelegates(@class, method); } WriteLine("private static void*[] __ManagedVTables;"); if (wrappedEntries.Any(e => e.Method.IsDestructor)) WriteLine("private static void*[] __ManagedVTablesDtorOnly;"); WriteLine("private static void*[] _Thunks;"); NewLine(); GenerateVTableClassSetup(@class, wrappedEntries); WriteLine("#endregion"); PopBlock(NewLineKind.BeforeNextBlock); } private void GenerateVTableClassSetup(Class @class, IList wrappedEntries) { const string destructorOnly = "destructorOnly"; WriteLine("private void SetupVTables(bool {0} = false)", destructorOnly); WriteStartBraceIndent(); WriteLine("if (__OriginalVTables != null)"); WriteLineIndent("return;"); WriteLine("var native = (Internal*) {0}.ToPointer();", Helpers.InstanceIdentifier); NewLine(); SaveOriginalVTablePointers(@class.Layout.VFTables); NewLine(); var hasVirtualDtor = wrappedEntries.Any(e => e.Method.IsDestructor); if (!hasVirtualDtor) { WriteLine("if ({0})", destructorOnly); WriteLineIndent("return;"); } // Get the _Thunks WriteLine("if (_Thunks == null)"); WriteStartBraceIndent(); WriteLine("_Thunks = new void*[{0}];", wrappedEntries.Count); var uniqueEntries = new HashSet(); for (int i = 0; i < wrappedEntries.Count; i++) { var entry = wrappedEntries[i]; var method = entry.Method; var name = GetVTableMethodDelegateName(method); var instance = name + "Instance"; if (uniqueEntries.Add(entry)) WriteLine("{0} += {1}Hook;", instance, name); WriteLine("_Thunks[{0}] = Marshal.GetFunctionPointerForDelegate({1}).ToPointer();", i, instance); } WriteCloseBraceIndent(); NewLine(); if (hasVirtualDtor) { WriteLine("if ({0})", destructorOnly); WriteStartBraceIndent(); WriteLine("if (__ManagedVTablesDtorOnly == null)"); WriteStartBraceIndent(); AllocateNewVTables(@class, wrappedEntries, true); WriteCloseBraceIndent(); WriteLine("else"); WriteStartBraceIndent(); } WriteLine("if (__ManagedVTables == null)"); WriteStartBraceIndent(); AllocateNewVTables(@class, wrappedEntries, false); if (hasVirtualDtor) WriteCloseBraceIndent(); WriteCloseBraceIndent(); NewLine(); } private void AllocateNewVTables(Class @class, IList wrappedEntries, bool destructorOnly) { if (Options.IsMicrosoftAbi) AllocateNewVTablesMS(@class, wrappedEntries, destructorOnly); else AllocateNewVTablesItanium(@class, wrappedEntries, destructorOnly); } private void SaveOriginalVTablePointers(IEnumerable vfTables) { if (Driver.Options.IsMicrosoftAbi) WriteLine("__OriginalVTables = new void*[] {{ {0} }};", string.Join(", ", vfTables.Select((v, i) => string.Format("native->vfptr{0}.ToPointer()", i)))); else WriteLine("__OriginalVTables = new void*[] { native->vfptr0.ToPointer() };"); } private void AllocateNewVTablesMS(Class @class, IList wrappedEntries, bool destructorOnly) { var managedVTables = destructorOnly ? "__ManagedVTablesDtorOnly" : "__ManagedVTables"; WriteLine("{0} = new void*[{1}];", managedVTables, @class.Layout.VFTables.Count); for (int tableIndex = 0; tableIndex < @class.Layout.VFTables.Count; tableIndex++) { var vfptr = @class.Layout.VFTables[tableIndex]; var size = vfptr.Layout.Components.Count; WriteLine("var vfptr{0} = Marshal.AllocHGlobal({1} * {2});", tableIndex, size, Driver.TargetInfo.PointerWidth / 8); WriteLine("{0}[{1}] = vfptr{1}.ToPointer();", managedVTables, tableIndex); AllocateNewVTableEntries(vfptr.Layout.Components, wrappedEntries, tableIndex, destructorOnly); } WriteCloseBraceIndent(); NewLine(); for (var i = 0; i < @class.Layout.VFTables.Count; i++) WriteLine("native->vfptr{0} = new IntPtr({1}[{0}]);", i, managedVTables); } private void AllocateNewVTablesItanium(Class @class, IList wrappedEntries, bool destructorOnly) { var managedVTables = destructorOnly ? "__ManagedVTablesDtorOnly" : "__ManagedVTables"; WriteLine("{0} = new void*[1];", managedVTables); var size = @class.Layout.Layout.Components.Count; var pointerSize = Driver.TargetInfo.PointerWidth / 8; WriteLine("var vtptr = Marshal.AllocHGlobal({0} * {1});", size, pointerSize); WriteLine("var vfptr0 = vtptr + {0} * {1};", VTables.ItaniumOffsetToTopAndRTTI, pointerSize); WriteLine("{0}[0] = vfptr0.ToPointer();", managedVTables); AllocateNewVTableEntries(@class.Layout.Layout.Components, wrappedEntries, 0, destructorOnly); WriteCloseBraceIndent(); NewLine(); WriteLine("native->vfptr0 = new IntPtr({0}[0]);", managedVTables); } private void AllocateNewVTableEntries(IList entries, IList wrappedEntries, int tableIndex, bool destructorOnly) { var pointerSize = Driver.TargetInfo.PointerWidth / 8; for (var i = 0; i < entries.Count; i++) { var entry = entries[i]; var offset = pointerSize * (i - (Options.IsMicrosoftAbi ? 0 : VTables.ItaniumOffsetToTopAndRTTI)); var nativeVftableEntry = string.Format("*(void**)(native->vfptr{0} + {1})", tableIndex, offset); var managedVftableEntry = string.Format("*(void**)(vfptr{0} + {1})", tableIndex, offset); if ((entry.Kind == VTableComponentKind.FunctionPointer || entry.Kind == VTableComponentKind.DeletingDtorPointer) && !entry.IsIgnored() && (!destructorOnly || entry.Method.IsDestructor || Driver.Options.ExplicitlyPatchedVirtualFunctions.Contains(entry.Method.OriginalName))) WriteLine("{0} = _Thunks[{1}];", managedVftableEntry, wrappedEntries.IndexOf(entry)); else WriteLine("{0} = {1};", managedVftableEntry, nativeVftableEntry); } } private void GenerateVTableClassSetupCall(Class @class, bool destructorOnly = false) { if (@class.IsDynamic && GetUniqueVTableMethodEntries(@class).Count > 0) { if (destructorOnly) { WriteLine("SetupVTables(true);"); return; } var typeFullName = TypePrinter.VisitClassDecl(@class); if (!string.IsNullOrEmpty(Driver.Options.OutputNamespace)) typeFullName = string.Format("{0}.{1}", Driver.Options.OutputNamespace, typeFullName); WriteLine("SetupVTables(GetType().FullName == \"{0}\");", typeFullName); } } private void GenerateVTableManagedCall(Method method) { if (method.IsDestructor) { WriteLine("{0}.Dispose(false);", Helpers.TargetIdentifier); return; } var marshals = new List(); for (int i = 0; i < method.Parameters.Count; i++) { var param = method.Parameters[i]; if (!param.IsGenerated && param.GenerationKind != GenerationKind.Link) continue; if (param.Kind == ParameterKind.IndirectReturnType) continue; var ctx = new CSharpMarshalContext(Driver) { ReturnType = param.QualifiedType, ReturnVarName = param.Name, ParameterIndex = i }; var marshal = new CSharpMarshalNativeToManagedPrinter(ctx) { MarshalsParameter = true }; param.Visit(marshal); if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore)) Write(marshal.Context.SupportBefore); marshals.Add(marshal.Context.Return); } var hasReturn = !method.OriginalReturnType.Type.IsPrimitiveType(PrimitiveType.Void); if (hasReturn) Write("var {0} = ", Helpers.ReturnIdentifier); if (method.IsGenerated || method.GenerationKind == GenerationKind.Link) { WriteLine("{0}.{1}({2});", Helpers.TargetIdentifier, method.Name, string.Join(", ", marshals)); } else { InvokeProperty(method, marshals); } if (hasReturn) { var param = new Parameter { Name = Helpers.ReturnIdentifier, QualifiedType = method.OriginalReturnType }; // Marshal the managed result to native var ctx = new CSharpMarshalContext(Driver) { ArgName = Helpers.ReturnIdentifier, Parameter = param, Function = method }; var marshal = new CSharpMarshalManagedToNativePrinter(ctx); method.OriginalReturnType.Visit(marshal); if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore)) Write(marshal.Context.SupportBefore); if (method.HasIndirectReturnTypeParameter) { var retParam = method.Parameters.First(p => p.Kind == ParameterKind.IndirectReturnType); TypePrinter.PushContext(CSharpTypePrinterContextKind.Native); WriteLine("*({0}*) {1} = {2};", method.OriginalReturnType.Visit(TypePrinter), retParam.Name, marshal.Context.Return); TypePrinter.PopContext(); } else { WriteLine("return {0};", marshal.Context.Return); } } } private void InvokeProperty(Declaration method, IEnumerable marshals) { var property = ((Class) method.Namespace).Properties.FirstOrDefault( p => p.GetMethod == method); if (property == null) { property = ((Class) method.Namespace).Properties.First( p => p.SetMethod == method); WriteLine("{0}.{1} = {2};", Helpers.TargetIdentifier, property.Name, string.Join(", ", marshals)); } else { WriteLine("{0}.{1};", Helpers.TargetIdentifier, property.Name); } } private void GenerateVTableMethodDelegates(Class @class, Method method) { PushBlock(CSharpBlockKind.VTableDelegate); // This works around a parser bug, see https://github.com/mono/CppSharp/issues/202 if (method.Signature != null) { var cleanSig = method.Signature.ReplaceLineBreaks(""); cleanSig = Regex.Replace(cleanSig, @"\s+", " "); WriteLine("// {0}", cleanSig); } CSharpTypePrinterResult retType; var @params = GatherInternalParams(method, out retType); var vTableMethodDelegateName = GetVTableMethodDelegateName(method); WriteLine("private static {0} {1}Instance;", DelegatesPass.Delegates[method].Visit(TypePrinter), vTableMethodDelegateName); NewLine(); WriteLine("private static {0} {1}Hook({2})", retType, vTableMethodDelegateName, string.Join(", ", @params)); WriteStartBraceIndent(); WriteLine("if (!NativeToManagedMap.ContainsKey(instance))"); WriteLineIndent("throw new global::System.Exception(\"No managed instance was found\");"); NewLine(); WriteLine("var {0} = ({1}) NativeToManagedMap[instance];", Helpers.TargetIdentifier, @class.Name); WriteLine("if ({0}.{1})", Helpers.TargetIdentifier, Helpers.OwnsNativeInstanceIdentifier); WriteLineIndent("{0}.SetupVTables();", Helpers.TargetIdentifier); GenerateVTableManagedCall(method); WriteCloseBraceIndent(); PopBlock(NewLineKind.Always); } public string GetVTableMethodDelegateName(Function function) { var nativeId = GetFunctionNativeIdentifier(function); // Trim '@' (if any) because '@' is valid only as the first symbol. nativeId = nativeId.Trim('@'); return string.Format("_{0}Delegate", nativeId); } public MemberDeclarationSyntax[] GenerateVTablePointers(Class @class) { var members = new List(); if (Options.IsMicrosoftAbi) { for (int i = 0; i < @class.Layout.VFTables.Count; i++) { var vFPtrFullOffset = (int) @class.Layout.VFTables[i].VFPtrFullOffset; members.Add(VTablePointer(i, vFPtrFullOffset)); } } else { members.Add(VTablePointer(0, 0)); } return members.ToArray(); } private FieldDeclarationSyntax VTablePointer(int i, int offset) { return SyntaxFactory.FieldDeclaration(SyntaxFactory.VariableDeclaration( SyntaxFactory.ParseTypeName("global::System.IntPtr")).AddVariables( SyntaxFactory.VariableDeclarator("vfptr" + i))) .AddAttributeLists(SyntaxFactory.AttributeList().AddAttributes( SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("FieldOffset")) .AddArgumentListArguments(SyntaxFactory.AttributeArgument( SyntaxFactory.LiteralExpression( SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(offset)))))) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); } #endregion #region Events private void GenerateEvent(Event @event) { PushBlock(CSharpBlockKind.Event, @event); TypePrinter.PushContext(CSharpTypePrinterContextKind.Native); var args = TypePrinter.VisitParameters(@event.Parameters, hasNames: true); TypePrinter.PopContext(); var delegateInstance = Generator.GeneratedIdentifier(@event.OriginalName); var delegateName = delegateInstance + "Delegate"; var delegateRaise = delegateInstance + "RaiseInstance"; WriteLine("[UnmanagedFunctionPointerAttribute(global::System.Runtime.InteropServices.CallingConvention.Cdecl)]"); WriteLine("delegate void {0}({1});", delegateName, args); WriteLine("{0} {1};", delegateName, delegateRaise); NewLine(); WriteLine("{0} {1};", @event.Type, delegateInstance); WriteLine("public event {0} {1}", @event.Type, @event.Name); WriteStartBraceIndent(); GenerateEventAdd(@event, delegateRaise, delegateName, delegateInstance); NewLine(); GenerateEventRemove(@event, delegateInstance); WriteCloseBraceIndent(); NewLine(); GenerateEventRaiseWrapper(@event, delegateInstance); PopBlock(NewLineKind.BeforeNextBlock); } private void GenerateEventAdd(Event @event, string delegateRaise, string delegateName, string delegateInstance) { WriteLine("add"); WriteStartBraceIndent(); WriteLine("if ({0} == null)", delegateRaise); WriteStartBraceIndent(); WriteLine("{0} = new {1}(_{2}Raise);", delegateRaise, delegateName, @event.Name); WriteLine("var {0} = Marshal.GetFunctionPointerForDelegate({1}).ToPointer();", Generator.GeneratedIdentifier("ptr"), delegateInstance); // Call type map here. //WriteLine("((::{0}*)NativePtr)->{1}.Connect(_fptr);", @class.QualifiedOriginalName, // @event.OriginalName); WriteCloseBraceIndent(); WriteLine("{0} = ({1})System.Delegate.Combine({0}, value);", delegateInstance, @event.Type); WriteCloseBraceIndent(); } private void GenerateEventRemove(ITypedDecl @event, string delegateInstance) { WriteLine("remove"); WriteStartBraceIndent(); WriteLine("{0} = ({1})System.Delegate.Remove({0}, value);", delegateInstance, @event.Type); WriteCloseBraceIndent(); } private void GenerateEventRaiseWrapper(Event @event, string delegateInstance) { TypePrinter.PushContext(CSharpTypePrinterContextKind.Native); var args = TypePrinter.VisitParameters(@event.Parameters, hasNames: true); TypePrinter.PopContext(); WriteLine("void _{0}Raise({1})", @event.Name, args); WriteStartBraceIndent(); var returns = new List(); foreach (var param in @event.Parameters) { var ctx = new CSharpMarshalContext(Driver) { ReturnVarName = param.Name, ReturnType = param.QualifiedType }; var marshal = new CSharpMarshalNativeToManagedPrinter(ctx); param.Visit(marshal); returns.Add(marshal.Context.Return); } WriteLine("if ({0} != null)", delegateInstance); WriteStartBraceIndent(); WriteLine("{0}({1});", delegateInstance, string.Join(", ", returns)); WriteCloseBraceIndent(); WriteCloseBraceIndent(); } #endregion #region Constructors public MemberDeclarationSyntax[] GenerateClassConstructors(Class @class) { var members = new List(); // Output a default constructor that takes the native pointer. members.AddRange(GenerateNativeConstructor(@class)); //foreach (var ctor in @class.Constructors) //{ // if (ASTUtils.CheckIgnoreMethod(ctor, Options)) // continue; // GenerateMethod(ctor, @class); //} if (@class.IsRefType) { if (Options.GenerateFinalizers) members.Add(GenerateClassFinalizer(@class)); // ensure any virtual dtor in the chain is called var dtor = @class.Destructors.FirstOrDefault(d => d.Access != AccessSpecifier.Private && d.IsVirtual); var baseDtor = @class.BaseClass == null ? null : @class.BaseClass.Destructors.FirstOrDefault(d => !d.IsVirtual); if (ShouldGenerateClassNativeField(@class) || (dtor != null && baseDtor != null) || // virtual destructors in abstract classes may lack a pointer in the v-table // so they have to be called by symbol; thus we need an explicit Dispose override @class.IsAbstract) members.AddRange(GenerateDisposeMethods(@class)); } return members.ToArray(); } private MemberDeclarationSyntax GenerateClassFinalizer(INamedDecl @class) { return SyntaxFactory.DestructorDeclaration(@class.Name).AddBodyStatements( SyntaxFactory.ParseStatement("Dispose(false)")); } private IEnumerable GenerateDisposeMethods(Class @class) { var members = new List(); var hasBaseClass = @class.HasBaseClass && @class.BaseClass.IsRefType; // Generate the IDispose Dispose() method. if (!hasBaseClass) { var dispose = SyntaxFactory.MethodDeclaration( SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)), "Dispose") .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)) .AddBodyStatements(SyntaxFactory.ParseStatement("Dispose(disposing: true);")); if (Options.GenerateFinalizers) dispose = dispose.AddBodyStatements( SyntaxFactory.ParseStatement("GC.SuppressFinalize(this);")); members.Add(dispose); } // Generate Dispose(bool) method var disposeBool = SyntaxFactory.MethodDeclaration( SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)), "Dispose") .AddModifiers(SyntaxFactory.Token( @class.IsValueType ? SyntaxKind.PrivateKeyword : SyntaxKind.ProtectedKeyword)) .AddParameterListParameters( SyntaxFactory.Parameter(SyntaxFactory.Identifier("disposing")).WithType( SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.BoolKeyword)))) .AddBodyStatements(); if (@class.IsRefType) disposeBool = disposeBool.AddModifiers(SyntaxFactory.Token( hasBaseClass ? SyntaxKind.OverrideKeyword : SyntaxKind.VirtualKeyword)); members.Add(disposeBool); //WriteLine("if (!{0} && disposing)", Helpers.OwnsNativeInstanceIdentifier); //WriteLineIndent("throw new global::System.InvalidOperationException" + // "(\"Managed instances owned by native code cannot be disposed of.\");"); //if (@class.IsRefType) //{ // var @base = @class.GetNonIgnoredRootBase(); // // Use interfaces if any - derived types with a this class as a seconary base, must be compatible with the map // var @interface = @base.Namespace.Classes.Find(c => c.OriginalClass == @base); // // The local var must be of the exact type in the object map because of TryRemove // WriteLine("{0} {1};", // (@interface ?? (@base.IsAbstractImpl ? @base.BaseClass : @base)).Visit(TypePrinter), // Helpers.DummyIdentifier); // WriteLine("NativeToManagedMap.TryRemove({0}, out {1});", // Helpers.InstanceIdentifier, Helpers.DummyIdentifier); // if (@class.IsDynamic && GetUniqueVTableMethodEntries(@class).Count != 0) // { // if (Options.IsMicrosoftAbi) // for (var i = 0; i < @class.Layout.VFTables.Count; i++) // WriteLine("((Internal*) {0})->vfptr{1} = new global::System.IntPtr(__OriginalVTables[{1}]);", // Helpers.InstanceIdentifier, i); // else // WriteLine("((Internal*) {0})->vfptr0 = new global::System.IntPtr(__OriginalVTables[0]);", // Helpers.InstanceIdentifier); // } //} //var dtor = @class.Destructors.FirstOrDefault(); //if (dtor != null && dtor.Access != AccessSpecifier.Private && // @class.HasNonTrivialDestructor && !dtor.IsPure) //{ // NativeLibrary library; // if (!Options.CheckSymbols || // Driver.Symbols.FindLibraryBySymbol(dtor.Mangled, out library)) // { // if (dtor.IsVirtual) // { // GenerateVirtualFunctionCall(dtor, @class, true); // if (@class.IsAbstract) // { // WriteCloseBraceIndent(); // WriteLine("else"); // PushIndent(); // GenerateInternalFunctionCall(dtor); // PopIndent(); // } // } // else // GenerateInternalFunctionCall(dtor); // } //} //WriteLine("if ({0})", Helpers.OwnsNativeInstanceIdentifier); //WriteLineIndent("Marshal.FreeHGlobal({0});", Helpers.InstanceIdentifier); return members; } private MemberDeclarationSyntax[] GenerateNativeConstructor(Class @class) { var members = new List(); var shouldGenerateClassNativeField = ShouldGenerateClassNativeField(@class); if (@class.IsRefType && shouldGenerateClassNativeField) { members.Add(SyntaxFactory.FieldDeclaration(SyntaxFactory.VariableDeclaration( SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.BoolKeyword))) .AddVariables( SyntaxFactory.VariableDeclarator(Helpers.OwnsNativeInstanceIdentifier))) .AddModifiers(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword))); } return members.ToArray(); var className = @class.IsAbstractImpl ? @class.BaseClass.Name : @class.Name; var ctorCall = string.Format("{0}{1}", @class.Name, @class.IsAbstract ? "Internal" : ""); if (!@class.IsAbstractImpl) { PushBlock(CSharpBlockKind.Method); WriteLine("public static {0}{1} {2}(global::System.IntPtr native{3}{4})", @class.NeedsBase && !@class.BaseClass.IsInterface ? "new " : string.Empty, @class.Name, Helpers.CreateInstanceIdentifier, @class.IsRefType ? ", bool ownsNativeInstance = false" : string.Empty, ", bool skipVTables = false"); WriteStartBraceIndent(); WriteLine("return new {0}(({1}.Internal*) native, skipVTables){2};", ctorCall, className, @class.IsRefType ? string.Format(" {{ {0} = ownsNativeInstance }}", Helpers.OwnsNativeInstanceIdentifier) : string.Empty); WriteCloseBraceIndent(); PopBlock(NewLineKind.BeforeNextBlock); } GenerateNativeConstructorByValue(@class, className, ctorCall); PushBlock(CSharpBlockKind.Method); WriteLine("{0} {1}({2}.Internal* native, bool skipVTables = false){3}", @class.IsAbstractImpl ? "internal" : (@class.IsRefType ? "protected" : "private"), @class.Name, className, @class.IsValueType ? " : this()" : string.Empty); var hasBaseClass = @class.HasBaseClass && @class.BaseClass.IsRefType; if (hasBaseClass) WriteLineIndent(": base(({0}.Internal*) null)", @class.BaseClass.Visit(TypePrinter)); WriteStartBraceIndent(); if (@class.IsRefType) { if (@class.HasBaseClass) WriteLine("{0} = {1};", Helpers.PointerAdjustmentIdentifier, @class.ComputeNonVirtualBaseClassOffsetTo(@class.BaseClass)); if (!@class.IsAbstractImpl) { WriteLine("if (native == null)"); WriteLineIndent("return;"); } WriteLine("{0} = new global::System.IntPtr(native);", Helpers.InstanceIdentifier); var dtor = @class.Destructors.FirstOrDefault(); var hasVTables = @class.IsDynamic && GetUniqueVTableMethodEntries(@class).Count > 0; var setupVTables = !@class.IsAbstractImpl && hasVTables && dtor != null && dtor.IsVirtual; if (setupVTables) { WriteLine("if (skipVTables)"); PushIndent(); } if (@class.IsAbstractImpl || hasVTables) SaveOriginalVTablePointers(@class.Layout.VFTables); if (setupVTables) { PopIndent(); WriteLine("else"); PushIndent(); GenerateVTableClassSetupCall(@class, destructorOnly: true); PopIndent(); } } else { WriteLine("{0} = *native;", Helpers.InstanceField); } WriteCloseBraceIndent(); PopBlock(NewLineKind.BeforeNextBlock); } private void GenerateNativeConstructorByValue(Class @class, string className, string ctorCall) { if (!@class.IsAbstractImpl) { PushBlock(CSharpBlockKind.Method); WriteLine("public static {0} {1}({0}.Internal native, bool skipVTables = false)", className, Helpers.CreateInstanceIdentifier); WriteStartBraceIndent(); WriteLine("return new {0}(native, skipVTables);", ctorCall); WriteCloseBraceIndent(); PopBlock(NewLineKind.BeforeNextBlock); } if (@class.IsRefType && !@class.IsAbstract) { PushBlock(CSharpBlockKind.Method); WriteLine("private static {0}.Internal* __CopyValue({0}.Internal native)", className); WriteStartBraceIndent(); var copyCtorMethod = @class.Methods.FirstOrDefault(method => method.IsCopyConstructor); if (@class.HasNonTrivialCopyConstructor && copyCtorMethod != null && copyCtorMethod.IsGenerated) { // Allocate memory for a new native object and call the ctor. WriteLine("var ret = Marshal.AllocHGlobal({0});", @class.Layout.Size); WriteLine("{0}.Internal.{1}(ret, new global::System.IntPtr(&native));", @class.Visit(TypePrinter), GetFunctionNativeIdentifier(copyCtorMethod)); WriteLine("return ({0}.Internal*) ret;", className); } else { WriteLine("var ret = ({0}.Internal*) Marshal.AllocHGlobal({1});", className, @class.Layout.Size); WriteLine("*ret = native;", className); WriteLine("return ret;"); } WriteCloseBraceIndent(); PopBlock(NewLineKind.BeforeNextBlock); } if (!@class.IsAbstract) { PushBlock(CSharpBlockKind.Method); WriteLine("{0} {1}({2}.Internal native, bool skipVTables = false)", @class.IsAbstractImpl ? "internal" : "private", @class.Name, className); WriteLineIndent(@class.IsRefType ? ": this(__CopyValue(native), skipVTables)" : ": this()"); WriteStartBraceIndent(); if (@class.IsRefType) { WriteLine("{0} = true;", Helpers.OwnsNativeInstanceIdentifier); WriteLine("NativeToManagedMap[{0}] = this;", Helpers.InstanceIdentifier); } else { WriteLine("{0} = native;", Helpers.InstanceField); } WriteCloseBraceIndent(); PopBlock(NewLineKind.BeforeNextBlock); } } private void GenerateClassConstructorBase(Class @class, Method method) { var hasBase = @class.HasBaseClass; if (hasBase && !@class.IsValueType) { PushIndent(); Write(": this("); Write(method != null ? "(Internal*) null" : "native"); WriteLine(")"); PopIndent(); } if (@class.IsValueType) WriteLineIndent(": this()"); } #endregion #region Methods / Functions public void GenerateFunction(Function function) { PushBlock(CSharpBlockKind.Function); GenerateDeclarationCommon(function); var functionName = GetFunctionIdentifier(function); Write("public static {0} {1}(", function.OriginalReturnType, functionName); Write(FormatMethodParameters(function.Parameters)); WriteLine(")"); WriteStartBraceIndent(); if (function.SynthKind == FunctionSynthKind.DefaultValueOverload) GenerateOverloadCall(function); else GenerateInternalFunctionCall(function); WriteCloseBraceIndent(); PopBlock(NewLineKind.BeforeNextBlock); } public void GenerateMethod(Method method, Class @class) { PushBlock(CSharpBlockKind.Method, method); GenerateDeclarationCommon(method); if (method.ExplicitInterfaceImpl == null) { //Write(Helpers.GetAccess(GetValidMethodAccess(method))); } // check if overriding a function from a secondary base Method rootBaseMethod; var isOverride = method.IsOverride && (rootBaseMethod = @class.GetBaseMethod(method, true)) != null && (rootBaseMethod.IsVirtual || rootBaseMethod.IsPure); if (method.IsVirtual && !isOverride && !method.IsOperator && !method.IsPure) Write("virtual "); var isBuiltinOperator = method.IsOperator && Operators.IsBuiltinOperator(method.OperatorKind); if (method.IsStatic || isBuiltinOperator) Write("static "); if (isOverride) { if (method.Access == AccessSpecifier.Private) Write("sealed "); Write("override "); } if (method.IsPure) Write("abstract "); var functionName = GetMethodIdentifier(method); if (method.IsConstructor || method.IsDestructor) Write("{0}(", functionName); else if (method.ExplicitInterfaceImpl != null) Write("{0} {1}.{2}(", method.OriginalReturnType, method.ExplicitInterfaceImpl.Name, functionName); else if (method.OperatorKind == CXXOperatorKind.Conversion || method.OperatorKind == CXXOperatorKind.ExplicitConversion) Write("{0} {1}(", functionName, method.OriginalReturnType); else Write("{0} {1}(", method.OriginalReturnType, functionName); Write(FormatMethodParameters(method.Parameters)); Write(")"); if (method.SynthKind == FunctionSynthKind.DefaultValueOverload && method.IsConstructor && !method.IsPure) { Write(" : this({0})", string.Join(", ", method.Parameters.Where( p => p.Kind == ParameterKind.Regular).Select( p => p.Ignore ? ExpressionPrinter.VisitExpression(p.DefaultArgument).Value : p.Name))); } if (method.IsPure) { Write(";"); PopBlock(NewLineKind.BeforeNextBlock); return; } NewLine(); if (method.Kind == CXXMethodKind.Constructor && method.SynthKind != FunctionSynthKind.DefaultValueOverload) GenerateClassConstructorBase(@class, method); WriteStartBraceIndent(); if (method.IsProxy) goto SkipImpl; if (method.SynthKind == FunctionSynthKind.DefaultValueOverload) { if (!method.IsConstructor) { GenerateOverloadCall(method); } goto SkipImpl; } if (@class.IsRefType) { if (method.IsConstructor) { GenerateClassConstructor(method, @class); } else if (method.IsOperator) { GenerateOperator(method); } else if (method.SynthKind == FunctionSynthKind.AbstractImplCall) { GenerateVirtualFunctionCall(method, @class.BaseClass); } else if (method.IsVirtual) { GenerateVirtualFunctionCall(method, @class); } else { GenerateInternalFunctionCall(method); } } else if (@class.IsValueType) { if (method.IsConstructor) { GenerateInternalFunctionCall(method); } else if (method.IsOperator) { GenerateOperator(method); } else { GenerateInternalFunctionCall(method); } } SkipImpl: WriteCloseBraceIndent(); if (method.OperatorKind == CXXOperatorKind.EqualEqual) { GenerateEqualsAndGetHashCode(method, @class); } PopBlock(NewLineKind.BeforeNextBlock); } private string OverloadParamNameWithDefValue(Parameter p, ref int index) { return p.Type.IsPointerToPrimitiveType() && p.Usage == ParameterUsage.InOut && p.HasDefaultValue ? "ref param" + index++ : ExpressionPrinter.VisitExpression(p.DefaultArgument).Value; } private void GenerateOverloadCall(Function function) { for (int i = 0, j = 0; i < function.Parameters.Count; i++) { var parameter = function.Parameters[i]; PrimitiveType primitiveType; if (parameter.Kind == ParameterKind.Regular && parameter.Ignore && parameter.Type.IsPointerToPrimitiveType(out primitiveType) && parameter.Usage == ParameterUsage.InOut && parameter.HasDefaultValue) { var pointeeType = ((PointerType) parameter.Type).Pointee.ToString(); WriteLine("{0} param{1} = {2};", pointeeType, j++, primitiveType == PrimitiveType.Bool ? "false" : "0"); } } GenerateManagedCall(function); } private void GenerateManagedCall(Function function, bool prependBase = false) { var type = function.OriginalReturnType.Type; var index = 0; WriteLine("{0}{1}{2}({3});", type.IsPrimitiveType(PrimitiveType.Void) ? string.Empty : "return ", prependBase ? "base." : string.Empty, function.Name, string.Join(", ", function.Parameters.Where( p => p.Kind != ParameterKind.IndirectReturnType).Select( p => p.Ignore ? OverloadParamNameWithDefValue(p, ref index) : (p.Usage == ParameterUsage.InOut ? "ref " : string.Empty) + p.Name))); } private void GenerateEqualsAndGetHashCode(Function method, Class @class) { Class leftHandSide; Class rightHandSide; if (method.Parameters[0].Type.SkipPointerRefs().TryGetClass(out leftHandSide) && leftHandSide.OriginalPtr == @class.OriginalPtr && method.Parameters[1].Type.SkipPointerRefs().TryGetClass(out rightHandSide) && rightHandSide.OriginalPtr == @class.OriginalPtr) { NewLine(); WriteLine("public override bool Equals(object obj)"); WriteStartBraceIndent(); if (@class.IsRefType) { WriteLine("return this == obj as {0};", @class.Name); } else { WriteLine("if (!(obj is {0})) return false;", @class.Name); WriteLine("return this == ({0}) obj;", @class.Name); } WriteCloseBraceIndent(); NewLine(); WriteLine("public override int GetHashCode()"); WriteStartBraceIndent(); if (@class.IsRefType) { WriteLine("if ({0} == global::System.IntPtr.Zero)", Helpers.InstanceIdentifier); WriteLineIndent("return global::System.IntPtr.Zero.GetHashCode();"); WriteLine("return (*(Internal*) {0}).GetHashCode();", Helpers.InstanceIdentifier); } else { WriteLine("return {0}.GetHashCode();", Helpers.InstanceIdentifier); } WriteCloseBraceIndent(); } } private void CheckArgumentRange(Function method) { if (Driver.Options.MarshalCharAsManagedChar) { foreach (var param in method.Parameters.Where( p => p.Type.IsPrimitiveType(PrimitiveType.Char))) { WriteLine("if ({0} < char.MinValue || {0} > sbyte.MaxValue)", param.Name); WriteLineIndent( "throw new global::System.ArgumentException(\"{0} must be in the range {1} - {2}.\");", param.Name, (int) char.MinValue, sbyte.MaxValue); } } } private static AccessSpecifier GetValidMethodAccess(Method method) { return method.IsOverride ? ((Class) method.Namespace).GetBaseMethod(method).Access : method.Access; } private static AccessSpecifier GetValidPropertyAccess(Property property) { if (property.Access == AccessSpecifier.Public) return AccessSpecifier.Public; return property.IsOverride ? ((Class) property.Namespace).GetBaseProperty(property).Access : property.Access; } private void GenerateVirtualPropertyCall(Method method, Class @class, Property property, List parameters = null) { if (property.IsOverride && !property.IsPure && method.SynthKind != FunctionSynthKind.AbstractImplCall && @class.HasNonAbstractBasePropertyInPrimaryBase(property)) { WriteLine(parameters == null ? "return base.{0};" : "base.{0} = value;", property.Name); } else { string delegateId; GetVirtualCallDelegate(method, @class, out delegateId); GenerateFunctionCall(delegateId, parameters ?? method.Parameters, method); } } private void GenerateVirtualFunctionCall(Method method, Class @class, bool forceVirtualCall = false) { if (!forceVirtualCall && method.IsOverride && !method.IsPure && method.SynthKind != FunctionSynthKind.AbstractImplCall && @class.HasNonAbstractBaseMethodInPrimaryBase(method)) { GenerateManagedCall(method, true); } else { string delegateId; GetVirtualCallDelegate(method, @class, out delegateId); GenerateFunctionCall(delegateId, method.Parameters, method); } } public void GetVirtualCallDelegate(Method method, Class @class, out string delegateId) { var i = VTables.GetVTableIndex(method.OriginalFunction ?? method, @class); WriteLine("var {0} = *(void**) ((IntPtr) __OriginalVTables[0] + {1} * {2});", Helpers.SlotIdentifier, i, Driver.TargetInfo.PointerWidth / 8); if (method.IsDestructor && @class.IsAbstract) { WriteLine("if ({0} != null)", Helpers.SlotIdentifier); WriteStartBraceIndent(); } var @delegate = GetVTableMethodDelegateName(method.OriginalFunction ?? method); delegateId = Generator.GeneratedIdentifier(@delegate); WriteLine("var {0} = ({1}) Marshal.GetDelegateForFunctionPointer(new IntPtr({2}), typeof({1}));", delegateId, DelegatesPass.Delegates[method].Visit(TypePrinter), Helpers.SlotIdentifier); } private void GenerateOperator(Method method) { if (method.SynthKind == FunctionSynthKind.ComplementOperator) { if (method.Kind == CXXMethodKind.Conversion) { // To avoid ambiguity when having the multiple inheritance pass enabled var paramType = method.Parameters[0].Type.SkipPointerRefs().Desugar(); Class paramClass; Class @interface = null; if (paramType.TryGetClass(out paramClass)) @interface = paramClass.Namespace.Classes.Find(c => c.OriginalClass == paramClass); var paramName = string.Format("{0}{1}", method.Parameters[0].Type.IsPrimitiveTypeConvertibleToRef() ? "ref *" : string.Empty, method.Parameters[0].Name); if (@interface != null) WriteLine("return new {0}(({2}) {1});", method.ConversionType, paramName, @interface.Name); else WriteLine("return new {0}({1});", method.ConversionType, paramName); } else { var @operator = Operators.GetOperatorOverloadPair(method.OperatorKind); WriteLine("return !({0} {1} {2});", method.Parameters[0].Name, @operator, method.Parameters[1].Name); } return; } if (method.OperatorKind == CXXOperatorKind.EqualEqual || method.OperatorKind == CXXOperatorKind.ExclaimEqual) { WriteLine("bool {0}Null = ReferenceEquals({0}, null);", method.Parameters[0].Name); WriteLine("bool {0}Null = ReferenceEquals({0}, null);", method.Parameters[1].Name); WriteLine("if ({0}Null || {1}Null)", method.Parameters[0].Name, method.Parameters[1].Name); WriteLineIndent("return {0}{1}Null && {2}Null{3};", method.OperatorKind == CXXOperatorKind.EqualEqual ? string.Empty : "!(", method.Parameters[0].Name, method.Parameters[1].Name, method.OperatorKind == CXXOperatorKind.EqualEqual ? string.Empty : ")"); } GenerateInternalFunctionCall(method); } private void GenerateClassConstructor(Method method, Class @class) { WriteLine("{0} = Marshal.AllocHGlobal({1});", Helpers.InstanceIdentifier, @class.Layout.Size); WriteLine("{0} = true;", Helpers.OwnsNativeInstanceIdentifier); WriteLine("NativeToManagedMap[{0}] = this;", Helpers.InstanceIdentifier); if (method.IsCopyConstructor) { if (@class.HasNonTrivialCopyConstructor) GenerateInternalFunctionCall(method); else WriteLine("*(({0}.Internal*) {2}) = *(({0}.Internal*) {1}.{2});", @class.Name, method.Parameters[0].Name, Helpers.InstanceIdentifier); } else { if (!method.IsDefaultConstructor || @class.HasNonTrivialDefaultConstructor) GenerateInternalFunctionCall(method); } GenerateVTableClassSetupCall(@class); } public void GenerateInternalFunctionCall(Function function, List parameters = null, Type returnType = null) { if (parameters == null) parameters = function.Parameters; CheckArgumentRange(function); var functionName = string.Format("Internal.{0}", GetFunctionNativeIdentifier(function.OriginalFunction ?? function)); GenerateFunctionCall(functionName, parameters, function, returnType); } public void GenerateFunctionCall(string functionName, List parameters, Function function, Type returnType = null) { if (function.IsPure) { WriteLine("throw new System.NotImplementedException();"); return; } var retType = function.OriginalReturnType; if (returnType == null) returnType = retType.Type; var method = function as Method; var hasThisReturnStructor = method != null && (method.IsConstructor || method.IsDestructor); var needsReturn = !retType.Type.IsPrimitiveType(PrimitiveType.Void) && !hasThisReturnStructor; var isValueType = false; var needsInstance = false; Parameter operatorParam = null; if (method != null) { var @class = (Class) method.Namespace; isValueType = @class.IsValueType; operatorParam = method.Parameters.FirstOrDefault( p => p.Kind == ParameterKind.OperatorParameter); needsInstance = !method.IsStatic || operatorParam != null; } var @params = GenerateFunctionParamsMarshal(parameters, function); var originalFunction = function.OriginalFunction ?? function; if (originalFunction.HasIndirectReturnTypeParameter) { var indirectRetType = originalFunction.Parameters.First( parameter => parameter.Kind == ParameterKind.IndirectReturnType); Class retClass; indirectRetType.Type.Desugar().TryGetClass(out retClass); TypeMap typeMap; string construct = null; if (Driver.TypeDatabase.FindTypeMap(retClass, out typeMap)) construct = typeMap.CSharpConstruct(); if (construct == null) { WriteLine("var {0} = new {1}.Internal();", Helpers.ReturnIdentifier, (retClass.OriginalClass ?? retClass).Visit(TypePrinter)); } else { if (string.IsNullOrWhiteSpace(construct)) WriteLine("{0} {1};", typeMap.CSharpSignature(new CSharpTypePrinterContext { Type = indirectRetType.Type.Desugar() }), Helpers.ReturnIdentifier); else WriteLine("var {0} = {1};", construct); } } var names = new List(); foreach (var param in @params) { if (param.Param == operatorParam && needsInstance) continue; var name = new StringBuilder(); if (param.Context != null && !string.IsNullOrWhiteSpace(param.Context.ArgumentPrefix)) name.Append(param.Context.ArgumentPrefix); name.Append(param.Name); names.Add(name.ToString()); } var needsFixedThis = needsInstance && isValueType; if (originalFunction.HasIndirectReturnTypeParameter) { var name = string.Format("new IntPtr(&{0})", Helpers.ReturnIdentifier); names.Insert(0, name); } if (needsInstance) { var instanceIndex = GetInstanceParamIndex(method); if (needsFixedThis) { names.Insert(instanceIndex, "new global::System.IntPtr(__instancePtr)"); } else { names.Insert(instanceIndex, operatorParam != null ? @params[0].Name : GetInstanceParam(function)); } } if (needsFixedThis) { if (operatorParam == null) { WriteLine("fixed (Internal* __instancePtr = &{0})", Helpers.InstanceField); WriteStartBraceIndent(); } else { WriteLine("var __instancePtr = &{0}.{1};", operatorParam.Name, Helpers.InstanceField); } } if (needsReturn && !originalFunction.HasIndirectReturnTypeParameter) Write("var {0} = ", Helpers.ReturnIdentifier); WriteLine("{0}({1});", functionName, string.Join(", ", names)); var cleanups = new List(); GenerateFunctionCallOutParams(@params, cleanups); cleanups.AddRange( from param in @params select param.Context into context where context != null && !string.IsNullOrWhiteSpace(context.Cleanup) select context.Cleanup); foreach (var cleanup in cleanups) { Write(cleanup); } if (needsReturn) { var ctx = new CSharpMarshalContext(Driver) { ArgName = Helpers.ReturnIdentifier, ReturnVarName = Helpers.ReturnIdentifier, ReturnType = retType, Parameter = operatorParam }; var marshal = new CSharpMarshalNativeToManagedPrinter(ctx); retType.CSharpMarshalToManaged(marshal); if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore)) Write(marshal.Context.SupportBefore); // Special case for indexer - needs to dereference if the internal // function is a pointer type and the property is not. if (retType.Type.IsAddress() && retType.Type.GetPointee().Equals(returnType) && returnType.IsPrimitiveType()) WriteLine("return *{0};", marshal.Context.Return); else WriteLine("return {0};", marshal.Context.Return); } if (needsFixedThis && operatorParam == null) WriteCloseBraceIndent(); var numFixedBlocks = @params.Count(param => param.HasFixedBlock); for(var i = 0; i < numFixedBlocks; ++i) WriteCloseBraceIndent(); } private static string GetInstanceParam(Function function) { var from = (Class) function.Namespace; var to = function.OriginalFunction == null ? @from.BaseClass : (Class) function.OriginalFunction.Namespace; var baseOffset = 0; if (to != null) { to = to.OriginalClass ?? to; baseOffset = @from.ComputeNonVirtualBaseClassOffsetTo(to); } var isPrimaryBase = from.BaseClass == to; if (isPrimaryBase) { return string.Format("({0} + {1}{2})", Helpers.InstanceIdentifier, Helpers.PointerAdjustmentIdentifier, baseOffset == 0 ? string.Empty : (" - " + baseOffset)); } return string.Format("({0}{1})", Helpers.InstanceIdentifier, baseOffset == 0 ? string.Empty : " + " + baseOffset); } private int GetInstanceParamIndex(Function method) { if (Options.IsMicrosoftAbi) return 0; var indirectReturnType = method.Parameters.FirstOrDefault( parameter => parameter.Kind == ParameterKind.IndirectReturnType); var indirectReturnTypeIndex = method.Parameters.IndexOf(indirectReturnType); return indirectReturnTypeIndex >= 0 ? ++indirectReturnTypeIndex : 0; } private void GenerateFunctionCallOutParams(IEnumerable @params, ICollection cleanups) { foreach (var paramInfo in @params) { var param = paramInfo.Param; if (!(param.IsOut || param.IsInOut)) continue; if (param.Type.IsPrimitiveTypeConvertibleToRef()) continue; var nativeVarName = paramInfo.Name; var ctx = new CSharpMarshalContext(Driver) { Parameter = param, ArgName = nativeVarName, ReturnVarName = nativeVarName, ReturnType = param.QualifiedType }; var marshal = new CSharpMarshalNativeToManagedPrinter(ctx); param.CSharpMarshalToManaged(marshal); if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore)) Write(marshal.Context.SupportBefore); WriteLine("{0} = {1};", param.Name, marshal.Context.Return); if (!string.IsNullOrWhiteSpace(marshal.Context.Cleanup)) cleanups.Add(marshal.Context.Cleanup); } } public struct ParamMarshal { public string Name; public Parameter Param; public CSharpMarshalContext Context; public bool HasFixedBlock; } public List GenerateFunctionParamsMarshal(IEnumerable @params, Function function = null) { var marshals = new List(); var paramIndex = 0; foreach (var param in @params) { if (param.Kind == ParameterKind.IndirectReturnType) continue; marshals.Add(GenerateFunctionParamMarshal(param, paramIndex, function)); paramIndex++; } return marshals; } private ParamMarshal GenerateFunctionParamMarshal(Parameter param, int paramIndex, Function function = null) { PrimitiveType primitive; // Do not delete instance in MS ABI. var name = param.Kind == ParameterKind.ImplicitDestructorParameter ? "0" : param.Name; if (param.Type.IsPrimitiveType(out primitive) && primitive != PrimitiveType.Char) { return new ParamMarshal { Name = name, Param = param }; } var argName = "arg" + paramIndex.ToString(CultureInfo.InvariantCulture); var paramMarshal = new ParamMarshal { Name = argName, Param = param }; if (param.IsOut || param.IsInOut) { var paramType = param.Type; Class @class; if ((paramType.GetFinalPointee() ?? paramType).Desugar().TryGetClass(out @class)) { var qualifiedIdentifier = (@class.OriginalClass ?? @class).Visit(TypePrinter); WriteLine("{0} = new {1}();", name, qualifiedIdentifier); } } var ctx = new CSharpMarshalContext(Driver) { Parameter = param, ParameterIndex = paramIndex, ArgName = argName, Function = function }; paramMarshal.Context = ctx; var marshal = new CSharpMarshalManagedToNativePrinter(ctx); param.CSharpMarshalToNative(marshal); paramMarshal.HasFixedBlock = ctx.HasFixedBlock; if (string.IsNullOrEmpty(marshal.Context.Return)) throw new Exception("Cannot marshal argument of function"); if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore)) Write(marshal.Context.SupportBefore); if (paramMarshal.HasFixedBlock) PushIndent(); WriteLine("var {0} = {1};", argName, marshal.Context.Return); return paramMarshal; } static string GetParameterUsage(ParameterUsage usage) { switch (usage) { case ParameterUsage.Out: return "out "; case ParameterUsage.InOut: return "ref "; default: return string.Empty; } } private string FormatMethodParameters(IEnumerable @params) { return string.Join(", ", from param in @params where param.Kind != ParameterKind.IndirectReturnType && !param.Ignore let typeName = param.CSharpType(TypePrinter) select string.Format("{0}{1} {2}", GetParameterUsage(param.Usage), typeName, param.Name + (param.DefaultArgument == null || !Options.GenerateDefaultValuesForArguments ? string.Empty : " = " + ExpressionPrinter.VisitExpression(param.DefaultArgument)))); } #endregion public MemberDeclarationSyntax GenerateTypedef(TypedefDecl typedef) { if (!typedef.IsGenerated) return null; //GenerateDeclarationCommon(typedef); FunctionType functionType; TagType tag; if (typedef.Type.IsPointerToPrimitiveType(PrimitiveType.Void) || typedef.Type.IsPointerTo(out tag)) { return SyntaxFactory.ClassDeclaration(typedef.Name).AddModifiers( SyntaxFactory.Token(SyntaxKind.PublicKeyword)); } if (!typedef.Type.IsPointerTo(out functionType)) { return null; } return GenerateDelegate(typedef, functionType); } private MemberDeclarationSyntax GenerateDelegate(TypedefDecl typedef, FunctionType functionType) { var attributedType = typedef.Type.GetPointee() as AttributedType; var callingConvention = attributedType == null ? functionType.CallingConvention : ((FunctionType) attributedType.Equivalent.Type).CallingConvention; var attributes = new List(2) { SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("SuppressUnmanagedCodeSecurity")) }; var interopCallConv = callingConvention.ToInteropCallConv(); if (interopCallConv != System.Runtime.InteropServices.CallingConvention.Winapi) attributes.Add( SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("UnmanagedFunctionPointerAttribute"), SyntaxFactory.AttributeArgumentList().AddArguments( SyntaxFactory.AttributeArgument( SyntaxFactory.IdentifierName( "global::System.Runtime.InteropServices.CallingConvention." + interopCallConv))))); TypePrinter.PushContext(CSharpTypePrinterContextKind.Native); var @delegate = SyntaxFactory.DelegateDeclaration( SyntaxFactory.ParseTypeName(functionType.ReturnType.Visit(TypePrinter).Type), typedef.Name).AddAttributeLists( SyntaxFactory.AttributeList().AddAttributes(attributes.ToArray())) .AddModifiers( SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)) .AddParameterListParameters( (from parameter in functionType.Parameters select SyntaxFactory.Parameter( SyntaxFactory.Identifier(parameter.Name)).WithType( SyntaxFactory.ParseTypeName(parameter.Type.Visit(TypePrinter).Type))).ToArray()); TypePrinter.PopContext(); return @delegate; } public EnumDeclarationSyntax GenerateEnum(Enumeration @enum) { var @enumDeclaration = SyntaxFactory.EnumDeclaration(@enum.Name).AddModifiers( SyntaxFactory.Token(Helpers.GetAccess(@enum.Access))); if (@enum.IsFlags) { enumDeclaration = enumDeclaration.AddAttributeLists( SyntaxFactory.AttributeList().AddAttributes( SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("Flags")))); } foreach (var item in @enum.Items) { var enumMember = SyntaxFactory.EnumMemberDeclaration(item.Name); if (item.ExplicitValue) { var value = @enum.GetItemValueAsString(item); enumMember = enumMember.WithEqualsValue( SyntaxFactory.EqualsValueClause(SyntaxFactory.IdentifierName(value))); } enumDeclaration = enumDeclaration.AddMembers(enumMember); } return enumDeclaration; //GenerateDeclarationCommon(@enum); } public static string GetMethodIdentifier(Method method) { if (method.IsConstructor || method.IsDestructor) return method.Namespace.Name; return GetFunctionIdentifier(method); } public static string GetFunctionIdentifier(Function function) { if (function.IsOperator) return Operators.GetOperatorIdentifier(function.OperatorKind); return function.Name; } public static string GetFunctionNativeIdentifier(Function function) { var functionName = function.Name; var method = function as Method; if (method != null) { if (method.IsConstructor && !method.IsCopyConstructor) functionName = "ctor"; else if (method.IsCopyConstructor) functionName = "cctor"; else if (method.IsDestructor) functionName = "dtor"; else functionName = GetMethodIdentifier(method); } var identifier = functionName; if (function.IsOperator) identifier = "Operator" + function.OperatorKind; var overloads = function.Namespace.GetOverloads(function) .ToList(); var index = overloads.IndexOf(function); if (index >= 0) identifier += "_" + index.ToString(CultureInfo.InvariantCulture); else if (function.Index.HasValue) identifier += "_" + function.Index.Value; return identifier; } public MemberDeclarationSyntax GenerateInternalFunction(Function function) { if (function.OriginalFunction != null) function = function.OriginalFunction; CSharpTypePrinterResult retType; var @params = GatherInternalParams(function, out retType); string libName = Options.SharedLibraryName; if (Options.CheckSymbols) { NativeLibrary library; Driver.Symbols.FindLibraryBySymbol(function.Mangled, out library); if (library != null) libName = Path.GetFileNameWithoutExtension(library.FileName); } if (Options.StripLibPrefix && libName != null && libName.Length > 3 && libName.StartsWith("lib")) { libName = libName.Substring(3); } if (libName == null) libName = Options.SharedLibraryName; if (Options.GenerateInternalImports) libName = "__Internal"; var callConv = function.CallingConvention.ToInteropCallConv(); return SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName(retType.Type), GetFunctionNativeIdentifier(function)) .AddAttributeLists(SyntaxFactory.AttributeList().AddAttributes( SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("SuppressUnmanagedCodeSecurity")))) .AddAttributeLists(SyntaxFactory.AttributeList().AddAttributes( SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("DllImport")) .AddArgumentListArguments( SyntaxFactory.AttributeArgument(SyntaxFactory.LiteralExpression( SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(libName))), SyntaxFactory.AttributeArgument(SyntaxFactory.IdentifierName( "CallingConvention = global::System.Runtime.InteropServices.CallingConvention." + callConv)), SyntaxFactory.AttributeArgument( SyntaxFactory.IdentifierName(string.Format("EntryPoint=\"{0}\"", function.Mangled)))))) .AddModifiers( SyntaxFactory.Token(SyntaxKind.InternalKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ExternKeyword)) .AddParameterListParameters( (@params.Select(p => SyntaxFactory.Parameter(SyntaxFactory.Identifier(p))).ToArray())) .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); if (function.ReturnType.Type.IsPrimitiveType(PrimitiveType.Bool)) WriteLine("[return: MarshalAsAttribute(UnmanagedType.I1)]"); } private T AddMembers(T parent, params MemberDeclarationSyntax[] members) where T : CSharpSyntaxNode { var compilationUnitSyntax = parent as CompilationUnitSyntax; if (compilationUnitSyntax != null) { return compilationUnitSyntax.AddMembers(members) as T; } var @namespace = parent as NamespaceDeclarationSyntax; if (@namespace != null) { return @namespace.AddMembers(members) as T; } var @class = parent as ClassDeclarationSyntax; if (@class != null) { return @class.AddMembers(members) as T; } var @struct = parent as StructDeclarationSyntax; if (@struct != null) { return @struct.AddMembers(members) as T; } var @interface = parent as InterfaceDeclarationSyntax; if (@interface != null) { return @interface.AddMembers(members) as T; } return parent; } private T AddModifier(T parent, SyntaxKind modifier) where T : CSharpSyntaxNode { var @class = parent as ClassDeclarationSyntax; if (@class != null) return @class.AddModifiers(SyntaxFactory.Token(modifier)) as T; var @struct = parent as StructDeclarationSyntax; if (@struct != null) return @struct.AddModifiers(SyntaxFactory.Token(modifier)) as T; var @interface = parent as InterfaceDeclarationSyntax; if (@interface != null) return @interface.AddModifiers(SyntaxFactory.Token(modifier)) as T; return parent; } private T AddBase(T parent, string @base) where T : CSharpSyntaxNode { var @class = parent as ClassDeclarationSyntax; if (@class != null) return @class.AddBaseListTypes( SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName(@base))) as T; var @struct = parent as StructDeclarationSyntax; if (@struct != null) return @struct.AddBaseListTypes( SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName(@base))) as T; var @interface = parent as InterfaceDeclarationSyntax; if (@interface != null) return @interface.AddBaseListTypes( SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName(@base))) as T; return parent; } private CompilationUnitSyntax compilationUnit; } internal class SymbolNotFoundException : Exception { public SymbolNotFoundException(string msg) : base(msg) {} } }