using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; namespace QuestPDF.InteropGenerators; /// /// Generates C# UnmanagedCallersOnly bindings for interop /// public static class CSharpInteropGenerator { /// /// Generates the complete C# interop code /// public static string GenerateInteropCode(List extensionMethods) { var sb = new StringBuilder(); sb.AppendLine("// "); sb.AppendLine("#nullable enable"); sb.AppendLine(); sb.AppendLine("using System;"); sb.AppendLine("using System.Runtime.CompilerServices;"); sb.AppendLine("using System.Runtime.InteropServices;"); sb.AppendLine(); sb.AppendLine("namespace QuestPDF.Generated;"); sb.AppendLine(); sb.AppendLine("public static unsafe class GeneratedInterop"); sb.AppendLine("{"); // Add helper methods GenerateHelperMethods(sb); foreach (var method in extensionMethods) { GenerateInteropMethod(sb, method); } sb.AppendLine("}"); return sb.ToString(); } private static void GenerateHelperMethods(StringBuilder sb) { sb.AppendLine(" static IntPtr BoxHandle(object obj)"); sb.AppendLine(" {"); sb.AppendLine(" var gch = GCHandle.Alloc(obj, GCHandleType.Normal);"); sb.AppendLine(" return GCHandle.ToIntPtr(gch);"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" static T UnboxHandle(nint handle) where T : class"); sb.AppendLine(" {"); sb.AppendLine(" var gch = GCHandle.FromIntPtr(handle);"); sb.AppendLine(" return (T)gch.Target!;"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" [UnmanagedCallersOnly(EntryPoint = \"questpdf_free_handle\", CallConvs = new[] { typeof(CallConvCdecl) })]"); sb.AppendLine(" public static void FreeHandle(nint handle)"); sb.AppendLine(" {"); sb.AppendLine(" if (handle == 0) return;"); sb.AppendLine(" var gch = GCHandle.FromIntPtr(handle);"); sb.AppendLine(" if (gch.IsAllocated) gch.Free();"); sb.AppendLine(" }"); sb.AppendLine(); } private static void GenerateInteropMethod(StringBuilder sb, IMethodSymbol method) { sb.AppendLine(); if (!PublicApiAnalyzer.IsSupported(method)) { GenerateUnsupportedMethodComment(sb, method); return; } var methodName = GenerateMethodName(method); var isExtensionMethod = method.IsExtensionMethod; var isInstanceMethod = !method.IsStatic && !isExtensionMethod; // Determine interop signature (reference types become nint handles) var interopReturnType = PublicApiAnalyzer.IsReferenceType(method.ReturnType) ? "nint" : method.ReturnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); // For instance methods, add 'this' parameter as first parameter var interopParametersList = new List(); if (isInstanceMethod) { // Instance methods need the 'this' object as first parameter interopParametersList.Add("nint @this"); } interopParametersList.AddRange(method.Parameters.Select(p => { var paramType = PublicApiAnalyzer.IsReferenceType(p.Type) ? "nint" : p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); return $"{paramType} {p.Name}"; })); var interopParameters = string.Join(", ", interopParametersList); sb.AppendLine($" [UnmanagedCallersOnly(EntryPoint = \"{GenerateEntryPointName(method)}\", CallConvs = new[] {{ typeof(CallConvCdecl) }})]"); sb.AppendLine($" public static {interopReturnType} {methodName}({interopParameters})"); sb.AppendLine(" {"); // For instance methods, unbox the 'this' parameter if (isInstanceMethod) { var containingType = method.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); sb.AppendLine($" var this_obj = UnboxHandle<{containingType}>(@this);"); } // Unbox reference type parameters foreach (var param in method.Parameters) { if (PublicApiAnalyzer.IsReferenceType(param.Type)) { var actualType = param.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); sb.AppendLine($" var {param.Name}_obj = UnboxHandle<{actualType}>({param.Name});"); } } // Build argument list (use unboxed versions for reference types) var arguments = string.Join(", ", method.Parameters.Select(p => PublicApiAnalyzer.IsReferenceType(p.Type) ? $"{p.Name}_obj" : p.Name)); // Build the call target string callTarget; if (isInstanceMethod) { callTarget = $"this_obj.{method.Name}"; } else { callTarget = $"{method.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}.{method.Name}"; } // Call the method and handle the result if (method.ReturnsVoid) { sb.AppendLine($" {callTarget}({arguments});"); } else if (PublicApiAnalyzer.IsReferenceType(method.ReturnType)) { sb.AppendLine($" var result = {callTarget}({arguments});"); sb.AppendLine($" return BoxHandle(result);"); } else { sb.AppendLine($" return {callTarget}({arguments});"); } sb.AppendLine(" }"); } /// /// Generates the entry point name for a method /// public static string GenerateEntryPointName(IMethodSymbol method) { var namespaceParts = method.ContainingNamespace.ToDisplayString().ToLowerInvariant().Replace(".", "_"); var typeName = method.ContainingType.Name.ToLowerInvariant(); var methodName = method.Name.ToLowerInvariant(); return $"{namespaceParts}_{typeName}_{methodName}"; } private static void GenerateUnsupportedMethodComment(StringBuilder sb, IMethodSymbol method) { var returnType = method.ReturnType.ToDisplayString(); var parameters = string.Join(", ", method.Parameters.Select(p => { var refKind = p.RefKind switch { RefKind.Ref => "ref ", RefKind.Out => "out ", RefKind.In => "in ", _ => "" }; return $"{refKind}{p.Type.ToDisplayString()} {p.Name}"; })); var fullSignature = $"{method.ContainingNamespace}.{method.ContainingType.Name}.{method.Name}({parameters}) : {returnType}"; sb.AppendLine($" // UNSUPPORTED: {fullSignature}"); } private static string GenerateMethodName(IMethodSymbol method) { var name = $"{method.ContainingType.Name}_{method.Name}"; if (method.Parameters.Length > 0) { var paramTypes = string.Join("_", method.Parameters.Select(p => SanitizeTypeName(p.Type.Name))); name += "_" + paramTypes; } return name; } private static string SanitizeTypeName(string typeName) { return typeName .Replace("<", "_") .Replace(">", "_") .Replace(",", "_") .Replace(" ", "") .Replace("?", "Nullable") .Replace("[]", "Array") .Replace("*", "Ptr") .Replace("&", "Ref"); } }