| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using Microsoft.CodeAnalysis;
- using Scriban;
- namespace QuestPDF.InteropGenerators;
- /// <summary>
- /// Generates C# UnmanagedCallersOnly bindings for interop using Scriban templates
- /// </summary>
- public static class CSharpInteropGenerator
- {
- private const string CSharpTemplate = @"// <auto-generated/>
- #nullable enable
- using System;
- using System.Runtime.CompilerServices;
- using System.Runtime.InteropServices;
- namespace QuestPDF.Generated;
- public static unsafe class GeneratedInterop
- {
- static IntPtr BoxHandle(object obj)
- {
- var gch = GCHandle.Alloc(obj, GCHandleType.Normal);
- return GCHandle.ToIntPtr(gch);
- }
- static T UnboxHandle<T>(nint handle) where T : class
- {
- var gch = GCHandle.FromIntPtr(handle);
- return (T)gch.Target!;
- }
- [UnmanagedCallersOnly(EntryPoint = ""questpdf_free_handle"", CallConvs = new[] { typeof(CallConvCdecl) })]
- public static void FreeHandle(nint handle)
- {
- if (handle == 0) return;
- var gch = GCHandle.FromIntPtr(handle);
- if (gch.IsAllocated) gch.Free();
- }
- {{ for m in methods }}
- {{~ if m.unsupported ~}}
- // UNSUPPORTED: {{ m.unsupported_signature }}
- {{~ else ~}}
- [UnmanagedCallersOnly(EntryPoint = ""{{ m.entry_point }}"", CallConvs = new[] { typeof(CallConvCdecl) })]
- public static {{ m.return_type }} {{ m.method_name }}({{ m.parameters_declaration }})
- {
- {{ m.body }}
- }
- {{~ end ~}}
- {{ end }}
- }
- ";
- private sealed class MethodModel
- {
- public bool unsupported { get; set; }
- public string unsupported_signature { get; set; } = string.Empty;
- public string entry_point { get; set; } = string.Empty;
- public string return_type { get; set; } = string.Empty;
- public string method_name { get; set; } = string.Empty;
- public string parameters_declaration { get; set; } = string.Empty;
- public string body { get; set; } = string.Empty;
- }
- /// <summary>
- /// Generates the complete C# interop code
- /// </summary>
- public static string GenerateInteropCode(List<IMethodSymbol> extensionMethods)
- {
- var methods = extensionMethods.Select(BuildMethodModel).ToList();
- var template = Template.Parse(CSharpTemplate);
- var output = template.Render(new { methods });
- return output;
- }
- private static MethodModel BuildMethodModel(IMethodSymbol method)
- {
- if (!PublicApiAnalyzer.IsSupported(method))
- {
- // build unsupported signature as before
- 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 ",
- _ => string.Empty
- };
- return $"{refKind}{p.Type.ToDisplayString()} {p.Name}";
- }));
- var fullSignature = $"{method.ContainingNamespace}.{method.ContainingType.Name}.{method.Name}({parameters}) : {returnType}";
- return new MethodModel
- {
- unsupported = true,
- unsupported_signature = fullSignature
- };
- }
- var isExtensionMethod = method.IsExtensionMethod;
- var isInstanceMethod = !method.IsStatic && !isExtensionMethod;
- var interopReturnType = PublicApiAnalyzer.IsReferenceType(method.ReturnType)
- ? "nint"
- : method.ReturnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
- var parametersList = new List<string>();
- if (isInstanceMethod)
- parametersList.Add("nint @this");
- parametersList.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(", ", parametersList);
- // Build body
- var bodySb = new StringBuilder();
- // indent 8 spaces inside method
- if (isInstanceMethod)
- {
- var containingType = method.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
- bodySb.AppendLine($" var this_obj = UnboxHandle<{containingType}>(@this);");
- }
- foreach (var param in method.Parameters)
- {
- if (PublicApiAnalyzer.IsReferenceType(param.Type))
- {
- var actualType = param.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
- bodySb.AppendLine($" var {param.Name}_obj = UnboxHandle<{actualType}>({param.Name});");
- }
- }
- var arguments = string.Join(", ", method.Parameters.Select(p =>
- PublicApiAnalyzer.IsReferenceType(p.Type) ? $"{p.Name}_obj" : p.Name));
- string callTarget = isInstanceMethod
- ? $"this_obj.{method.Name}"
- : $"{method.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}.{method.Name}";
- if (method.ReturnsVoid)
- {
- bodySb.AppendLine($" {callTarget}({arguments});");
- }
- else if (PublicApiAnalyzer.IsReferenceType(method.ReturnType))
- {
- bodySb.AppendLine($" var result = {callTarget}({arguments});");
- bodySb.AppendLine($" return BoxHandle(result);");
- }
- else
- {
- bodySb.AppendLine($" return {callTarget}({arguments});");
- }
- return new MethodModel
- {
- unsupported = false,
- entry_point = GenerateEntryPointName(method),
- return_type = interopReturnType,
- method_name = GenerateMethodName(method),
- parameters_declaration = interopParameters,
- body = bodySb.ToString().TrimEnd('\r', '\n')
- };
- }
-
- /// <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 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");
- }
- }
|