| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using Microsoft.CodeAnalysis;
- namespace QuestPDF.InteropGenerators;
- /// <summary>
- /// Generates C# UnmanagedCallersOnly bindings for interop
- /// </summary>
- public static class CSharpInteropGenerator
- {
- /// <summary>
- /// Generates the complete C# interop code
- /// </summary>
- public static string GenerateInteropCode(List<IMethodSymbol> extensionMethods)
- {
- var sb = new StringBuilder();
-
- sb.AppendLine("// <auto-generated/>");
- 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<T>(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 callTarget = $"{method.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}.{method.Name}";
-
- // Determine interop signature (reference types become nint handles)
- var interopReturnType = PublicApiAnalyzer.IsReferenceType(method.ReturnType)
- ? "nint"
- : method.ReturnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
-
- var interopParameters = string.Join(", ", method.Parameters.Select(p =>
- {
- var paramType = PublicApiAnalyzer.IsReferenceType(p.Type)
- ? "nint"
- : p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
- return $"{paramType} {p.Name}";
- }));
-
- sb.AppendLine($" [UnmanagedCallersOnly(EntryPoint = \"{GenerateEntryPointName(method)}\", CallConvs = new[] {{ typeof(CallConvCdecl) }})]");
- sb.AppendLine($" public static {interopReturnType} {methodName}({interopParameters})");
- sb.AppendLine(" {");
-
- // 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));
-
- // 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(" }");
- }
-
- /// <summary>
- /// Generates the entry point name for a method
- /// </summary>
- 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");
- }
- }
|