/// <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.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 %}
{% 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);
}
{% if has_bytes_param %}
private static byte[] MarshalBytesFromIntPtr(IntPtr ptr) {
if (ptr == IntPtr.Zero) return Array.Empty<byte>();
var json = global::System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr) ?? "[]";
return JsonSerializer.Deserialize<byte[]>(json) ?? Array.Empty<byte>();
}
{% endif %}
{{ 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();
}
}
}