Răsfoiți Sursa

Interop: implement JNA bindings for Java, enhancing type handling and callback support

Marcin Ziąbek 2 luni în urmă
părinte
comite
51d616373d

+ 189 - 23
Source/QuestPDF.Interop.Generators/QuestPDF.Interop.Generators/Languages/JavaLanguageProvider.cs

@@ -5,7 +5,7 @@ using QuestPDF.Interop.Generators.Models;
 namespace QuestPDF.Interop.Generators.Languages;
 
 /// <summary>
-/// Language provider for Java code generation using JNA.
+/// Language provider for Java code generation using JNA (Java Native Access).
 /// </summary>
 public class JavaLanguageProvider : ILanguageProvider
 {
@@ -16,6 +16,9 @@ public class JavaLanguageProvider : ILanguageProvider
     public string EnumTemplateName => "Java.Enum";
     public string ColorsTemplateName => "Java.Colors";
 
+    // Store current class name for TypeParameter resolution
+    private string _currentClassName;
+
     public string ConvertName(string csharpName, NameContext context)
     {
         return context switch
@@ -50,7 +53,7 @@ public class JavaLanguageProvider : ILanguageProvider
             InteropTypeKind.Enum => type.ShortName,
             InteropTypeKind.Class => type.ShortName,
             InteropTypeKind.Interface => type.ShortName.TrimStart('I'),
-            InteropTypeKind.TypeParameter => "Object",
+            InteropTypeKind.TypeParameter => _currentClassName ?? "Object",
             InteropTypeKind.Color => "Color",
             InteropTypeKind.Action => FormatFunctionalInterface(type, isFunc: false),
             InteropTypeKind.Func => FormatFunctionalInterface(type, isFunc: true),
@@ -59,9 +62,45 @@ public class JavaLanguageProvider : ILanguageProvider
         };
     }
 
+    /// <summary>
+    /// Gets the JNA-compatible type for native library interface declarations.
+    /// </summary>
+    public string GetJnaType(InteropTypeModel type)
+    {
+        return type.Kind switch
+        {
+            InteropTypeKind.Void => "void",
+            InteropTypeKind.Boolean => "byte", // JNA uses byte for C bool
+            InteropTypeKind.Integer => GetJnaIntegerType(type),
+            InteropTypeKind.Float => GetJnaFloatType(type),
+            InteropTypeKind.String => "WString", // UTF-16 for .NET compatibility
+            InteropTypeKind.Enum => "int",
+            InteropTypeKind.Class => "Pointer",
+            InteropTypeKind.Interface => "Pointer",
+            InteropTypeKind.TypeParameter => "Pointer",
+            InteropTypeKind.Color => "int", // ARGB packed as int
+            InteropTypeKind.Action => "Pointer", // Callback pointer
+            InteropTypeKind.Func => "Pointer", // Callback pointer
+            InteropTypeKind.Unknown => "Pointer",
+            _ => "Pointer"
+        };
+    }
+
     private string GetJavaIntegerType(InteropTypeModel type)
     {
-        var typeName = type.OriginalTypeName;
+        var typeName = type.OriginalTypeName ?? "";
+        if (typeName.Contains("Int64") || typeName.Contains("UInt64"))
+            return "long";
+        if (typeName.Contains("Int16") || typeName.Contains("UInt16"))
+            return "short";
+        if (typeName.Contains("Byte") || typeName.Contains("SByte"))
+            return "byte";
+        return "int";
+    }
+
+    private string GetJnaIntegerType(InteropTypeModel type)
+    {
+        var typeName = type.OriginalTypeName ?? "";
         if (typeName.Contains("Int64") || typeName.Contains("UInt64"))
             return "long";
         if (typeName.Contains("Int16") || typeName.Contains("UInt16"))
@@ -73,7 +112,12 @@ public class JavaLanguageProvider : ILanguageProvider
 
     private string GetJavaFloatType(InteropTypeModel type)
     {
-        return type.OriginalTypeName.Contains("Double") ? "double" : "float";
+        return type.OriginalTypeName?.Contains("Double") == true ? "double" : "float";
+    }
+
+    private string GetJnaFloatType(InteropTypeModel type)
+    {
+        return type.OriginalTypeName?.Contains("Double") == true ? "double" : "float";
     }
 
     private string FormatFunctionalInterface(InteropTypeModel type, bool isFunc)
@@ -84,28 +128,41 @@ public class JavaLanguageProvider : ILanguageProvider
         if (isFunc)
         {
             if (type.TypeArguments.Length == 2)
-                return $"Function<{GetTargetTypeForGeneric(type.TypeArguments[0])}, {GetTargetTypeForGeneric(type.TypeArguments[1])}>";
+                return $"Function<{GetTargetTypeBoxed(type.TypeArguments[0])}, {GetTargetTypeBoxed(type.TypeArguments[1])}>";
             return "Function<?, ?>";
         }
         else
         {
             if (type.TypeArguments.Length == 1)
-                return $"Consumer<{GetTargetTypeForGeneric(type.TypeArguments[0])}>";
+                return $"Consumer<{GetTargetTypeBoxed(type.TypeArguments[0])}>";
             return "Consumer<?>";
         }
     }
 
