34 lines
1.6 KiB
C#
34 lines
1.6 KiB
C#
|
|
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);
|
||
|
|
}
|
||
|
|
}
|