public static CodeTypeDelegate ToDelegate(this TypeInfo type, string nativeName, Cursor cursor, ModuleGenerator generator) { if (type.Kind != TypeKind.FunctionProto && type.Kind != TypeKind.Unexposed) { throw new InvalidOperationException(); } var clrName = NameConversions.ToClrName(nativeName, NameConversion.Type); CodeTypeDelegate delegateType = new CodeTypeDelegate(); delegateType.CustomAttributes.Add( new CodeAttributeDeclaration( new CodeTypeReference(typeof(UnmanagedFunctionPointerAttribute)), new CodeAttributeArgument( new CodePropertyReferenceExpression( new CodeTypeReferenceExpression(typeof(CallingConvention)), type.GetCallingConvention().ToString())))); delegateType.Attributes = MemberAttributes.Public | MemberAttributes.Final; delegateType.Name = clrName; delegateType.ReturnType = new CodeTypeReference(type.GetResultType().ToClrType()); uint argumentCounter = 0; var cursorVisitor = new DelegatingCursorVisitor( delegate(Cursor c, Cursor parent1) { if (c.Kind == CursorKind.ParmDecl) { delegateType.Parameters.Add(Argument.GenerateArgument(generator, type, c, argumentCounter++, FunctionType.Delegate)); } return(ChildVisitResult.Continue); }); cursorVisitor.VisitChildren(cursor); return(delegateType); }
public ChildVisitResult Visit(Cursor cursor, Cursor parent) { if (!cursor.GetLocation().IsFromMainFile()) { return(ChildVisitResult.Continue); } CursorKind curKind = cursor.Kind; if (curKind == CursorKind.EnumDecl) { var nativeName = cursor.GetSpelling(); var type = cursor.GetEnumDeclIntegerType().ToClrType(); var enumComment = this.GetComment(cursor, forType: true); // enumName can be empty because of typedef enum { .. } enumName; // so we have to find the sibling, and this is the only way I've found // to do with libclang, maybe there is a better way? if (string.IsNullOrEmpty(nativeName)) { var forwardDeclaringVisitor = new ForwardDeclarationVisitor(cursor, skipSystemHeaderCheck: true); forwardDeclaringVisitor.VisitChildren(cursor.GetLexicalParent()); nativeName = forwardDeclaringVisitor.ForwardDeclarationCursor.GetSpelling(); if (string.IsNullOrEmpty(nativeName)) { nativeName = "_"; } } var clrName = NameConversions.ToClrName(nativeName, NameConversion.Type); // if we've printed these previously, skip them if (this.generator.NameMapping.ContainsKey(nativeName)) { return(ChildVisitResult.Continue); } CodeTypeDeclaration enumDeclaration = new CodeTypeDeclaration(); enumDeclaration.Attributes = MemberAttributes.Public; enumDeclaration.IsEnum = true; enumDeclaration.Name = clrName; enumDeclaration.BaseTypes.Add(type); if (enumComment != null) { enumDeclaration.Comments.Add(enumComment); } // visit all the enum values DelegatingCursorVisitor visitor = new DelegatingCursorVisitor( (c, vistor) => { var field = new CodeMemberField() { Name = NameConversions.ToClrName(c.GetSpelling(), NameConversion.Field), InitExpression = new CodePrimitiveExpression(c.GetEnumConstantDeclValue()) }; var fieldComment = this.GetComment(c, forType: true); if (fieldComment != null) { field.Comments.Add(fieldComment); } enumDeclaration.Members.Add(field); return(ChildVisitResult.Continue); }); visitor.VisitChildren(cursor); this.generator.AddType(nativeName, new CodeDomGeneratedType(enumDeclaration)); } return(ChildVisitResult.Recurse); }
public ChildVisitResult Visit(Cursor cursor, Cursor parent) { if (!cursor.GetLocation().IsFromMainFile()) { return(ChildVisitResult.Continue); } CursorKind curKind = cursor.Kind; if (curKind == CursorKind.StructDecl) { this.fieldPosition = 0; var nativeName = cursor.GetSpelling(); // struct names can be empty, and so we visit its sibling to find the name if (string.IsNullOrEmpty(nativeName)) { var forwardDeclaringVisitor = new ForwardDeclarationVisitor(cursor, skipSystemHeaderCheck: true); forwardDeclaringVisitor.VisitChildren(cursor.GetSemanticParent()); nativeName = forwardDeclaringVisitor.ForwardDeclarationCursor.GetSpelling(); if (string.IsNullOrEmpty(nativeName)) { nativeName = "_"; } } var clrName = NameConversions.ToClrName(nativeName, NameConversion.Type); if (!this.generator.NameMapping.ContainsKey(nativeName)) { this.current = new CodeTypeDeclaration(clrName); this.current.IsStruct = true; this.current.Attributes = MemberAttributes.Public | MemberAttributes.Final; this.generator.AddType(nativeName, this.current); var layoutAttribute = new CodeAttributeDeclaration( new CodeTypeReference(typeof(StructLayoutAttribute)), new CodeAttributeArgument( new CodePropertyReferenceExpression( new CodeTypeReferenceExpression(typeof(LayoutKind)), nameof(LayoutKind.Sequential)))); this.current.CustomAttributes.Add(layoutAttribute); var visitor = new DelegatingCursorVisitor(this.Visit); visitor.VisitChildren(cursor); } return(ChildVisitResult.Continue); } if (curKind == CursorKind.FieldDecl) { var fieldName = cursor.GetSpelling(); if (string.IsNullOrEmpty(fieldName)) { fieldName = "field" + this.fieldPosition; // what if they have fields called field*? :) } this.fieldPosition++; foreach (var member in cursor.ToCodeTypeMember(fieldName, this.generator)) { this.current.Members.Add(member); } return(ChildVisitResult.Continue); } return(ChildVisitResult.Recurse); }
public void GenerateTypes(string libraryName = "imobiledevice") { string[] arguments = { // Use the C++ backend "-x", "c++", // Parse the doxygen comments "-Wdocumentation", // Target 32-bit OS "-m32" }; arguments = arguments.Concat(this.IncludeDirectories.Select(x => "-I" + x)).ToArray(); FunctionVisitor functionVisitor; using (var createIndex = new Index(false, true)) using (var translationUnit = createIndex.ParseTranslationUnit(this.InputFile, arguments)) { StringWriter errorWriter = new StringWriter(); var set = DiagnosticSet.FromTranslationUnit(translationUnit); var numDiagnostics = set.GetNumDiagnostics(); bool hasError = false; bool hasWarning = false; for (uint i = 0; i < numDiagnostics; ++i) { Diagnostic diagnostic = set.GetDiagnostic(i); var severity = diagnostic.GetSeverity(); switch (severity) { case DiagnosticSeverity.Error: case DiagnosticSeverity.Fatal: hasError = true; break; case DiagnosticSeverity.Warning: hasWarning = true; break; } var location = diagnostic.GetLocation(); var fileName = location.SourceFile; var line = location.Line; var message = diagnostic.GetSpelling(); errorWriter.WriteLine($"{severity}: {fileName}:{line} {message}"); } if (hasError) { throw new Exception(errorWriter.ToString()); } if (hasWarning) { // Dump the warnings to the console output. Console.WriteLine(errorWriter.ToString()); } // Generate the marhaler types for string arrays (char **) var arrayMarshalerGenerator = new ArrayMarshalerGenerator(this); arrayMarshalerGenerator.Generate(); // Creates enums var enumVisitor = new EnumVisitor(this); var realEnumVisitor = new DelegatingCursorVisitor(enumVisitor.Visit); var cursor = translationUnit.GetCursor(); realEnumVisitor.VisitChildren(cursor); // Creates structs var structVisitor = new StructVisitor(this); var realStructVisitor = new DelegatingCursorVisitor(structVisitor.Visit); realStructVisitor.VisitChildren(translationUnit.GetCursor()); // Creates safe handles & delegates var typeDefVisitor = new TypeDefVisitor(this); var realTypeDefVisitor = new DelegatingCursorVisitor(typeDefVisitor.Visit); realTypeDefVisitor.VisitChildren(translationUnit.GetCursor()); // Creates functions in a NativeMethods class functionVisitor = new FunctionVisitor(this, libraryName); var realFunctionVisitor = new DelegatingCursorVisitor(functionVisitor.Visit); realFunctionVisitor.VisitChildren(translationUnit.GetCursor()); createIndex.Dispose(); } // Update the SafeHandle to call the _free method var handles = this.Types.Where(t => t.Name.EndsWith("Handle")); foreach (var handle in handles) { var freeMethod = functionVisitor.NativeMethods.Members .OfType <CodeMemberMethod>() .Where(m => (m.Name.EndsWith("_free") || m.Name.EndsWith("_disconnect")) && m.Parameters.Count == 1 && m.Parameters[0].Type.BaseType == handle.Name) .SingleOrDefault(); if (freeMethod == null) { continue; } var type = (HandleType)((NustacheGeneratedType)handle).Type; type.ReleaseMethodName = freeMethod.Name; type.ReleaseMethodReturnsValue = freeMethod.ReturnType.BaseType != "System.Void"; // Directly pass the IntPtr, becuase the handle itself will already be in the 'closed' state // when this method is called. freeMethod.Parameters[0].Type = new CodeTypeReference(typeof(IntPtr)); freeMethod.Parameters[0].Direction = FieldDirection.In; } // Extract the API interface and class, as well as the Exception class. Used for DI. ApiExtractor extractor = new ApiExtractor(this, functionVisitor); extractor.Generate(); // Add the 'Error' extension IsError and ThrowOnError extension methods var extensionsExtractor = new ErrorExtensionExtractor(this, functionVisitor); extensionsExtractor.Generate(); // Patch the native methods to be compatible with .NET Core - basically, // do the marshalling ourselves NativeMethodOverloadGenerator.Generate(this); }
public void Generate(string targetDirectory, string libraryName = "imobiledevice") { string[] arguments = { // Use the C++ backend "-x", "c++", // Parse the doxygen comments "-Wdocumentation", // Target 32-bit OS "-m32" }; arguments = arguments.Concat(this.IncludeDirectories.Select(x => "-I" + x)).ToArray(); FunctionVisitor functionVisitor; using (var createIndex = new Index(false, true)) using (var translationUnit = createIndex.ParseTranslationUnit(this.InputFile, arguments)) { StringWriter errorWriter = new StringWriter(); var set = DiagnosticSet.FromTranslationUnit(translationUnit); var numDiagnostics = set.GetNumDiagnostics(); bool hasError = false; bool hasWarning = false; for (uint i = 0; i < numDiagnostics; ++i) { Diagnostic diagnostic = set.GetDiagnostic(i); var severity = diagnostic.GetSeverity(); switch (severity) { case DiagnosticSeverity.Error: case DiagnosticSeverity.Fatal: hasError = true; break; case DiagnosticSeverity.Warning: hasWarning = true; break; } var location = diagnostic.GetLocation(); var fileName = location.SourceFile; var line = location.Line; var message = diagnostic.GetSpelling(); errorWriter.WriteLine($"{severity}: {fileName}:{line} {message}"); } if (hasError) { throw new Exception(errorWriter.ToString()); } if (hasWarning) { // Dump the warnings to the console output. Console.WriteLine(errorWriter.ToString()); } // Generate the marhaler types for string arrays (char **) var arrayMarshalerGenerator = new ArrayMarshalerGenerator(this); arrayMarshalerGenerator.Generate(); // Creates enums var enumVisitor = new EnumVisitor(this); var realEnumVisitor = new DelegatingCursorVisitor(enumVisitor.Visit); var cursor = translationUnit.GetCursor(); realEnumVisitor.VisitChildren(cursor); // Creates structs var structVisitor = new StructVisitor(this); var realStructVisitor = new DelegatingCursorVisitor(structVisitor.Visit); realStructVisitor.VisitChildren(translationUnit.GetCursor()); // Creates safe handles & delegates var typeDefVisitor = new TypeDefVisitor(this); var realTypeDefVisitor = new DelegatingCursorVisitor(typeDefVisitor.Visit); realTypeDefVisitor.VisitChildren(translationUnit.GetCursor()); // Creates functions in a NativeMethods class functionVisitor = new FunctionVisitor(this, libraryName); var realFunctionVisitor = new DelegatingCursorVisitor(functionVisitor.Visit); realFunctionVisitor.VisitChildren(translationUnit.GetCursor()); createIndex.Dispose(); } var moduleDirectory = Path.Combine(targetDirectory, this.Name); if (!Directory.Exists(moduleDirectory)) { Directory.CreateDirectory(moduleDirectory); } // Update the SafeHandle to call the _free method var handles = this.Types.Where(t => t.Name.EndsWith("Handle")); foreach (var handle in handles) { var freeMethod = functionVisitor.NativeMethods.Members .OfType <CodeMemberMethod>() .Where(m => (m.Name.EndsWith("_free") || m.Name.EndsWith("_disconnect")) && m.Parameters.Count == 1 && m.Parameters[0].Type.BaseType == handle.Name) .SingleOrDefault(); if (freeMethod == null) { continue; } // Directly pass the IntPtr, becuase the handle itself will already be in the 'closed' state // when this method is called. freeMethod.Parameters[0].Type = new CodeTypeReference(typeof(IntPtr)); freeMethod.Parameters[0].Direction = FieldDirection.In; var releaseMethod = handle.Members.OfType <CodeMemberMethod>().Single(m => m.Name == "ReleaseHandle"); // Sample statement: // System.Diagnostics.Debug.WriteLine("Releasing {0} {1}", this.GetType().Name, this.handle); // this.Api.Plist.plist_free(this.handle); // return true; releaseMethod.Statements.Clear(); // Trace the release call: // Debug.WriteLine("Releasing {0} {1}", this.GetType().Name, this.handle); releaseMethod.Statements.Add( new CodeMethodInvokeExpression( new CodeMethodReferenceExpression( new CodeTypeReferenceExpression(typeof(Debug)), "WriteLine"), new CodePrimitiveExpression("Releasing {0} {1} using {2}. This object was created at {3}"), new CodePropertyReferenceExpression( new CodeMethodInvokeExpression( new CodeThisReferenceExpression(), nameof(GetType)), "Name"), new CodeFieldReferenceExpression( new CodeThisReferenceExpression(), "handle"), new CodePropertyReferenceExpression( new CodeThisReferenceExpression(), "Api"), new CodeFieldReferenceExpression( new CodeThisReferenceExpression(), "creationStackTrace"))); var freeMethodInvokeExpression = new CodeMethodInvokeExpression( new CodeMethodReferenceExpression( new CodePropertyReferenceExpression( new CodePropertyReferenceExpression( new CodeThisReferenceExpression(), "Api"), this.Name), freeMethod.Name), new CodeFieldReferenceExpression( new CodeThisReferenceExpression(), "handle")); if (freeMethod.ReturnType.BaseType != "System.Void") { // If the free method returns a value, it's an error code, and we can make sure the value indicates // success. releaseMethod.Statements.Add( new CodeMethodReturnStatement( new CodeBinaryOperatorExpression( freeMethodInvokeExpression, CodeBinaryOperatorType.IdentityEquality, new CodePropertyReferenceExpression( new CodeTypeReferenceExpression($"{this.Name}Error"), "Success")))); } else { // If it does not, we always return true (which is a pitty, but this is how plist is implemented for now) // - on the other hand, in how many ways can free() really fail? :-) releaseMethod.Statements.Add(freeMethodInvokeExpression); releaseMethod.Statements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression(true))); } } // Extract the API interface and class, as well as the Exception class. Used for DI. ApiExtractor extractor = new ApiExtractor(this, functionVisitor); extractor.Generate(); // Add the 'Error' extension IsError and ThrowOnError extension methods var extensionsExtractor = new ErrorExtensionExtractor(this, functionVisitor); extensionsExtractor.Generate(); // Patch the native methods to be compatible with .NET Core - basically, // do the marshalling ourselves NativeMethodOverloadGenerator.Generate(this); // Write the files foreach (var declaration in this.Types) { if (declaration.Name.EndsWith("Private")) { continue; } // Generate the container unit CodeCompileUnit program = new CodeCompileUnit(); // Generate the namespace CodeNamespace ns = new CodeNamespace($"iMobileDevice.{this.Name}"); ns.Imports.Add(new CodeNamespaceImport("System.Runtime.InteropServices")); ns.Imports.Add(new CodeNamespaceImport("System.Diagnostics")); ns.Imports.Add(new CodeNamespaceImport("iMobileDevice.iDevice")); ns.Imports.Add(new CodeNamespaceImport("iMobileDevice.Lockdown")); ns.Imports.Add(new CodeNamespaceImport("iMobileDevice.Afc")); ns.Imports.Add(new CodeNamespaceImport("iMobileDevice.Plist")); ns.Types.Add(declaration); program.Namespaces.Add(ns); string suffix = string.Empty; if (declaration.UserData.Contains("FileNameSuffix")) { suffix = (string)declaration.UserData["FileNameSuffix"]; } string path = Path.Combine(moduleDirectory, $"{declaration.Name}{suffix}.cs"); using (var outFile = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None)) using (var fileWriter = new StreamWriter(outFile)) using (var indentedTextWriter = new CSharpTextWriter(fileWriter, " ")) { // Generate source code using the code provider. indentedTextWriter.Generate(ns); indentedTextWriter.Flush(); } // Add #if statements for code that doesn't work on .NET Core if (true) { string content = File.ReadAllText(path); content = content.Replace("#region !core", "#if !NETSTANDARD1_5"); content = content.Replace("#endregion", "#endif"); using (StringReader reader = new StringReader(content)) using (StreamWriter writer = new StreamWriter(path)) { while (reader.Peek() >= 0) { string line = reader.ReadLine(); if (line.Contains(nameof(SecurityPermissionAttribute)) || line.Contains(nameof(ReliabilityContractAttribute)) || line.Contains(nameof(SerializableAttribute))) { writer.WriteLine("#if !NETSTANDARD1_5"); writer.WriteLine(line); writer.WriteLine("#endif"); } else if (line.Contains("SerializationInfo info")) { writer.WriteLine("#if !NETSTANDARD1_5"); writer.WriteLine(line); writer.WriteLine(reader.ReadLine()); writer.WriteLine(reader.ReadLine()); writer.WriteLine(reader.ReadLine()); writer.WriteLine("#endif"); } else { writer.WriteLine(line); } } } } // Fix other CodeDOM shortcomings if (declaration.Name.EndsWith("NativeMethods") && string.IsNullOrEmpty(suffix)) { string content = File.ReadAllText(path); content = content.Replace("public abstract", "public static extern"); File.WriteAllText(path, content); } if (declaration.Name.EndsWith("Extensions")) { string content = File.ReadAllText(path); content = content.Replace("public class", "public static class"); File.WriteAllText(path, content); } } }