-    private string GetTargetTypeForGeneric(InteropTypeModel type)
+    /// <summary>
+    /// Gets the boxed type name for use in generics (Integer instead of int).
+    /// </summary>
+    private string GetTargetTypeBoxed(InteropTypeModel type)
     {
-        if (type.Kind == InteropTypeKind.Interface)
-            return type.ShortName.TrimStart('I');
-        return type.ShortName;
+        var targetType = GetTargetType(type);
+        return targetType switch
+        {
+            "int" => "Integer",
+            "long" => "Long",
+            "short" => "Short",
+            "byte" => "Byte",
+            "float" => "Float",
+            "double" => "Double",
+            "boolean" => "Boolean",
+            "void" => "Void",
+            _ => targetType
+        };
     }
 
     public string FormatDefaultValue(InteropParameterModel parameter)
     {
-        // Java doesn't support default parameter values in the same way
-        // We would need to use method overloading
+        // Java doesn't support default parameter values directly
+        // Overloaded methods are used instead
         return null;
     }
 
@@ -114,43 +171,152 @@ public class JavaLanguageProvider : ILanguageProvider
         return parameter.Type.Kind switch
         {
             InteropTypeKind.Enum => $"{variableName}.getValue()",
-            InteropTypeKind.String => variableName,
+            InteropTypeKind.String => $"new WString({variableName})",
+            InteropTypeKind.Boolean => $"({variableName} ? (byte)1 : (byte)0)",
+            InteropTypeKind.Color => $"{variableName}.getHex()",
             InteropTypeKind.Action => $"{variableName}Callback",
             InteropTypeKind.Func => $"{variableName}Callback",
+            InteropTypeKind.Class => $"{variableName}.getPointer()",
+            InteropTypeKind.Interface => $"{variableName}.getPointer()",
             _ => variableName
         };
     }
 
     public object BuildClassTemplateModel(InteropClassModel classModel)
     {
+        _currentClassName = classModel.GeneratedClassName;
+
+        // Collect all unique callback interfaces needed
+        var callbackInterfaces = classModel.Methods
+            .SelectMany(m => m.Callbacks)
+            .Select(c => new
+            {
+                InterfaceName = $"{c.ArgumentTypeName}Callback",
+                ArgumentTypeName = c.ArgumentTypeName
+            })
+            .DistinctBy(c => c.InterfaceName)
+            .ToList();
+
         return new
         {
             ClassName = classModel.GeneratedClassName,
-            Methods = classModel.Methods.Select(BuildMethodTemplateModel).ToList()
+            Methods = classModel.Methods.Select(BuildMethodTemplateModel).ToList(),
+            CallbackInterfaces = callbackInterfaces,
+            CallbackTypedefs = classModel.CallbackTypedefs,
+            Headers = classModel.CHeaderSignatures
         };
     }
 
     private object BuildMethodTemplateModel(InteropMethodModel method)
     {
+        // Build Java method parameter string
+        var javaParams = method.Parameters.Select(p =>
+        {
+            var name = ConvertName(p.OriginalName, NameContext.Parameter);
+            var type = GetTargetType(p.Type);
+            return new { Name = name, Type = type, OriginalType = p.Type };
+        }).ToList();
+
+        var javaParametersStr = string.Join(", ", javaParams.Select(p => $"{p.Type} {p.Name}"));
+
+        // Build native call arguments
+        var nativeArgs = new List<string> { "this.ptr" };
+        nativeArgs.AddRange(method.Parameters.Select(p =>
+            GetInteropValue(p, ConvertName(p.OriginalName, NameContext.Parameter))));
+        var nativeCallArgsStr = string.Join(", ", nativeArgs);
+
+        // Build JNA interface method signature
+        var jnaSignature = BuildJnaSignature(method);
+
+        // Determine return type
+        var javaReturnType = GetReturnTypeName(method);
+        var jnaReturnType = method.ReturnType.Kind == InteropTypeKind.Void
+            ? "void"
+            : GetJnaType(method.ReturnType);
+        var returnClassName = javaReturnType != "void" ? javaReturnType : null;
+
+        // Extract unique ID from native entry point
+        var uniqueId = ExtractUniqueId(method.NativeEntryPoint);
+
+        // Use disambiguated name for overloads
+        var methodName = method.IsOverload
+            ? ConvertName(method.DisambiguatedName, NameContext.Method)
+            : ConvertName(method.OriginalName, NameContext.Method);
+
         return new
         {
-            JavaMethodName = ConvertName(method.OriginalName, NameContext.Method),
+            JavaMethodName = methodName,
             NativeMethodName = method.NativeEntryPoint,
-            Parameters = method.Parameters.Select(p => new
-            {
-                Name = ConvertName(p.OriginalName, NameContext.Parameter),
-                Type = GetTargetType(p.Type)
-            }).ToList(),
-            ReturnType = GetTargetType(method.ReturnType),
+            JavaParameters = javaParametersStr,
+            JavaReturnType = javaReturnType,
+            JnaReturnType = jnaReturnType,
+            JnaSignature = jnaSignature,
+            ReturnClassName = returnClassName,
+            NativeCallArgs = nativeCallArgsStr,
+            UniqueId = uniqueId,
             DeprecationMessage = method.DeprecationMessage,
             Callbacks = method.Callbacks.Select(c => new
             {
                 ParameterName = ConvertName(c.ParameterName, NameContext.Parameter),
-                ArgumentTypeName = c.ArgumentTypeName
-            }).ToList()
+                ArgumentTypeName = c.ArgumentTypeName,
+                CallbackInterfaceName = $"{c.ArgumentTypeName}Callback"
+            }).ToList(),
+            IsOverload = method.IsOverload,
+            OriginalName = ConvertName(method.OriginalName, NameContext.Method),
+            Parameters = javaParams.Select(p => new
+            {
+                p.Name,
+                p.Type,
+                JnaType = GetJnaType(p.OriginalType),
+                IsCallback = p.OriginalType.Kind is InteropTypeKind.Action or InteropTypeKind.Func,
+                IsEnum = p.OriginalType.Kind == InteropTypeKind.Enum,
+                IsString = p.OriginalType.Kind == InteropTypeKind.String,
+                IsBoolean = p.OriginalType.Kind == InteropTypeKind.Boolean,
+                IsColor = p.OriginalType.Kind == InteropTypeKind.Color,
+                IsObject = p.OriginalType.Kind is InteropTypeKind.Class or InteropTypeKind.Interface
+            }).ToList(),
+            ReturnsPointer = method.ReturnType.Kind is InteropTypeKind.Class or InteropTypeKind.Interface or InteropTypeKind.TypeParameter,
+            HasCallbacks = method.Callbacks.Any()
         };
     }
 
