// Adapted mfkl and jeremyVignelles work on libvlcsharp for marshalling va_list arguments // For more information see the dotnet issue: https://github.com/dotnet/runtime/issues/9316 // Example of va_list interop: https://github.com/jeremyVignelles/va-list-interop-demo using System; using System.Runtime.InteropServices; namespace Raylib_cs { internal readonly struct Native { internal const string Msvcrt = "msvcrt"; internal const string Libc = "libc"; internal const string LibSystem = "libSystem"; [DllImport(LibSystem, EntryPoint = "vasprintf", CallingConvention = CallingConvention.Cdecl)] public static extern int vasprintf_apple(ref IntPtr buffer, IntPtr format, IntPtr args); [DllImport(Libc, EntryPoint = "vsprintf", CallingConvention = CallingConvention.Cdecl)] public static extern int vsprintf_linux(IntPtr buffer, IntPtr format, IntPtr args); [DllImport(Msvcrt, EntryPoint = "vsprintf", CallingConvention = CallingConvention.Cdecl)] public static extern int vsprintf_windows(IntPtr buffer, IntPtr format, IntPtr args); [DllImport(Libc, EntryPoint = "vsnprintf", CallingConvention = CallingConvention.Cdecl)] public static extern int vsnprintf_linux(IntPtr buffer, UIntPtr size, IntPtr format, IntPtr args); [DllImport(Msvcrt, EntryPoint = "vsnprintf", CallingConvention = CallingConvention.Cdecl)] public static extern int vsnprintf_windows(IntPtr buffer, UIntPtr size, IntPtr format, IntPtr args); } [StructLayout(LayoutKind.Sequential, Pack = 4)] struct VaListLinuxX64 { uint gpOffset; uint fpOffset; IntPtr overflowArgArea; IntPtr regSaveArea; } /// /// Logging workaround for formatting strings from native code /// public static unsafe class Logging { [UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] public static unsafe void LogConsole(int msgType, sbyte* text, sbyte* args) { var message = GetLogMessage(new IntPtr(text), new IntPtr(args)); Console.WriteLine(message); } public static string GetLogMessage(IntPtr format, IntPtr args) { if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { return AppleLogCallback(format, args); } // Special marshalling is needed on Linux desktop 64 bits. if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && IntPtr.Size == 8) { return LinuxX64LogCallback(format, args); } var byteLength = vsnprintf(IntPtr.Zero, UIntPtr.Zero, format, args) + 1; if (byteLength <= 1) { return string.Empty; } var buffer = Marshal.AllocHGlobal(byteLength); vsprintf(buffer, format, args); string result = Marshal.PtrToStringUTF8(buffer); Marshal.FreeHGlobal(buffer); return result; } static string AppleLogCallback(IntPtr format, IntPtr args) { IntPtr buffer = IntPtr.Zero; try { var count = Native.vasprintf_apple(ref buffer, format, args); if (count == -1) { return string.Empty; } return Marshal.PtrToStringUTF8(buffer) ?? string.Empty; } finally { Marshal.FreeHGlobal(buffer); } } static string LinuxX64LogCallback(IntPtr format, IntPtr args) { // The args pointer cannot be reused between two calls. We need to make a copy of the underlying structure. var listStructure = Marshal.PtrToStructure(args); IntPtr listPointer = IntPtr.Zero; int byteLength = 0; string result = ""; // Get length of args listPointer = Marshal.AllocHGlobal(Marshal.SizeOf(listStructure)); Marshal.StructureToPtr(listStructure, listPointer, false); byteLength = Native.vsnprintf_linux(IntPtr.Zero, UIntPtr.Zero, format, listPointer) + 1; // Allocate buffer for result Marshal.StructureToPtr(listStructure, listPointer, false); IntPtr utf8Buffer = IntPtr.Zero; utf8Buffer = Marshal.AllocHGlobal(byteLength); // Print result into buffer Native.vsprintf_linux(utf8Buffer, format, listPointer); result = Marshal.PtrToStringUTF8(utf8Buffer); Marshal.FreeHGlobal(listPointer); Marshal.FreeHGlobal(utf8Buffer); return result; } // https://github.com/dotnet/runtime/issues/51052 static int vsnprintf(IntPtr buffer, UIntPtr size, IntPtr format, IntPtr args) { var os = Environment.OSVersion; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return Native.vsnprintf_windows(buffer, size, format, args); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { return Native.vsnprintf_linux(buffer, size, format, args); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("ANDROID"))) { return Native.vsprintf_linux(buffer, format, args); } return -1; } // yhttps://github.com/dotnet/runtime/issues/51052 static int vsprintf(IntPtr buffer, IntPtr format, IntPtr args) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return Native.vsprintf_windows(buffer, format, args); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { return Native.vsprintf_linux(buffer, format, args); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("ANDROID"))) { return Native.vsprintf_linux(buffer, format, args); } return -1; } } }