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

1537 lines
49 KiB

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
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 = new string[]
{
"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 GeneratedIdentifier(string id)
{
return "__" + id;
}
public static string SafeIdentifier(string id)
{
id = new string(((IEnumerable<char>)id)
.Select(c => char.IsLetterOrDigit(c) ? c : '_').ToArray());
return Keywords.Contains(id) ? "@" + id : id;
}
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";
}
}
public class CSharpTextTemplate : TextTemplate
{
public CSharpTypePrinter 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
public string QualifiedIdentifier(Declaration decl)
{
if (Options.GenerateLibraryNamespace)
return string.Format("{0}::{1}", Options.OutputNamespace, decl.QualifiedName);
return string.Format("{0}", decl.QualifiedName);
}
public static string GeneratedIdentifier(string id)
{
return Helpers.GeneratedIdentifier(id);
}
public static string SafeIdentifier(string id)
{
return Helpers.SafeIdentifier(id);
}
#endregion
public override void Generate()
{
GenerateHeader();
WriteLine("using System;");
WriteLine("using System.Runtime.InteropServices;");
WriteLine("using System.Security;");
NewLine();
if (Options.GenerateLibraryNamespace)
{
WriteLine("namespace {0}", SafeIdentifier(Driver.Options.OutputNamespace));
WriteStartBraceIndent();
}
GenerateNamespace(TranslationUnit);
if (Options.GenerateLibraryNamespace)
WriteCloseBraceIndent();
}
public void GenerateHeader()
{
if (Transform != null)
{
Transform.GenerateStart(this);
return;
}
WriteLine("//----------------------------------------------------------------------------");
WriteLine("// This is autogenerated code by CppSharp.");
WriteLine("// Do not edit this file or all your changes will be lost after re-generation.");
WriteLine("//----------------------------------------------------------------------------");
}
private void GenerateNamespace(Namespace @namespace)
{
bool isGlobalNamespace = @namespace is TranslationUnit;
if (!isGlobalNamespace)
{
WriteLine("namespace {0}", @namespace.Name);
WriteStartBraceIndent();
}
// Generate all the enum declarations for the module.
foreach (var @enum in @namespace.Enums)
{
if (@enum.Ignore || @enum.IsIncomplete)
continue;
NewLineIfNeeded();
GenerateEnum(@enum);
NeedNewLine();
}
// Generate all the typedef declarations for the module.
foreach (var typedef in @namespace.Typedefs)
{
if (typedef.Ignore) continue;
NewLineIfNeeded();
if (!GenerateTypedef(typedef))
continue;
NeedNewLine();
}
// Generate all the struct/class declarations for the module.
foreach (var @class in @namespace.Classes)
{
if (@class.Ignore || @class.IsIncomplete)
continue;
if (@class.IsDependent)
continue;
NewLineIfNeeded();
GenerateClass(@class);
NeedNewLine();
}
if (@namespace.HasFunctions)
{
NewLineIfNeeded();
WriteLine("public partial class {0}{1}", SafeIdentifier(Options.LibraryName),
TranslationUnit.FileNameWithoutExtension);
WriteStartBraceIndent();
// Generate all the function declarations for the module.
foreach (var function in @namespace.Functions)
GenerateFunction(function);
WriteCloseBraceIndent();
}
foreach(var childNamespace in @namespace.Namespaces)
GenerateNamespace(childNamespace);
if (!isGlobalNamespace)
WriteCloseBraceIndent();
}
public void GenerateDeclarationCommon(Declaration decl)
{
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("/// <summary>");
WriteLine("/// {0}", comment);
WriteLine("/// </summary>");
}
public void GenerateInlineSummary(string comment)
{
if (String.IsNullOrWhiteSpace(comment))
return;
WriteLine("/// <summary>{0}</summary>", comment);
}
#region Classes
public void GenerateClass(Class @class)
{
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();
if (!@class.IsOpaque)
{
GenerateClassInternals(@class);
if (ShouldGenerateClassNativeField(@class))
{
NewLineIfNeeded();
WriteLine("public System.IntPtr Instance { get; protected set; }");
NeedNewLine();
}
GenerateClassConstructors(@class);
GenerateClassFields(@class);
GenerateClassMethods(@class);
GenerateClassVariables(@class);
GenerateClassProperties(@class);
}
WriteCloseBraceIndent();
}
public void GenerateClassInternals(Class @class)
{
var typePrinter = TypePrinter as CSharpTypePrinter;
typePrinter.PushContext(CSharpTypePrinterContextKind.Native);
WriteLine("[StructLayout(LayoutKind.Explicit, Size = {0})]",
@class.Layout.Size);
Write("internal ");
if (@class.HasBaseClass)
Write("new ");
WriteLine("struct Internal");
WriteStartBraceIndent();
ResetNewLine();
foreach (var field in @class.Fields)
{
NewLineIfNeeded();
if (field.Ignore)
continue;
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();
GenerateFunction(ctor, @class);
}
foreach (var method in @class.Methods)
{
if (CheckIgnoreMethod(@class, method))
continue;
if (method.IsConstructor)
continue;
if (method.IsSynthetized)
continue;
NewLineIfNeeded();
GenerateFunction(method, @class);
}
WriteCloseBraceIndent();
NeedNewLine();
typePrinter.PopContext();
}
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;
bool isRefClass;
var nativeField = GetFieldLocation(field, "native",
CSharpTypePrinterContextKind.ManagedPointer, out isRefClass);
var ctx = new CSharpMarshalContext(Driver)
{
Kind = CSharpMarshalKind.NativeField,
ArgName = field.Name,
ReturnVarName = nativeField,
ReturnType = field.QualifiedType
};
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)
WriteLine("[StructLayout(LayoutKind.Explicit)]");
Write("public unsafe ");
if (Options.GeneratePartialClasses)
Write("partial ");
Write(@class.IsValueType ? "struct " : "class ");
Write("{0}", SafeIdentifier(@class.Name));
var needsBase = @class.HasBaseClass && !@class.IsValueType
&& !@class.Bases[0].Class.IsValueType
&& !@class.Bases[0].Class.Ignore;
if (needsBase || @class.IsRefType)
Write(" : ");
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);
}
}
foreach (var field in @class.Fields)
{
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();
GeneratePropertySetter(field, @class);
WriteCloseBraceIndent();
}
enum PropertyMethodKind
{
Getter,
Setter
}
private string GetFieldLocation(Field field, string instance,
CSharpTypePrinterContextKind kind, out bool isRefClass)
{
isRefClass = false;
var typePrinter = TypePrinter as CSharpTypePrinter;
typePrinter.PushContext(kind);
var type = field.Type.Visit(typePrinter);
typePrinter.PopContext();
var fieldType = field.Type.Desugar();
Class fieldClass;
if (fieldType.IsTagDecl(out fieldClass) && fieldClass.IsRefType)
isRefClass = true;
TagType tagType;
if (fieldType.IsPointerTo(out tagType)
&& tagType.IsTagDecl(out fieldClass) && fieldClass.IsRefType)
isRefClass = true;
if (CSharpTypePrinter.IsConstCharString(field.QualifiedType))
isRefClass = true;
FunctionType functionType;
if (fieldType.IsPointerTo(out functionType))
isRefClass = true;
if (isRefClass)
type = "void*";
var location = string.Format("({0} + {1})", instance,
field.OffsetInBytes);
if (type.TypeMap == null)
location = string.Format("*({0}*){1}", type, location);
return location;
}
private string GetPropertyLocation<T>(T decl, PropertyMethodKind kind,
out bool isRefClass) where T : Declaration, ITypedDecl
{
isRefClass = false;
if (decl is Variable)
{
var @var = decl as Variable;
string symbol;
if (!FindMangledDeclSymbol(@var, out symbol))
return string.Empty;
NativeLibrary library;
Driver.LibrarySymbols.FindLibraryBySymbol(symbol, out library);
return string.Format("CppSharp.SymbolResolver.ResolveSymbol(\"{0}\", \"{1}\")",
Path.GetFileNameWithoutExtension(library.FileName), symbol);
}
else if (decl is Field)
{
var field = decl as Field;
var location = GetFieldLocation(field, "Instance",
CSharpTypePrinterContextKind.Managed, out isRefClass);
if (isRefClass && kind == PropertyMethodKind.Getter)
location = string.Format("new System.IntPtr({0})", location);
return location;
}
throw new NotSupportedException();
}
private void GeneratePropertySetter<T>(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,
};
if (decl is Function)
{
var function = decl as Function;
var parameters = new List<Parameter> { param };
GenerateInternalFunctionCall(function, @class, parameters);
}
else
{
bool isRefClass;
var variable = GetPropertyLocation(decl, PropertyMethodKind.Setter,
out isRefClass);
var marshal = new CSharpMarshalManagedToNativePrinter(ctx);
param.Visit(marshal);
if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore))
Write(marshal.Context.SupportBefore);
Write("{0} = {1}", variable, marshal.Context.Return);
if (isRefClass)
Write(".ToPointer()");
WriteLine(";");
}
WriteCloseBraceIndent();
}
private void GeneratePropertyGetter<T>(T decl, Class @class)
where T : Declaration, ITypedDecl
{
WriteLine("get");
WriteStartBraceIndent();
var @return = string.Empty;
if (decl is Function)
{
var function = decl as Function;
GenerateInternalFunctionCall(function, @class);
@return = "ret";
}
else
{
bool isRefClass;
@return = GetPropertyLocation(decl, PropertyMethodKind.Getter,
out isRefClass);
var ctx = new CSharpMarshalContext(Driver)
{
ArgName = decl.Name,
ReturnVarName = @return,
ReturnType = decl.QualifiedType
};
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)
{
var staticMethods = new List<Method>();
foreach (var method in @class.Methods)
{
if (CheckIgnoreMethod(@class, method))
continue;
if (method.IsConstructor)
continue;
if (method.IsStatic)
{
staticMethods.Add(method);
continue;
}
NewLineIfNeeded();
GenerateMethod(method, @class);
NeedNewLine();
}
foreach (var method in staticMethods)
{
NewLineIfNeeded();
GenerateMethod(method, @class);
NeedNewLine();
}
}
public void GenerateClassVariables(Class @class)
{
foreach (var variable in @class.Variables)
{
if (variable.Ignore) continue;
if (variable.Access != AccessSpecifier.Public)
continue;
var type = variable.Type;
NewLineIfNeeded();
GenerateVariable(@class, type, variable);
NeedNewLine();
}
}
private void GenerateClassProperties(Class @class)
{
foreach (var prop in @class.Properties)
{
if (prop.Ignore) continue;
NewLineIfNeeded();
WriteLine("public {0} {1}", prop.Type, prop.Name);
WriteStartBraceIndent();
GeneratePropertyGetter(prop.GetMethod, @class);
NeedNewLine();
if (prop.SetMethod != null)
{
NewLineIfNeeded();
GeneratePropertySetter(prop.SetMethod, @class);
}
WriteCloseBraceIndent();
NeedNewLine();
}
}
private void GenerateVariable(Class @class, Type type, Variable variable)
{
WriteLine("public static {0} {1}", type, variable.Name);
WriteStartBraceIndent();
GeneratePropertyGetter(variable, @class);
if (!variable.QualifiedType.Qualifiers.IsConst)
GeneratePropertySetter(variable, @class);
WriteCloseBraceIndent();
}
#endregion
#region Constructors
public void GenerateClassConstructors(Class @class)
{
NewLineIfNeeded();
// Output a default constructor that takes the native pointer.
GenerateNativeConstructor(@class);
NeedNewLine();
foreach (var ctor in @class.Constructors)
{
if (CheckIgnoreMethod(@class, ctor))
continue;
NewLineIfNeeded();
GenerateMethod(ctor, @class);
NeedNewLine();
}
if (@class.IsRefType)
GenerateDisposeMethods(@class);
}
private void GenerateDisposeMethods(Class @class)
{
var hasBaseClass = @class.HasBaseClass && @class.BaseClass.IsRefType;
// Generate the IDispose Dispose() method.
if (!hasBaseClass)
{
NewLineIfNeeded();
WriteLine("public void Dispose()");
WriteStartBraceIndent();
WriteLine("Dispose(disposing: true);");
WriteLine("GC.SuppressFinalize(this);");
WriteCloseBraceIndent();
NeedNewLine();
}
// Generate Dispose(bool) method
NewLineIfNeeded();
Write("protected ");
Write(hasBaseClass ? "override " : "virtual ");
WriteLine("void Dispose(bool disposing)");
WriteStartBraceIndent();
if (ShouldGenerateClassNativeField(@class))
WriteLine("Marshal.FreeHGlobal(Instance);");
if (hasBaseClass)
WriteLine("base.Dispose(disposing);");
WriteCloseBraceIndent();
NeedNewLine();
}
private void GenerateNativeConstructor(Class @class)
{
WriteLine("internal {0}(System.IntPtr native)", SafeIdentifier(@class.Name));
var hasBaseClass = @class.HasBaseClass && @class.BaseClass.IsRefType;
if (hasBaseClass)
WriteLineIndent(": base(native)");
WriteStartBraceIndent();
if (@class.IsRefType)
{
if (ShouldGenerateClassNativeField(@class))
WriteLine("Instance = native;");
}
else
{
GenerateStructMarshaling(@class);
}
WriteCloseBraceIndent();
}
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.Operator)
Write("static ");
var functionName = GetFunctionIdentifier(method, @class);
if (method.IsConstructor || method.IsDestructor)
Write("{0}(", functionName);
else
Write("{0} {1}(", method.ReturnType, functionName);
GenerateMethodParameters(method, @class);
WriteLine(")");
if (method.Kind == CXXMethodKind.Constructor)
GenerateClassConstructorBase(@class, method);
WriteStartBraceIndent();
if (@class.IsRefType)
{
if (method.IsConstructor)
{
GenerateClassConstructor(method, @class);
}
else if (method.IsOperator)
{
GeneratedOperator(method, @class);
}
else
{
GenerateInternalFunctionCall(method, @class);
}
}
else if (@class.IsValueType)
{
if (method.IsConstructor)
{
GenerateInternalFunctionCall(method, @class);
}
else if (method.IsOperator)
{
GeneratedOperator(method, @class);
}
else
{
GenerateInternalFunctionCall(method, @class);
}
}
WriteCloseBraceIndent();
}
private static string GetOperatorOverloadPair(CXXOperatorKind kind)
{
switch (kind)
{
case CXXOperatorKind.EqualEqual:
return "!=";
case CXXOperatorKind.ExclaimEqual:
return "==";
case CXXOperatorKind.Less:
return ">";
case CXXOperatorKind.Greater:
return "<";
case CXXOperatorKind.LessEqual:
return ">=";
case CXXOperatorKind.GreaterEqual:
return "<=";
default:
throw new NotSupportedException();
}
}
private void GeneratedOperator(Method method, Class @class)
{
if (method.IsSynthetized)
{
var @operator = GetOperatorOverloadPair(method.OperatorKind);
WriteLine("return !({0} {1} {2});", method.Parameters[0].Name,
@operator, method.Parameters[1].Name);
return;
}
GenerateInternalFunctionCall(method, @class);
}
private void GenerateClassConstructor(Method method, Class @class)
{
var @params = GenerateFunctionParamsMarshal(method.Parameters, method);
WriteLine("Instance = Marshal.AllocHGlobal({0});", @class.Layout.Size);
Write("Internal.{0}(Instance", GetFunctionNativeIdentifier(method, @class));
if (@params.Any())
Write(", ");
GenerateFunctionParams(@params);
WriteLine(");");
}
private void GenerateValueTypeConstructorCall(Method method, Class @class)
{
var names = new List<string>();
foreach (var param in method.Parameters)
{
var ctx = new CSharpMarshalContext(Driver)
{
Parameter = param,
ArgName = param.Name,
};
var marshal = new CSharpMarshalManagedToNativePrinter(ctx);
param.Visit(marshal);
if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore))
Write(marshal.Context.SupportBefore);
names.Add(marshal.Context.Return);
}
WriteLine("var {0} = new {1}.Internal();", GeneratedIdentifier("instance"),
@class.QualifiedName);
GenerateInternalFunctionCall(method, @class);
GenerateValueTypeConstructorCallFields(@class);
}
private void GenerateValueTypeConstructorCallFields(Class @class)
{
foreach (var @base in @class.Bases)
{
if (!@base.IsClass || @base.Class.Ignore)
continue;
var baseClass = @base.Class;
GenerateValueTypeConstructorCallFields(baseClass);
}
foreach (var field in @class.Fields)
{
if (CheckIgnoreField(@class, field)) continue;
var nativeField = string.Format("*({0}*) (&{1} + {2})",
field.Type, GeneratedIdentifier("instance"), field.OffsetInBytes);
var ctx = new CSharpMarshalContext(Driver)
{
ReturnVarName = nativeField,
ReturnType = field.QualifiedType
};
var marshal = new CSharpMarshalNativeToManagedPrinter(ctx);
field.Visit(marshal);
if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore))
Write(marshal.Context.SupportBefore);
WriteLine("this.{0} = {1};", field.Name, marshal.Context.Return);
}
}
public void GenerateInternalFunctionCall(Function function, Class @class,
List<Parameter> parameters = null)
{
if (parameters == null)
parameters = function.Parameters;
var functionName = string.Format("Internal.{0}",
GetFunctionNativeIdentifier(function, @class));
GenerateFunctionCall(functionName, parameters, function, @class);
}
public void GenerateFunctionCall(string functionName, List<Parameter> parameters,
Function function, Class @class = null)
{
var retType = function.ReturnType;
var needsReturn = !retType.Type.IsPrimitiveType(PrimitiveType.Void);
var method = function as Method;
var needsInstance = method != null && !method.IsStatic && !method.IsOperator;
var isValueType = @class != null && @class.IsValueType;
var needsFixedThis = needsInstance && isValueType;
Class retClass = null;
if (function.HasHiddenStructParameter)
{
function.ReturnType.Type.IsTagDecl(out retClass);
WriteLine("var {0} = new {1}.Internal();", GeneratedIdentifier("udt"),
retClass.OriginalName);
retType.Type = new BuiltinType(PrimitiveType.Void);
needsReturn = false;
}
var @params = GenerateFunctionParamsMarshal(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);
}
if (needsInstance)
{
names.Insert(0, needsFixedThis ? string.Format("new System.IntPtr({0})",
GeneratedIdentifier("instance")) : "Instance");
}
if (needsFixedThis)
{
WriteLine("fixed({0}* {1} = &this)", @class.QualifiedName,
GeneratedIdentifier("instance"));
WriteStartBraceIndent();
}
if (needsReturn)
Write("var ret = ");
WriteLine("{0}({1});", functionName, string.Join(", ", names));
var cleanups = new List<TextGenerator>();
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.Type.Visit(marshal, function.ReturnType.Qualifiers);
if (!string.IsNullOrWhiteSpace(marshal.Context.SupportBefore))
Write(marshal.Context.SupportBefore);
WriteLine("return {0};", marshal.Context.Return);
}
if (function.HasHiddenStructParameter)
{
WriteLine("var ret = new {0}();", retClass.Name);
if (retClass.IsValueType)
WriteLine("*({0}.Internal*) &ret = {1};", retClass.Name,
GeneratedIdentifier("udt"));
else
WriteLine("*({0}.Internal*) ret.Instance.ToPointer() = {1};",
retClass.Name, GeneratedIdentifier("udt"));
WriteLine("return ret;");
}
if (needsFixedThis)
WriteCloseBraceIndent();
}
private void GenerateFunctionCallOutParams(IEnumerable<ParamMarshal> @params,
ICollection<TextGenerator> 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.QualifiedType
};
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 GenerateFunctionParams(List<ParamMarshal> @params)
{
var names = @params.Select(param => param.Name).ToList();
Write(string.Join(", ", names));
}
public List<ParamMarshal> GenerateFunctionParamsMarshal(IEnumerable<Parameter> @params,
Function function = null)
{
var marshals = new List<ParamMarshal>();
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};", SafeIdentifier(argName), marshal.Context.Return);
}
return paramMarshal;
}
private void GenerateMethodParameters(Method method, Class @class)
{
var @params = new List<string>();
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
public bool GenerateTypedef(TypedefDecl typedef)
{
if (typedef.Ignore)
return false;
GenerateDeclarationCommon(typedef);
FunctionType function;
TagType tag;
if (typedef.Type.IsPointerToPrimitiveType(PrimitiveType.Void)
|| typedef.Type.IsPointerTo<TagType>(out tag))
{
WriteLine("public class " + SafeIdentifier(typedef.Name) + @" { }");
}
else if (typedef.Type.IsPointerTo<FunctionType>(out function))
{
WriteLine("public {0};",
string.Format(TypePrinter.VisitDelegate(function).Type,
SafeIdentifier(typedef.Name)));
NeedNewLine();
}
else if (typedef.Type.IsEnumType())
{
// Already handled in the parser.
return false;
}
else
{
Console.WriteLine("Unhandled typedef type: {0}", typedef);
return false;
}
return true;
}
public void GenerateEnum(Enumeration @enum)
{
if (@enum.Ignore) return;
GenerateDeclarationCommon(@enum);
if (@enum.IsFlags)
WriteLine("[Flags]");
Write("public enum {0}", SafeIdentifier(@enum.Name));
var typeName = TypePrinter.VisitPrimitiveType(@enum.BuiltinType.Type,
new TypeQualifiers());
if (@enum.BuiltinType.Type != PrimitiveType.Int32)
Write(" : {0}", typeName);
NewLine();
WriteStartBraceIndent();
for (var i = 0; i < @enum.Items.Count; ++i)
{
var item = @enum.Items[i];
GenerateInlineSummary(item.Comment);
Write(item.ExplicitValue
? string.Format("{0} = {1}", SafeIdentifier(item.Name), item.Value)
: string.Format("{0}", SafeIdentifier(item.Name)));
if (i < @enum.Items.Count - 1)
Write(",");
NewLine();
}
WriteCloseBraceIndent();
}
public string GetOperatorIdentifier(CXXOperatorKind kind)
{
// These follow the order described in MSDN (Overloadable Operators).
switch (kind)
{
// These unary operators can be overloaded
case CXXOperatorKind.Plus: return "operator +";
case CXXOperatorKind.Minus: return "operator -";
case CXXOperatorKind.Exclaim: return "operator !";
case CXXOperatorKind.Tilde: return "operator ~";
case CXXOperatorKind.PlusPlus: return "operator ++";
case CXXOperatorKind.MinusMinus: return "operator --";
// These binary operators can be overloaded
case CXXOperatorKind.Star: return "operator *";
case CXXOperatorKind.Slash: return "operator /";
case CXXOperatorKind.Percent: return "operator +";
case CXXOperatorKind.Amp: return "operator &";
case CXXOperatorKind.Pipe: return "operator |";
case CXXOperatorKind.Caret: return "operator ^";
case CXXOperatorKind.LessLess: return "operator <<";
case CXXOperatorKind.GreaterGreater: return "operator >>";
// The comparison operators can be overloaded
case CXXOperatorKind.EqualEqual: return "operator ==";
case CXXOperatorKind.ExclaimEqual: return "operator !=";
case CXXOperatorKind.Less: return "operator <";
case CXXOperatorKind.Greater: return "operator >";
case CXXOperatorKind.LessEqual: return "operator <=";
case CXXOperatorKind.GreaterEqual: return "operator >=";
// Assignment operators cannot be overloaded
case CXXOperatorKind.PlusEqual:
case CXXOperatorKind.MinusEqual:
case CXXOperatorKind.StarEqual:
case CXXOperatorKind.SlashEqual:
case CXXOperatorKind.PercentEqual:
case CXXOperatorKind.AmpEqual:
case CXXOperatorKind.PipeEqual:
case CXXOperatorKind.CaretEqual:
case CXXOperatorKind.LessLessEqual:
case CXXOperatorKind.GreaterGreaterEqual:
// The array indexing operator cannot be overloaded
case CXXOperatorKind.Subscript:
// The conditional logical operators cannot be overloaded
case CXXOperatorKind.AmpAmp:
case CXXOperatorKind.PipePipe:
// These operators cannot be overloaded.
case CXXOperatorKind.Equal:
case CXXOperatorKind.Comma:
case CXXOperatorKind.ArrowStar:
case CXXOperatorKind.Arrow:
case CXXOperatorKind.Call:
case CXXOperatorKind.Conditional:
case CXXOperatorKind.New:
case CXXOperatorKind.Delete:
case CXXOperatorKind.Array_New:
case CXXOperatorKind.Array_Delete:
default: throw new NotImplementedException();
}
}
public string GetFunctionIdentifier(Function function, Class @class)
{
var identifier = SafeIdentifier(function.Name);
var printer = TypePrinter as CSharpTypePrinter;
var isNativeContext = printer.ContextKind == CSharpTypePrinterContextKind.Native;
var method = function as Method;
if (method != null && method.Kind == CXXMethodKind.Operator)
{
if (isNativeContext)
identifier = "Operator" + method.OperatorKind.ToString();
else
identifier = GetOperatorIdentifier(method.OperatorKind);
}
var overloads = AST.Utils.GetFunctionOverloads(function, @class);
var index = overloads.IndexOf(function);
if (isNativeContext && index >= 0)
identifier += index.ToString();
return identifier;
}
public string GetFunctionNativeIdentifier(Function function, Class @class = null)
{
var typePrinter = TypePrinter as CSharpTypePrinter;
typePrinter.PushContext(CSharpTypePrinterContextKind.Native);
var name = GetFunctionIdentifier(function, @class);
typePrinter.PopContext();
return name;
}
bool FindMangledDeclLibrary(IMangledDecl decl, out NativeLibrary library)
{
string symbol;
if (!FindMangledDeclSymbol(decl, out symbol))
{
library = null;
return false;
}
Driver.LibrarySymbols.FindLibraryBySymbol(symbol, out library);
return true;
}
bool FindMangledDeclSymbol(IMangledDecl decl, out string symbol)
{
symbol = decl.Mangled;
if (!Driver.LibrarySymbols.FindSymbol(ref symbol))
{
Driver.Diagnostics.EmitError(DiagnosticId.SymbolNotFound,
"Symbol not found: {0}", symbol);
symbol = null;
return false;
}
return true;
}
public void GenerateFunction(Function function, Class @class = null)
{
if(function.Ignore) return;
NativeLibrary library;
if (!FindMangledDeclLibrary(function, out library))
return;
GenerateDeclarationCommon(function);
WriteLine("[SuppressUnmanagedCodeSecurity]");
Write("[DllImport(\"{0}\", ", library.FileName);
WriteLine("CallingConvention = CallingConvention.{0},",
Helpers.ToCSharpCallConv(function.CallingConvention));
WriteLineIndent("EntryPoint=\"{0}\")]", function.Mangled);
if (function.ReturnType.Type.Desugar().IsPrimitiveType(PrimitiveType.Bool))
WriteLine("[return: MarshalAsAttribute(UnmanagedType.I1)]");
var @params = new List<string>();
var typePrinter = TypePrinter as CSharpTypePrinter;
var retType = typePrinter.VisitParameterDecl(new Parameter()
{
QualifiedType = function.ReturnType
});
var method = function as Method;
if (method != null && !method.IsStatic)
{
@params.Add("System.IntPtr instance");
if (method.IsConstructor && Options.IsMicrosoftAbi)
retType = "System.IntPtr";
}
for(var i = 0; i < function.Parameters.Count; ++i)
{
var param = function.Parameters[i];
if (param.Kind == ParameterKind.OperatorParameter)
continue;
var typeName = param.Visit(typePrinter);
var paramName = param.IsSynthetized ?
GeneratedIdentifier(param.Name) : SafeIdentifier(param.Name);
@params.Add(string.Format("{0} {1}", typeName, paramName));
}
if (method != null && method.IsConstructor)
if (Options.IsMicrosoftAbi && @class.Layout.HasVirtualBases)
@params.Add("int " + GeneratedIdentifier("forBases"));
WriteLine("public unsafe static extern {0} {1}({2});", retType,
GetFunctionIdentifier(function, @class),
string.Join(", ", @params));
}
}
internal class SymbolNotFoundException : Exception
{
public SymbolNotFoundException(string msg) : base(msg)
{}
}
}