/// <summary>
/// Manages the FFI vtable and delegates for a {{ trait_pascal }} implementation
/// </summary>
public sealed class {{ trait_pascal }}Bridge : IDisposable {
private readonly I{{ trait_pascal }} _impl;
private readonly GCHandle _implHandle;
internal IntPtr _vtable;
private bool _disposed;
private readonly object[] _delegates;
// Vtable slot delegates ({{ num_vtable_fields }})
{% if has_super_trait %}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int NameFn(IntPtr userData, out IntPtr outName);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int VersionFn(IntPtr userData, out IntPtr outVersion);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int InitializeFn(IntPtr userData, out IntPtr outError);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int ShutdownFn(IntPtr userData, out IntPtr outError);
{% endif %}
{% for method in methods %}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
{% if method.is_primitive_return %}
{# Primitive returns: return directly, no out params #}
{% if method.params_empty %}
private delegate {{ method.delegate_return_type }} {{ method.pascal_name }}Fn(IntPtr userData);
{% else %}
private delegate {{ method.delegate_return_type }} {{ method.pascal_name }}Fn(IntPtr userData, {{ method.unmanaged_params }});
{% endif %}
{% else %}
{# Complex returns: use out params #}
{% if method.params_empty %}
{% if is_options_field %}
private delegate int {{ method.pascal_name }}Fn(IntPtr userData, out IntPtr outResult);
{% else %}
private delegate int {{ method.pascal_name }}Fn(IntPtr userData, out IntPtr outResult, out IntPtr outError);
{% endif %}
{% else %}
{% if is_options_field %}
private delegate int {{ method.pascal_name }}Fn(IntPtr userData, {{ method.unmanaged_params }}, out IntPtr outResult);
{% else %}
private delegate int {{ method.pascal_name }}Fn(IntPtr userData, {{ method.unmanaged_params }}, out IntPtr outResult, out IntPtr outError);
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void FreeUserDataFn(IntPtr userData);
public {{ trait_pascal }}Bridge(I{{ trait_pascal }} impl) {
_impl = impl ?? throw new ArgumentNullException(nameof(impl));
_implHandle = GCHandle.Alloc(impl, GCHandleType.Normal);
_vtable = IntPtr.Zero;
_disposed = false;
_delegates = new object[{{ num_vtable_fields }}];
BuildVtable();
}
private void BuildVtable() {
// Allocate unmanaged vtable struct (array of function pointers)
_vtable = global::System.Runtime.InteropServices.Marshal.AllocHGlobal(IntPtr.Size * {{ num_vtable_fields }});
{{ vtable_slots }}
}
private static string ToJsonString<T>(T value) {
return JsonSerializer.Serialize(value);
}
{{ callbacks }}
public void Dispose() {
if (_disposed) return;
_disposed = true;
if (_vtable != IntPtr.Zero) {
global::System.Runtime.InteropServices.Marshal.FreeHGlobal(_vtable);
_vtable = IntPtr.Zero;
}
if (_implHandle.IsAllocated) {
_implHandle.Free();
}
}
/// <summary>Register a {{ trait_pascal }} implementation and return its native handle</summary>
{% if has_super_trait %}
public static IntPtr Register(I{{ trait_pascal }} impl) {
if (impl == null)
throw new ArgumentNullException(nameof(impl));
var name = impl.Name;
{% else %}
public static IntPtr Register(I{{ trait_pascal }} impl, string name) {
if (impl == null)
throw new ArgumentNullException(nameof(impl));
{% endif %}
var bridge = new {{ trait_pascal }}Bridge(impl);
try {
var userDataHandle = GCHandle.Alloc(bridge, GCHandleType.Normal);
var userData = GCHandle.ToIntPtr(userDataHandle);
var vtablePtr = bridge._vtable;
var result = NativeMethods.Register{{ trait_pascal }}(name, vtablePtr, userData, out var outError);
if (result != 0) {
userDataHandle.Free();
bridge.Dispose();
var errorMsg = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(outError) ?? "Unknown error";
global::System.Runtime.InteropServices.Marshal.FreeCoTaskMem(outError);
throw new InvalidOperationException($"Failed to register {name}: {errorMsg}");
}
return userData;
} catch {
bridge.Dispose();
throw;
}
}
}