mirror of https://github.com/mono/CppSharp.git
c-sharpdotnetmonobindingsbridgecclangcpluspluscppsharpglueinteropparserparsingpinvokeswigsyntax-treevisitorsxamarinxamarin-bindings
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.
618 lines
17 KiB
618 lines
17 KiB
// |
|
// Generator.cs: C++ Interop Code Generator |
|
// |
|
// |
|
|
|
using System; |
|
using System.IO; |
|
using System.Diagnostics; |
|
using System.Collections.Generic; |
|
using System.Xml; |
|
using System.Xml.Linq; |
|
using System.Linq; |
|
using System.Reflection; |
|
|
|
using Templates; |
|
using NDesk.Options; |
|
using Mono.Cxxi; |
|
|
|
public class Generator { |
|
|
|
// Command line arguments |
|
public string InputFileName { get; set; } |
|
public string OutputDir { get; set; } |
|
public string FilterFile { get; set; } |
|
|
|
// In the future we might support more than one of these at once... |
|
public Lib Lib { get; set; } |
|
|
|
// Classes to generate code for |
|
public Dictionary<Node, Namespace> NodeToNamespace { get; set; } |
|
|
|
private FilterMode default_filter_mode; |
|
public FilterMode DefaultFilterMode { get { return default_filter_mode; } set { default_filter_mode = value; } } |
|
public Dictionary<string, Filter> Filters { get; set; } |
|
|
|
// Code templates |
|
public LibsBase LibsTemplate { get; set; } |
|
public ClassBase ClassTemplate { get; set; } |
|
public EnumBase EnumTemplate { get; set; } |
|
|
|
public static int Main (String[] args) { |
|
var generator = new Generator (); |
|
generator.Run (args); |
|
return 0; |
|
} |
|
|
|
void Run (String[] args) { |
|
Lib = new Lib (); |
|
NodeToNamespace = new Dictionary<Node, Namespace> (); |
|
|
|
if (ParseArguments (args) != 0) { |
|
Environment.Exit (1); |
|
} |
|
|
|
Node root = LoadXml (InputFileName); |
|
|
|
if (FilterFile != null) |
|
Filters = Filter.Load (XDocument.Load (FilterFile), out default_filter_mode); |
|
|
|
CreateTypes (root); |
|
|
|
CreateMembers (); |
|
|
|
GenerateCode (); |
|
} |
|
|
|
int ParseArguments (String[] args) { |
|
bool help = false; |
|
|
|
var p = new OptionSet { |
|
{ "h|?|help", "Show this help message", v => help = v != null }, |
|
{ "o=|out=", "Set the output directory", v => OutputDir = v }, |
|
{ "ns=|namespace=", "Set the namespace of the generated code", v => Lib.BaseNamespace = v }, |
|
{ "lib=", "The base name of the C++ library, i.e. 'qt' for libqt.so", v =>Lib.BaseName = v }, |
|
{ "filters=", "A file containing filter directives for filtering classes", v => FilterFile = v }, |
|
{ "inline=", "Inline methods in lib are: notpresent (default), present, surrogatelib (present in %lib%-inline)", v => Lib.InlinePolicy = (InlineMethods)Enum.Parse (typeof (InlineMethods), v, true) } |
|
}; |
|
|
|
try { |
|
args = p.Parse (args).ToArray (); |
|
} catch (OptionException) { |
|
Console.WriteLine ("Try `generator --help' for more information."); |
|
return 1; |
|
} |
|
|
|
if (help) { |
|
p.WriteOptionDescriptions (Console.Error); |
|
return 1; |
|
} |
|
|
|
if (args.Length != 1) { |
|
Console.WriteLine ("Usage: generator <options> <input xml file>"); |
|
return 1; |
|
} |
|
|
|
// Code templates |
|
LibsTemplate = new CSharpLibs (); |
|
ClassTemplate = new CSharpClass (); |
|
EnumTemplate = new CSharpEnum (); |
|
|
|
InputFileName = args [0]; |
|
|
|
if (Lib.BaseName == null) { |
|
Console.WriteLine ("The --lib= option is required."); |
|
return 1; |
|
} |
|
|
|
if (Lib.BaseNamespace == null) { |
|
Lib.BaseNamespace = Path.GetFileNameWithoutExtension (Lib.BaseName); |
|
} |
|
|
|
if (OutputDir == null) |
|
OutputDir = "output"; |
|
|
|
return 0; |
|
} |
|
|
|
Node LoadXml (string file) { |
|
XmlReader reader = XmlReader.Create (file, new XmlReaderSettings ()); |
|
|
|
Node[] parents = new Node [1024]; |
|
|
|
Node root = null; |
|
|
|
while (reader.Read()) { |
|
if (reader.IsStartElement ()) { |
|
string type = reader.Name; |
|
|
|
var attributes = new Dictionary<string, string> (); |
|
while (reader.MoveToNextAttribute ()) { |
|
attributes [reader.Name] = reader.Value; |
|
} |
|
|
|
Node n = new Node { |
|
Id = "", |
|
Type = type, |
|
Attributes = attributes, |
|
Children = new List<Node> () |
|
}; |
|
|
|
if (attributes.ContainsKey ("id")) { |
|
n.Id = attributes ["id"]; |
|
Node.IdToNode [n.Id] = n; |
|
} |
|
|
|
if (attributes.ContainsKey ("name")) |
|
n.Name = attributes ["name"]; |
|
|
|
if (parents [reader.Depth - 1] != null) { |
|
//Console.WriteLine (parents [reader.Depth - 1].type + " -> " + e.type); |
|
parents [reader.Depth - 1].Children.Add (n); |
|
} |
|
parents [reader.Depth] = n; |
|
|
|
if (n.Type == "GCC_XML" && root == null) |
|
root = n; |
|
} |
|
} |
|
|
|
return root; |
|
} |
|
|
|
void CreateTypes (Node root) { |
|
|
|
foreach (Node n in root.Children) { |
|
if (n.IsTrue ("incomplete") || !n.HasValue ("name") || n.Attributes ["name"] == "::") |
|
continue; |
|
|
|
Namespace ns; |
|
switch (n.Type) { |
|
|
|
case "Class": |
|
case "Struct": |
|
ns = new Class (n); |
|
break; |
|
|
|
case "Enumeration": |
|
ns = new Enumeration (n); |
|
break; |
|
|
|
case "Namespace": |
|
ns = new Namespace (n); |
|
break; |
|
|
|
default: |
|
continue; |
|
|
|
} |
|
|
|
NodeToNamespace [n] = ns; |
|
Lib.Namespaces.Add (ns); |
|
} |
|
|
|
for (var i = 0; i < Lib.Namespaces.Count; i++) { |
|
Namespace ns = Lib.Namespaces [i]; |
|
SetParentNamespace (ns); |
|
|
|
var filter = GetFilterOrDefault (ns); |
|
if (filter.Mode == FilterMode.Exclude) |
|
NodeToNamespace.Remove (ns.Node); |
|
|
|
if (filter.Mode != FilterMode.Include) { |
|
Lib.Namespaces.RemoveAt (i); |
|
i--; |
|
continue; |
|
} |
|
|
|
var klass = ns as Class; |
|
if (klass == null) |
|
continue; |
|
|
|
// Compute bases |
|
foreach (Node bn in klass.Node.Children.Where (o => o.Type == "Base")) { |
|
Class baseClass = NodeToNamespace [bn.NodeForAttr ("type")] as Class; |
|
Debug.Assert (baseClass != null); |
|
klass.BaseClasses.Add (baseClass); |
|
} |
|
} |
|
} |
|
|
|
void SetParentNamespace (Namespace ns) |
|
{ |
|
Namespace parent = null; |
|
if (ns.Node.HasValue ("context") && NodeToNamespace.TryGetValue (Node.IdToNode [ns.Node.Attributes ["context"]], out parent)) |
|
{ |
|
SetParentNamespace (parent); |
|
ns.ParentNamespace = parent; |
|
} |
|
} |
|
|
|
void CreateMembers () { |
|
|
|
foreach (var ns in Lib.Namespaces) { |
|
|
|
var parentClass = ns.ParentNamespace as Class; |
|
|
|
var @enum = ns as Enumeration; |
|
if (@enum != null) { |
|
if (parentClass != null) |
|
parentClass.NestedEnums.Add (@enum); |
|
|
|
foreach (var enumValue in @enum.Node.Children.Where (o => o.Type == "EnumValue")) { |
|
int val; |
|
var item = new Enumeration.Item { Name = enumValue.Attributes ["name"] }; |
|
|
|
if (enumValue.HasValue ("init") && int.TryParse (enumValue.Attributes ["init"], out val)) |
|
item.Value = val; |
|
|
|
@enum.Items.Add (item); |
|
} |
|
|
|
continue; |
|
} |
|
|
|
var klass = ns as Class; |
|
if (klass == null || !klass.Node.HasValue ("members")) |
|
continue; |
|
|
|
if (parentClass != null) |
|
parentClass.NestedClasses.Add (klass); |
|
|
|
int fieldCount = 0; |
|
foreach (Node n in klass.Node ["members"].Split (new[] {' '}, StringSplitOptions.RemoveEmptyEntries).Select (id => Node.IdToNode [id])) { |
|
bool ctor = false; |
|
bool dtor = false; |
|
bool skip = false; |
|
|
|
switch (n.Type) { |
|
case "Field": |
|
var fieldType = GetType (GetTypeNode (n)); |
|
if (fieldType.ElementType == CppTypes.Unknown && fieldType.ElementTypeName == null) |
|
fieldType = new CppType (CppTypes.Void, CppModifiers.Pointer); |
|
|
|
string fieldName; |
|
if (n.Name != "") |
|
fieldName = n.Name; |
|
else |
|
fieldName = "field" + fieldCount++; |
|
|
|
klass.Fields.Add (new Field (fieldName, fieldType, (Access)Enum.Parse (typeof (Access), n ["access"]))); |
|
break; |
|
|
|
case "Constructor": |
|
ctor = true; |
|
break; |
|
case "Destructor": |
|
dtor = true; |
|
break; |
|
case "Method": |
|
break; |
|
default: |
|
continue; |
|
} |
|
|
|
if (n.Name == "timerEvent") |
|
Console.WriteLine ("foo"); |
|
|
|
if ((!dtor && n.HasValue ("overrides") && CheckPrimaryBases (klass, b => b.Node.CheckValueList ("members", n.Attributes ["overrides"]))) || // excl. virtual methods from primary base (except dtor) |
|
(!n.IsTrue ("extern") && !n.IsTrue ("inline"))) |
|
continue; |
|
|
|
if (n.IsTrue ("inline") && Lib.InlinePolicy == InlineMethods.NotPresent) |
|
skip = true; |
|
|
|
string name = dtor ? "Destruct" : n.Name; |
|
|
|
var method = new Method (n) { |
|
Name = name, |
|
Access = (Access)Enum.Parse (typeof (Access), n.Attributes ["access"]), |
|
IsVirtual = n.IsTrue ("virtual"), |
|
IsStatic = n.IsTrue ("static"), |
|
IsConst = n.IsTrue ("const"), |
|
IsInline = n.IsTrue ("inline"), |
|
IsArtificial = n.IsTrue ("artificial"), |
|
IsConstructor = ctor, |
|
IsDestructor = dtor |
|
}; |
|
|
|
if (method.Access == Access.@private) |
|
skip = true; |
|
|
|
if (dtor || method.IsArtificial) |
|
method.GenWrapperMethod = false; |
|
|
|
CppType retType; |
|
if (n.HasValue ("returns")) |
|
retType = GetType (n.NodeForAttr ("returns")); |
|
else |
|
retType = CppTypes.Void; |
|
if (retType.ElementType == CppTypes.Unknown) { |
|
retType = CppTypes.Void; |
|
skip = true; |
|
} |
|
if (CppTypeToManaged (retType) == null) { |
|
//Console.WriteLine ("\t\tS: " + retType); |
|
retType = CppTypes.Void; |
|
skip = true; |
|
} |
|
|
|
method.ReturnType = retType; |
|
|
|
int c = 0; |
|
var argTypes = new List<CppType> (); |
|
foreach (Node arg in n.Children.Where (o => o.Type == "Argument")) { |
|
string argname; |
|
if (arg.Name == null || arg.Name == "") |
|
argname = "arg" + c; |
|
else |
|
argname = arg.Name; |
|
|
|
var argtype = GetType (GetTypeNode (arg)); |
|
if (argtype.ElementType == CppTypes.Unknown) { |
|
//Console.WriteLine ("Skipping method " + klass.Name + "::" + member.Name + " () because it has an argument with unknown type '" + TypeNodeToString (arg) + "'."); |
|
argtype = new CppType (CppTypes.Void, CppModifiers.Pointer); |
|
skip = true; |
|
} |
|
|
|
if (CppTypeToManaged (argtype) == null) { |
|
//Console.WriteLine ("\t\tS: " + argtype); |
|
argtype = new CppType (CppTypes.Void, CppModifiers.Pointer); |
|
skip = true; |
|
} |
|
|
|
method.Parameters.Add (new Parameter (argname, argtype)); |
|
argTypes.Add (argtype); |
|
|
|
c++; |
|
} |
|
if (skip && !method.IsVirtual) |
|
continue; |
|
else if (skip && method.IsVirtual) |
|
method.GenWrapperMethod = false; |
|
|
|
// FIXME: More complete type name check |
|
if (ctor && argTypes.Count == 1 && argTypes [0].ElementType == CppTypes.Class && argTypes [0].ElementTypeName == klass.Name && argTypes [0].Modifiers.Count == 2 && argTypes [0].Modifiers.Contains (CppModifiers.Const) && argTypes [0].Modifiers.Contains (CppModifiers.Reference)) |
|
method.IsCopyCtor = true; |
|
|
|
Console.WriteLine ("\t" + klass.Name + "." + method.Name); |
|
|
|
klass.Methods.Add (method); |
|
} |
|
|
|
foreach (var method in klass.Methods) { |
|
if (AddAsProperty (klass, method)) |
|
method.GenWrapperMethod = false; |
|
} |
|
|
|
Field f2 = klass.Fields.FirstOrDefault (f => f.Type.ElementType == CppTypes.Unknown); |
|
if (f2 != null) { |
|
Console.WriteLine ("Skipping " + klass.Name + " because field " + f2.Name + " has unknown type."); |
|
klass.Disable = true; |
|
} |
|
} |
|
} |
|
|
|
// |
|
// Property support |
|
// |
|
bool AddAsProperty (Class klass, Method method) { |
|
// if it's const, returns a value, has no parameters, and there is no other method with the same name |
|
// in this class assume it's a property getter (for now?) |
|
if (method.IsConst && !method.ReturnType.Equals (CppTypes.Void) && !method.Parameters.Any () && |
|
klass.Methods.Count (o => o.Name == method.Name) == 1) { |
|
Property property; |
|
|
|
property = klass.Properties.Where (o => o.Name == method.FormattedName).FirstOrDefault (); |
|
if (property != null) { |
|
property.GetMethod = method; |
|
} else { |
|
property = new Property (method.FormattedName, method.ReturnType) { GetMethod = method }; |
|
klass.Properties.Add (property); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
// if it's name starts with "set", does not return a value, and has one arg (besides this ptr) |
|
// and there is no other method with the same name... |
|
if (method.Name.ToLower ().StartsWith ("set") && method.ReturnType.Equals (CppTypes.Void) && |
|
method.Parameters.Count == 1 && klass.Methods.Count (o => o.Name == method.Name) == 1) { |
|
string getterName = method.Name.Substring (3).TrimStart ('_').ToLower (); |
|
|
|
string pname = method.FormattedName.Substring (3); |
|
Property property = null; |
|
|
|
// ...AND there is a corresponding getter method that returns the right type, then assume it's a property setter |
|
bool doIt = false; |
|
property = klass.Properties.Where (o => o.Name == pname).FirstOrDefault (); |
|
if (property != null) { |
|
doIt = property.GetMethod != null && property.GetMethod.ReturnType.Equals (method.Parameters[0].Type); |
|
} else { |
|
Method getter = klass.Methods.Where (o => o.Name == getterName).FirstOrDefault (); |
|
doIt = getter != null && getter.ReturnType.Equals (method.Parameters[0].Type); |
|
} |
|
if (doIt) { |
|
if (property != null) { |
|
property.SetMethod = method; |
|
} else { |
|
property = new Property (pname, method.Parameters [0].Type) { SetMethod = method }; |
|
klass.Properties.Add (property); |
|
} |
|
|
|
// set the method's arg name to "value" so that the prop setter works right |
|
var valueParam = method.Parameters[0]; |
|
valueParam.Name = "value"; |
|
|
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
// Checks klass's primary base, primary base's primary base, and so on up the hierarchy |
|
public bool CheckPrimaryBases (Class klass, Func<Class, bool> predicate) |
|
{ |
|
if (klass.BaseClasses.Count == 0) |
|
return false; |
|
var primaryBase = klass.BaseClasses [0]; |
|
return predicate (primaryBase) || CheckPrimaryBases (primaryBase, predicate); |
|
} |
|
|
|
// Return a CppType for the type node N, return CppTypes.Unknown for unknown types |
|
CppType GetType (Node n) { |
|
return GetType (n, new CppType ()); |
|
} |
|
|
|
CppType GetType (Node n, CppType modifiers) { |
|
var fundamental = CppTypes.Unknown; |
|
|
|
switch (n.Type) { |
|
case "Typedef": |
|
return GetType (GetTypeNode (n), modifiers); |
|
case "ArrayType": |
|
return GetType (GetTypeNode (n), modifiers.Modify (CppModifiers.Array)); |
|
case "PointerType": |
|
return GetType (GetTypeNode (n), modifiers.Modify (CppModifiers.Pointer)); |
|
case "ReferenceType": |
|
return GetType (GetTypeNode (n), modifiers.Modify (CppModifiers.Reference)); |
|
case "FundamentalType": |
|
return modifiers.CopyTypeFrom (new CppType (n.Name)); |
|
case "CvQualifiedType": |
|
if (n.IsTrue ("const")) |
|
return GetType (GetTypeNode (n), modifiers.Modify (CppModifiers.Const)); |
|
else |
|
throw new NotImplementedException (); |
|
case "Class": |
|
fundamental = CppTypes.Class; |
|
break; |
|
case "Struct": |
|
fundamental = CppTypes.Struct; |
|
break; |
|
case "Enumeration": |
|
fundamental = CppTypes.Enum; |
|
break; |
|
default: |
|
return CppTypes.Unknown; |
|
} |
|
|
|
if (!NodeToNamespace.ContainsKey (n)) { |
|
// FIXME: Do something better |
|
return CppTypes.Unknown; |
|
} |
|
|
|
return modifiers.CopyTypeFrom (new CppType (fundamental, NodeToNamespace [n].FullyQualifiedName)); |
|
} |
|
|
|
Node GetTypeNode (Node n) { |
|
return Node.IdToNode [n.Attributes ["type"]]; |
|
} |
|
|
|
// Return the System.Type name corresponding to T, or null |
|
// Returned as a string, because other wrappers do not have System.Types yet |
|
public string CppTypeToManaged (CppType t) { |
|
|
|
Type mtype = t.ToManagedType (); |
|
if (mtype != null && mtype != typeof (ICppObject)) { |
|
return mtype.FullName; |
|
} |
|
|
|
switch (t.ElementType) { |
|
|
|
case CppTypes.Class: |
|
case CppTypes.Struct: |
|
case CppTypes.Enum: |
|
|
|
var filter = GetFilterOrDefault (t); |
|
var qname = filter.TypeName.Replace ("::", "."); |
|
|
|
if (filter.ImplType == ImplementationType.@struct && !IsByVal (t)) |
|
return qname + "&"; |
|
else |
|
return qname; |
|
|
|
} |
|
|
|
return null; |
|
} |
|
|
|
void GenerateCode () { |
|
Directory.CreateDirectory (OutputDir); |
|
|
|
// Generate Libs file |
|
using (TextWriter w = File.CreateText (Path.Combine (OutputDir, "Libs.cs"))) { |
|
LibsTemplate.Generator = this; |
|
LibsTemplate.Libs = new[] { Lib }; |
|
w.Write (LibsTemplate.TransformText ()); |
|
} |
|
|
|
|
|
// Generate user types |
|
foreach (Namespace ns in Lib.Namespaces) { |
|
if (ns.ParentNamespace is Class) |
|
continue; |
|
|
|
var klass = ns as Class; |
|
if (klass != null) { |
|
if (klass.Disable) |
|
continue; |
|
|
|
using (TextWriter w = File.CreateText (Path.Combine (OutputDir, klass.Name + ".cs"))) { |
|
ClassTemplate.Generator = this; |
|
ClassTemplate.Class = klass; |
|
ClassTemplate.Nested = false; |
|
w.Write (ClassTemplate.TransformText ()); |
|
} |
|
|
|
continue; |
|
} |
|
|
|
var @enum = ns as Enumeration; |
|
if (@enum != null) { |
|
|
|
using (TextWriter w = File.CreateText (Path.Combine (OutputDir, @enum.Name + ".cs"))) { |
|
EnumTemplate.Generator = this; |
|
EnumTemplate.Enum = @enum; |
|
EnumTemplate.Nested = false; |
|
w.Write (EnumTemplate.TransformText ()); |
|
} |
|
|
|
continue; |
|
} |
|
|
|
} |
|
} |
|
|
|
static public bool IsByVal (CppType t) |
|
{ |
|
return ((t.ElementType == CppTypes.Class || t.ElementType == CppTypes.Struct) && |
|
!t.Modifiers.Contains (CppModifiers.Pointer) && |
|
!t.Modifiers.Contains (CppModifiers.Reference) && |
|
!t.Modifiers.Contains (CppModifiers.Array)); |
|
} |
|
|
|
Filter GetFilterOrDefault (Namespace ns) |
|
{ |
|
return GetFilterOrDefault (ns.FullyQualifiedName); |
|
} |
|
|
|
Filter GetFilterOrDefault (CppType cpptype) |
|
{ |
|
var fqn = cpptype.ElementTypeName; |
|
if (cpptype.Namespaces != null) |
|
fqn = string.Join ("::", cpptype.Namespaces) + "::" + fqn; |
|
|
|
var newtype = new CppType (fqn, cpptype.Modifiers.Where (m => m == CppModifiers.Template)); |
|
return GetFilterOrDefault (newtype.ToString ().Replace (" ", "")); |
|
} |
|
|
|
Filter GetFilterOrDefault (string fqn) |
|
{ |
|
Filter result; |
|
if (Filters != null && Filters.TryGetValue (fqn, out result)) |
|
return result; |
|
|
|
return new Filter { TypeName = fqn, Mode = default_filter_mode }; |
|
} |
|
}
|
|
|