From fd0c086e3d317a05e3b8460ea6c01fa2d13576cb Mon Sep 17 00:00:00 2001 From: Stefanos A Date: Tue, 17 Dec 2013 11:39:47 +0100 Subject: [PATCH] Marshal strings as UTF8 (affects #18) Starting with OpenGL 4.2, strings passed to GL.ShaderSource are allowed to contain multi-byte characters in comments (issue #18). This patch modifies the marshaling code to use UTF8.GetBytes in order to marshal strings, instead of Marshal.StringToHGlobalAnsi(). --- Source/Generator.Rewrite/Program.cs | 8 ++-- Source/OpenTK/BindingsBase.cs | 64 ++++++++++++++++++++++++----- 2 files changed, 58 insertions(+), 14 deletions(-) diff --git a/Source/Generator.Rewrite/Program.cs b/Source/Generator.Rewrite/Program.cs index b6d5189e..47531f09 100644 --- a/Source/Generator.Rewrite/Program.cs +++ b/Source/Generator.Rewrite/Program.cs @@ -371,10 +371,10 @@ namespace OpenTK.Rewrite static void EmitStringParameter(MethodDefinition wrapper, TypeReference p, MethodBody body, ILProcessor il) { // string marshaling: - // IntPtr ptr = Marshal.StringToHGlobalAnsi(str); + // IntPtr ptr = MarshalStringToPtr(str); // try { calli } // finally { Marshal.FreeHGlobal(ptr); } - var marshal_str_to_ptr = wrapper.Module.Import(TypeMarshal.Methods.First(m => m.Name == "StringToHGlobalAnsi")); + var marshal_str_to_ptr = wrapper.Module.Import(TypeBindingsBase.Methods.First(m => m.Name == "MarshalStringToPtr")); // IntPtr ptr; var variable_name = p.Name + "_string_ptr"; @@ -396,9 +396,9 @@ namespace OpenTK.Rewrite var p = wrapper.Parameters[i].ParameterType; if (p.Name == "String" && !p.IsArray) { - var free = wrapper.Module.Import(TypeMarshal.Methods.First(m => m.Name == "FreeHGlobal")); + var free = wrapper.Module.Import(TypeBindingsBase.Methods.First(m => m.Name == "FreeStringPtr")); - // Marshal.FreeHGlobal(ptr) + // FreeStringPtr(ptr) var variable_name = p.Name + "_string_ptr"; var v = body.Variables.First(m => m.Name == variable_name); il.Emit(OpCodes.Ldloc, v.Index); diff --git a/Source/OpenTK/BindingsBase.cs b/Source/OpenTK/BindingsBase.cs index 36d421fe..d62d553e 100644 --- a/Source/OpenTK/BindingsBase.cs +++ b/Source/OpenTK/BindingsBase.cs @@ -145,7 +145,57 @@ namespace OpenTK } /// - /// Marshals a string array to unmanaged memory by calling + /// Marshal a System.String to unmanaged memory. + /// The resulting string is encoded in UTF-8 and must be freed + /// with FreeStringPtr. + /// + /// The System.String to marshal. + /// + /// An unmanaged pointer containing the marshalled string. + /// This pointer must be freed with FreeStringPtr + /// + protected static IntPtr MarshalStringToPtr(string str) + { + if (String.IsNullOrEmpty(str)) + { + return IntPtr.Zero; + } + + // Allocate a buffer big enough to hold the marshalled string. + // We use GetMaxByteCount() as it is faster than GetByteCount(). + // The downside is that it may allocate up to 3x more memory than + // strictly necessary. + int max_count = Encoding.UTF8.GetMaxByteCount(str.Length) + 1; + IntPtr ptr = Marshal.AllocHGlobal(max_count); + if (ptr == IntPtr.Zero) + { + throw new OutOfMemoryException(); + } + + // Pin the managed string and convert it to UTF-8 using + // the pointer overload of System.Encoding.UTF8.GetBytes(). + unsafe + { + fixed (char* pstr = str) + { + int actual_count = Encoding.UTF8.GetBytes(pstr, str.Length, (byte*)ptr, max_count); + Marshal.WriteByte(ptr, actual_count, 0); // Append '\0' at the end of the string + return ptr; + } + } + } + + /// + /// Frees a marshalled string that allocated by MarshalStringToPtr. + /// + /// An unmanaged pointer allocated with MarshalStringToPtr + protected static void FreeStringPtr(IntPtr ptr) + { + Marshal.FreeHGlobal(ptr); + } + + /// + /// Marshals a System.String array to unmanaged memory by calling /// Marshal.AllocHGlobal for each element. /// /// An unmanaged pointer to an array of null-terminated strings @@ -163,12 +213,7 @@ namespace OpenTK for (int i = 0; i < str_array.Length; i++) { - IntPtr str = Marshal.StringToHGlobalAnsi(str_array[i]); - if (str == IntPtr.Zero) - { - throw new OutOfMemoryException(); - } - + IntPtr str = MarshalStringToPtr(str_array[i]); Marshal.WriteIntPtr(ptr, i * IntPtr.Size, str); } } @@ -176,10 +221,9 @@ namespace OpenTK } /// - /// Frees a string array that has previously been - /// marshalled by MarshalStringArrayToPtr. + /// Frees a marshalled string that allocated by MarshalStringArrayToPtr. /// - /// An unmanaged pointer allocated by MarshalStringArrayToPtr + /// An unmanaged pointer allocated with MarshalStringArrayToPtr /// The length of the string array. protected static void FreeStringArrayPtr(IntPtr ptr, int length) {