+    private static string ExtractUniqueId(string nativeEntryPoint)
+    {
+        var lastUnderscore = nativeEntryPoint.LastIndexOf("__");
+        if (lastUnderscore >= 0 && lastUnderscore < nativeEntryPoint.Length - 2)
+        {
+            return nativeEntryPoint.Substring(lastUnderscore + 2);
+        }
+        return nativeEntryPoint.GetHashCode().ToString("x8");
+    }
+
+    private string GetReturnTypeName(InteropMethodModel method)
+    {
+        if (method.ReturnType.Kind == InteropTypeKind.Void)
+            return "void";
+
+        if (method.ReturnType.Kind == InteropTypeKind.TypeParameter)
+            return _currentClassName;
+
+        return GetTargetType(method.ReturnType);
+    }
+
+    private string BuildJnaSignature(InteropMethodModel method)
+    {
+        var returnType = GetJnaType(method.ReturnType);
+        var methodName = method.NativeEntryPoint;
+
+        // Build parameters: first is always Pointer target
+        var parameters = new List<string> { "Pointer target" };
+        parameters.AddRange(method.Parameters.Select(p =>
+        {
+            var jnaType = GetJnaType(p.Type);
+            return $"{jnaType} {p.OriginalName}";
+        }));
+
+        return $"{returnType} {methodName}({string.Join(", ", parameters)})";
+    }
+
     public object BuildEnumTemplateModel(object enums)
     {
         return new { Enums = enums };

+ 315 - 1
Source/QuestPDF.Interop.Generators/QuestPDF.Interop.Generators/Templates/Java/Main.liquid

@@ -1,5 +1,319 @@
 // AUTO-GENERATED on {{ generationDateTime }}
+// QuestPDF Java Bindings using JNA (Java Native Access)
+
+package com.questpdf.interop;
+
+import com.sun.jna.*;
+import com.sun.jna.ptr.*;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+// ============================================================================
+// Native Buffer Structure
+// ============================================================================
+
+/**
+ * Native buffer structure for receiving byte arrays from native code.
+ */
+class NativeBuffer extends Structure {
+    public Pointer data;
+    public NativeLong length;
+
+    public NativeBuffer() {
+        super();
+    }
+
+    public NativeBuffer(Pointer p) {
+        super(p);
+        read();
+    }
+
+    @Override
+    protected List<String> getFieldOrder() {
+        return List.of("data", "length");
+    }
+
+    public static class ByValue extends NativeBuffer implements Structure.ByValue {}
+}
+
+// ============================================================================
+// Native Library Interface
+// ============================================================================
+
+/**
+ * JNA interface for the QuestPDF native library.
+ */
+interface QuestPDFNative extends Library {
+    // Test function
+    int questpdf_sum(int a, int b);
+
+    // Document lifecycle
+    Pointer questpdf_document_create(Callback cb);
+    NativeBuffer.ByValue questpdf_document_generate_pdf(Pointer document);
+    void questpdf_document_destroy(Pointer document);
+
+    // Document container
+    void questpdf_document_container_add_page(Pointer container, Callback cb);
+
+    // Page descriptor
+    void questpdf_page_set_margin(Pointer page, int margin);
+    Pointer questpdf_page_set_content(Pointer page);
+
+    // Container
+    Pointer questpdf_container_background(Pointer container, int color);
+
+    // Memory management
+    void questpdf_free_bytes(Pointer ptr);
+}
+
+// ============================================================================
+// Library Loading
+// ============================================================================
+
+/**
+ * QuestPDF library manager for loading and accessing the native library.
+ */
+public final class QuestPDF {
+    private static QuestPDFNative lib = null;
+    private static final Object lock = new Object();
+
+    private QuestPDF() {}
+
+    /**
+     * Gets the native library instance, loading it if necessary.
+     */
+    public static QuestPDFNative getLib() {
+        if (lib == null) {
+            synchronized (lock) {
+                if (lib == null) {
+                    throw new IllegalStateException("QuestPDF library not initialized. Call initialize() first.");
+                }
+            }
+        }
+        return lib;
+    }
+
+    /**
+     * Initializes the QuestPDF library with automatic platform detection.
+     */
+    public static void initialize() {
+        initialize(null);
+    }
+
+    /**
+     * Initializes the QuestPDF library with a specific library path.
+     * @param libraryPath Path to the native library, or null for auto-detection.
+     */
+    public static void initialize(String libraryPath) {
+        synchronized (lock) {
+            if (lib != null) {
+                return; // Already initialized
+            }
+
+            String libPath = libraryPath != null ? libraryPath : getDefaultLibraryPath();
+            lib = Native.load(libPath, QuestPDFNative.class);
+        }
+    }
+
+    private static String getDefaultLibraryPath() {
+        String os = System.getProperty("os.name").toLowerCase();
+        String libName;
+
+        if (os.contains("win")) {
+            libName = "QuestPDF.Interop.dll";
+        } else if (os.contains("mac")) {
+            libName = "QuestPDF.Interop.dylib";
+        } else {
+            libName = "libQuestPDF.Interop.so";
+        }
+
+        // Try multiple locations
+        String[] searchPaths = {
+            libName,
+            "./" + libName,
+            "../" + libName,
+            System.getProperty("user.dir") + "/" + libName
+        };
+
+        for (String path : searchPaths) {
+            File file = new File(path);
+            if (file.exists()) {
+                return file.getAbsolutePath();
+            }
+        }
+
+        // Fall back to library name only, let JNA resolve it
+        return libName;
+    }
+}
+
+// ============================================================================
+// Callback Interfaces
+// ============================================================================
+
+/**
+ * Callback interface for document container configuration.
+ */
+interface DocumentContainerCallback extends Callback {
+    void invoke(Pointer containerPtr);
+}
+
+/**
+ * Callback interface for page configuration.
+ */
+interface PageCallback extends Callback {
+    void invoke(Pointer pagePtr);
+}
+
+/**
+ * Generic callback interface for void pointer arguments.
+ */
+interface VoidPtrCallback extends Callback {
+    void invoke(Pointer ptr);
+}
+
+// ============================================================================
+// Core Classes
+// ============================================================================
+
+/**
+ * Represents a page in a QuestPDF document.
+ */
+class Page {
+    private final Pointer ptr;
+
+    Page(Pointer ptr) {
+        this.ptr = ptr;
+    }
+
+    public Pointer getPointer() {
+        return ptr;
+    }
+
+    /**
+     * Sets the margin for this page.
+     */
+    public void margin(int margin) {
+        QuestPDF.getLib().questpdf_page_set_margin(this.ptr, margin);
+    }
+
+    /**
+     * Gets the content container for this page.
+     */
+    public Container content() {
+        Pointer result = QuestPDF.getLib().questpdf_page_set_content(this.ptr);
+        return new Container(result);
+    }
+}
+
+/**
+ * Represents a document container for adding pages.
+ */
+class DocumentContainer {
+    private final Pointer ptr;
+    private final List<Callback> callbacks = new ArrayList<>();
+
+    DocumentContainer(Pointer ptr) {
+        this.ptr = ptr;
+    }
+
+    public Pointer getPointer() {
+        return ptr;
+    }
+
+    /**
+     * Adds a page to the document.
+     */
+    public void page(Consumer<Page> configurator) {
+        PageCallback callback = pagePtr -> {
+            Page page = new Page(pagePtr);
+            configurator.accept(page);
+        };
+
+        // Keep callback reference to prevent GC
+        callbacks.add(callback);
+        QuestPDF.getLib().questpdf_document_container_add_page(this.ptr, callback);
+    }
+}
+
+/**
+ * Represents a QuestPDF document.
+ */
+class Document {
+    private Pointer ptr = null;
+    private DocumentContainerCallback containerCallback = null;
+
+    private Document() {}
+
+    /**
+     * Creates a new document with the given configuration.
+     */
+    public static Document create(Consumer<DocumentContainer> configurator) {
+        Document doc = new Document();
+
+        doc.containerCallback = containerPtr -> {
+            DocumentContainer container = new DocumentContainer(containerPtr);
+            configurator.accept(container);
+        };
+
+        doc.ptr = QuestPDF.getLib().questpdf_document_create(doc.containerCallback);
+        return doc;
+    }
+
+    /**
+     * Generates the PDF as a byte array.
+     */
+    public byte[] generatePdf() {
+        if (ptr == null) {
+            throw new IllegalStateException("Document not created. Use Document.create() first.");
+        }
+
+        NativeBuffer.ByValue buffer = QuestPDF.getLib().questpdf_document_generate_pdf(this.ptr);
+        try {
+            int length = buffer.length.intValue();
+            byte[] data = buffer.data.getByteArray(0, length);
+            return data;
+        } finally {
+            QuestPDF.getLib().questpdf_free_bytes(buffer.data);
+        }
+    }
+
+    /**
+     * Saves the PDF to a file.
+     */
+    public Document saveToFile(String filename) throws IOException {
+        byte[] pdfBytes = generatePdf();
+        try (FileOutputStream fos = new FileOutputStream(filename)) {
+            fos.write(pdfBytes);
+        }
+        return this;
+    }
+
+    /**
+     * Destroys the document and frees native resources.
+     */
+    public void destroy() {
+        if (ptr != null) {
+            QuestPDF.getLib().questpdf_document_destroy(ptr);
+            ptr = null;
+            containerCallback = null;
+        }
+    }
+}
+
+// ============================================================================
+// Generated Code
+// ============================================================================
 
 {% for fragment in fragments %}
 {{ fragment }}
-{% endfor %}
+{% endfor %}

+ 114 - 2
Source/QuestPDF.Interop.Generators/QuestPDF.Interop.Generators/Templates/Java/Object.liquid

@@ -1,3 +1,115 @@
-// Java Object class: {{ className }}
-// TODO: Implement Java JNA bindings for {{ className }}
+// ============================================================================
+// {{ className }} - Native Function Interface
+// ============================================================================
 
+/**
+ * JNA interface for {{ className }} native methods.
+ */
+interface {{ className }}Native extends Library {
+{% for method in methods %}
+    {{ method.jnaReturnType }} {{ method.nativeMethodName }}(Pointer target{% for param in method.parameters %}, {{ param.jnaType }} {{ param.name }}{% endfor %});
+{% endfor %}
+}
+
+// ============================================================================
+// {{ className }} Callback Interfaces
+// ============================================================================
+
+{% for callback in callbackInterfaces %}
+/**
+ * Callback interface for {{ callback.argumentTypeName }} configuration.
+ */
+interface {{ callback.interfaceName }} extends Callback {
+    void invoke(Pointer ptr);
+}
+
+{% endfor %}
+// ============================================================================
+// {{ className }} Class
+// ============================================================================
+
+/**
+ * {{ className }} wrapper for the native QuestPDF {{ className }}.
+ */
+class {{ className }} {
+    private static {{ className }}Native nativeLib = null;
+    private static final Object initLock = new Object();
+
+    private final Pointer ptr;
+    private final List<Callback> callbacks = new ArrayList<>();
+
+    {{ className }}(Pointer ptr) {
+        this.ptr = ptr;
+        ensureInitialized();
+    }
+
+    public Pointer getPointer() {
+        return ptr;
+    }
+
+    private static void ensureInitialized() {
+        if (nativeLib == null) {
+            synchronized (initLock) {
+                if (nativeLib == null) {
+                    nativeLib = Native.load(getLibraryPath(), {{ className }}Native.class);
+                }
+            }
+        }
+    }
+
+    private static String getLibraryPath() {
+        String os = System.getProperty("os.name").toLowerCase();
+        String libName;
+
+        if (os.contains("win")) {
+            libName = "QuestPDF.Interop.dll";
+        } else if (os.contains("mac")) {
+            libName = "QuestPDF.Interop.dylib";
+        } else {
+            libName = "libQuestPDF.Interop.so";
+        }
+
+        String[] searchPaths = {
+            libName,
+            "./" + libName,
+            "../" + libName,
+            System.getProperty("user.dir") + "/" + libName
+        };
+
+        for (String path : searchPaths) {
+            File file = new File(path);
+            if (file.exists()) {
+                return file.getAbsolutePath();
+            }
+        }
+
+        return libName;
+    }
+
+{% for method in methods %}
+    {% if method.deprecationMessage %}/**
+     * @deprecated {{ method.deprecationMessage }}
+     */
+    @Deprecated
+    {% endif %}public {{ method.javaReturnType }} {{ method.originalName }}({{ method.javaParameters }}) {
+{% for callback in method.callbacks %}
+        // Create callback for {{ callback.parameterName }}
+        {{ callback.callbackInterfaceName }} {{ callback.parameterName }}Callback = argPtr -> {
+            {{ callback.argumentTypeName }} obj = new {{ callback.argumentTypeName }}(argPtr);
+            {{ callback.parameterName }}.accept(obj);
+        };
+        this.callbacks.add({{ callback.parameterName }}Callback);
+
+{% endfor %}
+{% if method.javaReturnType == 'void' %}
+        nativeLib.{{ method.nativeMethodName }}({{ method.nativeCallArgs }});
+{% elsif method.returnsPointer %}
+        Pointer result = nativeLib.{{ method.nativeMethodName }}({{ method.nativeCallArgs }});
+        return new {{ method.returnClassName }}(result);
+{% else %}
+        return nativeLib.{{ method.nativeMethodName }}({{ method.nativeCallArgs }});
+{% endif %}
+    }
+
+{% endfor %}
+}