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");
}
}