Implemented marshaling for string arrays
authorStefanos A <stapostol@gmail.com>
Wed, 4 Dec 2013 19:33:19 +0000 (20:33 +0100)
committerStefanos A <stapostol@gmail.com>
Wed, 4 Dec 2013 19:33:19 +0000 (20:33 +0100)
This allows functions such as GL.ShaderSource to run on Mono without
crashing.

Source/OpenTK.Rewrite/Program.cs
Source/OpenTK/BindingsBase.cs

index bae0091..c5e9e06 100644 (file)
@@ -47,6 +47,7 @@ namespace OpenTK.Rewrite
         // mscorlib types
         static AssemblyDefinition mscorlib;
         static TypeDefinition TypeMarshal;
+        static TypeDefinition TypeStringArray;
         static TypeDefinition TypeStringBuilder;
         static TypeDefinition TypeVoid;
         static TypeDefinition TypeIntPtr;
@@ -111,6 +112,7 @@ namespace OpenTK.Rewrite
                     return;
                 }
                 TypeMarshal = mscorlib.MainModule.GetType("System.Runtime.InteropServices.Marshal");
+                TypeStringArray = mscorlib.MainModule.GetType("System.String").MakeArrayType().Resolve();
                 TypeStringBuilder = mscorlib.MainModule.GetType("System.Text.StringBuilder");
                 TypeVoid = mscorlib.MainModule.GetType("System.Void");
                 TypeIntPtr = mscorlib.MainModule.GetType("System.IntPtr");
@@ -328,7 +330,7 @@ namespace OpenTK.Rewrite
                     // try {
                     //  foo_sb_ptr = Marshal.AllocHGlobal(sb.Capacity + 1); -- already emitted
                     //  glGetShaderInfoLog(..., foo_sb_ptr); -- already emitted
-                    //  MarshalStringBuilder(foo_sb_ptr, foo);
+                    //  MarshalPtrToStringBuilder(foo_sb_ptr, foo);
                     // }
                     // finally {
                     //  Marshal.FreeHGlobal(foo_sb_ptr);
@@ -358,6 +360,52 @@ namespace OpenTK.Rewrite
             }
         }
 
+        static void EmitStringArrayParameter(MethodDefinition wrapper, TypeReference p, MethodBody body, ILProcessor il)
+        {
+            // string[] masrhaling:
+            // IntPtr ptr = MarshalStringArrayToPtr(strings);
+            // try {
+            //  calli
+            // }
+            // finally {
+            //  UnmarshalStringArray(ptr);
+            // }
+            var marshal_str_array_to_ptr = wrapper.Module.Import(TypeBindingsBase.Methods.First(m => m.Name == "MarshalStringArrayToPtr"));
+
+            // IntPtr ptr;
+            var variable_name = p.Name + " _string_array_ptr";
+            body.Variables.Add(new VariableDefinition(variable_name, TypeIntPtr));
+            int index = body.Variables.Count - 1;
+
+            // ptr = MarshalStringArrayToPtr(strings);
+            il.Emit(OpCodes.Call, marshal_str_array_to_ptr);
+            il.Emit(OpCodes.Stloc, index);
+            il.Emit(OpCodes.Ldloc, index);
+
+            // The finally block will be emitted in the function epilogue
+        }
+
+        static void EmitStringArrayEpilogue(MethodDefinition wrapper, MethodDefinition native, MethodBody body, ILProcessor il)
+        {
+            for (int i = 0; i < wrapper.Parameters.Count; i++)
+            {
+                var p = wrapper.Parameters[i].ParameterType;
+                if (p.Name == "String" && p.IsArray)
+                {
+                    var free = wrapper.Module.Import(TypeBindingsBase.Methods.First(m => m.Name == "FreeStringArrayPtr"));
+                    var get_length = wrapper.Module.Import(TypeStringArray.Methods.First(m => m.Name == "get_Length"));
+
+                    // FreeStringArrayPtr(string_array_ptr, string_array.Length)
+                    var variable_name = p.Name + "_string_array_ptr";
+                    var v = body.Variables.First(m => m.Name == variable_name);
+                    il.Emit(OpCodes.Ldloc, v.Index);
+                    il.Emit(OpCodes.Ldarg, i);
+                    il.Emit(OpCodes.Callvirt, get_length);
+                    il.Emit(OpCodes.Call, free);
+                }
+            }
+        }
+
         private static void EmitConvenienceWrapper(MethodDefinition wrapper,
             MethodDefinition native, int difference, MethodBody body, ILProcessor il)
         {
@@ -491,8 +539,7 @@ namespace OpenTK.Rewrite
                     }
                     else
                     {
-                        // String[] requires special marshalling.
-                        // Let the runtime handle this for now.
+                        EmitStringArrayParameter(method, p, body, il);
                     }
                 }
             }
index 1f2372e..36d421f 100644 (file)
@@ -119,13 +119,13 @@ namespace OpenTK
         /// unique objects, but all instances of ES10.GL should return the same object.</remarks>
         protected abstract object SyncRoot { get; }
 
-               /// <summary>
+        /// <summary>
         /// Marshals a pointer to a null-terminated byte array to the specified <c>StringBuilder</c>.
         /// This method supports OpenTK and is not intended to be called by user code.
-               /// </summary>
+       /// </summary>
         /// <param name="ptr">A pointer to a null-terminated byte array.</param>
         /// <param name="sb">The StringBuilder to receive the contents of the pointer.</param>
-               protected static void MarshalPtrToStringBuilder(IntPtr ptr, StringBuilder sb)
+        protected static void MarshalPtrToStringBuilder(IntPtr ptr, StringBuilder sb)
         {
             if (ptr == IntPtr.Zero)
                 throw new ArgumentException("ptr");
@@ -144,6 +144,52 @@ namespace OpenTK
             }
         }
 
+        /// <summary>
+        /// Marshals a string array to unmanaged memory by calling
+        /// Marshal.AllocHGlobal for each element.
+        /// </summary>
+        /// <returns>An unmanaged pointer to an array of null-terminated strings</returns>
+        /// <param name="str_array">The string array to marshal.</param>
+        protected static IntPtr MarshalStringArrayToPtr(string[] str_array)
+        {
+            IntPtr ptr = IntPtr.Zero;
+            if (str_array != null && str_array.Length != 0)
+            {
+                ptr = Marshal.AllocHGlobal(str_array.Length * IntPtr.Size);
+                if (ptr == IntPtr.Zero)
+                {
+                    throw new OutOfMemoryException();
+                }
+
+                for (int i = 0; i < str_array.Length; i++)
+                {
+                    IntPtr str = Marshal.StringToHGlobalAnsi(str_array[i]);
+                    if (str == IntPtr.Zero)
+                    {
+                        throw new OutOfMemoryException();
+                    }
+
+                    Marshal.WriteIntPtr(ptr, i * IntPtr.Size, str);
+                }
+            }
+            return ptr;
+        }
+
+        /// <summary>
+        /// Frees a string array that has previously been
+        /// marshalled by <c>MarshalStringArrayToPtr</c>.
+        /// </summary>
+        /// <param name="ptr">An unmanaged pointer allocated by <c>MarshalStringArrayToPtr</c></param>
+        /// <param name="length">The length of the string array.</param>
+        protected static void FreeStringArrayPtr(IntPtr ptr, int length)
+        {
+            for (int i = 0; i < length; i++)
+            {
+                Marshal.FreeHGlobal(Marshal.ReadIntPtr(ptr, length * IntPtr.Size));
+            }
+            Marshal.FreeHGlobal(ptr);
+        }
+
         #endregion
 
         #region Internal Members