using System.Runtime.InteropServices; // Shared "walk a native array of owned-struct entries, convert to managed records, free the // native list" pattern — identical shape for vc_device_list/vc_channel_list/vc_user_list/ // vc_stream_summary_list (all core-allocated, caller-freed; see voicecat.h's doc comments on // each). The matching vc_free_*_list call happens INSIDE each ToManaged here, immediately // after the conversion, so callers never need to remember to free anything themselves. namespace VoiceCat.Interop; internal static class Marshaling { public static List ToManaged(ref VcDeviceListNative native) { var result = new List((int)native.Count); int size = Marshal.SizeOf(); for (nuint i = 0; i < native.Count; i++) { var raw = Marshal.PtrToStructure(native.Items + (int)i * size); result.Add(new DeviceInfo( Marshal.PtrToStringUTF8(raw.Id) ?? string.Empty, Marshal.PtrToStringUTF8(raw.Name) ?? string.Empty, raw.IsDefault != 0)); } NativeMethods.vc_free_device_list(ref native); return result; } public static List ToManaged(ref VcChannelListNative native) { var result = new List((int)native.Count); int size = Marshal.SizeOf(); for (nuint i = 0; i < native.Count; i++) { var raw = Marshal.PtrToStructure(native.Items + (int)i * size); result.Add(new ChannelInfo( raw.Id, raw.ParentId, Marshal.PtrToStringUTF8(raw.Name) ?? string.Empty, raw.PasswordProtected != 0, raw.MaxUsers)); } NativeMethods.vc_free_channel_list(ref native); return result; } public static List ToManaged(ref VcUserListNative native) { var result = new List((int)native.Count); int size = Marshal.SizeOf(); for (nuint i = 0; i < native.Count; i++) { var raw = Marshal.PtrToStructure(native.Items + (int)i * size); result.Add(new UserInfo( raw.Id, Marshal.PtrToStringUTF8(raw.Nickname) ?? string.Empty, raw.IsGuest != 0, raw.ChannelId)); } NativeMethods.vc_free_user_list(ref native); return result; } public static List ToManaged(ref VcStreamSummaryListNative native) { var result = new List((int)native.Count); int size = Marshal.SizeOf(); for (nuint i = 0; i < native.Count; i++) { var raw = Marshal.PtrToStructure(native.Items + (int)i * size); result.Add(new StreamSummary( raw.StreamId, raw.Kind, Marshal.PtrToStringUTF8(raw.Label) ?? string.Empty)); } NativeMethods.vc_free_stream_summary_list(ref native); return result; } public static AudioConfigInfo ToManaged(in VcAudioConfigNative native) => new( native.Codec, native.Mode != 0, native.SampleRate, native.BitrateBps, native.FrameMs, native.Application, native.Fec != 0, native.ExpectedPacketLoss, native.Dtx != 0, native.Complexity); }