public unsafe 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 = ClangSharp.Index.Create(false, true)) using (var translationUnit = CXTranslationUnit.Parse(createIndex.Handle, this.InputFile, arguments, null, CXTranslationUnit_Flags.CXTranslationUnit_None)) { StringWriter errorWriter = new StringWriter(); var set = translationUnit.DiagnosticSet; var numDiagnostics = set.NumDiagnostics; bool hasError = false; bool hasWarning = false; for (uint i = 0; i < numDiagnostics; ++i) { CXDiagnostic diagnostic = set.GetDiagnostic(i); var severity = diagnostic.Severity; switch (severity) { case CXDiagnosticSeverity.CXDiagnostic_Error: case CXDiagnosticSeverity.CXDiagnostic_Fatal: hasError = true; break; case CXDiagnosticSeverity.CXDiagnostic_Warning: hasWarning = true; break; } var location = diagnostic.Location; location.GetFileLocation(out CXFile file, out uint line, out _, out _); var fileName = file.Name.CString; var message = diagnostic.Spelling.CString; 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 DelegatingCXCursorVisitor(enumVisitor.Visit); translationUnit.Cursor.VisitChildren(realEnumVisitor.Visit, new CXClientData()); // Creates structs var structVisitor = new StructVisitor(this); var realStructVisitor = new DelegatingCXCursorVisitor(structVisitor.Visit); translationUnit.Cursor.VisitChildren(realStructVisitor.Visit, new CXClientData()); // Creates safe handles & delegates var typeDefVisitor = new TypeDefVisitor(this); var realTypeDefVisitor = new DelegatingCXCursorVisitor(typeDefVisitor.Visit); translationUnit.Cursor.VisitChildren(realTypeDefVisitor.Visit, new CXClientData()); // Creates functions in a NativeMethods class functionVisitor = new FunctionVisitor(this, libraryName); var realFunctionVisitor = new DelegatingCXCursorVisitor(functionVisitor.Visit); translationUnit.Cursor.VisitChildren(realFunctionVisitor.Visit, new CXClientData()); 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") { var createIndex = clang.createIndex(0, 0); string[] arguments = { "-x", "c++", "-Wdocumentation" }; arguments = arguments.Concat(this.IncludeDirectories.Select(x => "-I" + x)).ToArray(); CXTranslationUnit translationUnit; CXUnsavedFile unsavedFile; var translationUnitError = clang.parseTranslationUnit2(createIndex, this.InputFile, arguments, arguments.Length, out unsavedFile, 0, 0, out translationUnit); StringWriter errorWriter = new StringWriter(); var numDiagnostics = clang.getNumDiagnostics(translationUnit); bool hasError = false; bool hasWarning = false; for (uint i = 0; i < numDiagnostics; ++i) { var diagnostic = clang.getDiagnostic(translationUnit, i); var severity = clang.getDiagnosticSeverity(diagnostic); switch (severity) { case CXDiagnosticSeverity.CXDiagnostic_Error: case CXDiagnosticSeverity.CXDiagnostic_Fatal: hasError = true; break; case CXDiagnosticSeverity.CXDiagnostic_Warning: hasWarning = true; break; } var location = clang.getDiagnosticLocation(diagnostic); CXFile file; uint line; uint column; uint offset; clang.getFileLocation(location, out file, out line, out column, out offset); var fileName = clang.getFileName(file).ToString(); var message = clang.getDiagnosticSpelling(diagnostic).ToString(); errorWriter.WriteLine($"{severity}: {fileName}:{line} {message}"); clang.disposeDiagnostic(diagnostic); } 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); clang.visitChildren(clang.getTranslationUnitCursor(translationUnit), enumVisitor.Visit, new CXClientData(IntPtr.Zero)); // Creates structs var structVisitor = new StructVisitor(this); clang.visitChildren(clang.getTranslationUnitCursor(translationUnit), structVisitor.Visit, new CXClientData(IntPtr.Zero)); // Creates safe handles & delegates var typeDefVisitor = new TypeDefVisitor(this); clang.visitChildren(clang.getTranslationUnitCursor(translationUnit), typeDefVisitor.Visit, new CXClientData(IntPtr.Zero)); // Creates functions in a NativeMethods class var functionVisitor = new FunctionVisitor(this, libraryName); clang.visitChildren(clang.getTranslationUnitCursor(translationUnit), functionVisitor.Visit, new CXClientData(IntPtr.Zero)); clang.disposeTranslationUnit(translationUnit); clang.disposeIndex(createIndex); 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); } } }