Files
voice-cat/clients/windows/VoiceCat.Interop/NativeCallbacks.cs
T

34 lines
1.6 KiB
C#
Raw Normal View History

using System.Runtime.InteropServices;
// [UnmanagedCallersOnly] static methods for vc_callbacks.on_event/on_level — true native
// function pointers, not GC-tracked delegates (avoids the classic P/Invoke pitfall where a
// delegate is collected by the GC sometime after the call that registered it returns; see
// docs/tech-stack.md §2). vc_callbacks.user is a GCHandle-wrapped VoiceCatClient (allocated in
// VoiceCatClient's constructor, freed in Dispose) — these methods must be static, so they
// resolve back to the right client instance via that handle rather than closing over state.
namespace VoiceCat.Interop;
internal static unsafe class NativeCallbacks
{
[UnmanagedCallersOnly]
internal static void OnEvent(IntPtr userContext, VcEventNative* ev)
{
if (userContext == IntPtr.Zero) return;
if (GCHandle.FromIntPtr(userContext).Target is not VoiceCatClient client) return;
// CRITICAL (voicecat.h's vc_event doc comment): ev->Text is owned by the core and
// valid ONLY for the duration of this callback. Convert to a managed string NOW,
// before returning — never store/queue the raw VcEventNative across the callback
// boundary, or Text will be a dangling pointer by the time it's read.
client.EnqueueEvent(VoiceCatEvent.FromNative(*ev));
}
[UnmanagedCallersOnly]
internal static void OnLevel(IntPtr userContext, uint streamId, float rms)
{
if (userContext == IntPtr.Zero) return;
if (GCHandle.FromIntPtr(userContext).Target is not VoiceCatClient client) return;
client.EnqueueLevel(streamId, rms);
}
}