Use "out String" parameters instead of "StringBuilder"
authorFraser Waters <frassle@gmail.com>
Sun, 23 Jul 2017 10:54:40 +0000 (11:54 +0100)
committerFraser Waters <frassle@gmail.com>
Fri, 28 Jul 2017 05:16:52 +0000 (06:16 +0100)
src/Generator.Bind/FuncProcessor.cs
src/Generator.Rewrite/CountAttribute.cs [new file with mode: 0644]
src/Generator.Rewrite/Generator.Rewrite.csproj
src/Generator.Rewrite/Program.cs
src/OpenTK/BindingsBase.cs

index 6a0a664..213f8f6 100644 (file)
@@ -358,7 +358,7 @@ namespace Bind
                 }
                 else if (enum_override.Value == "StringBuilder")
                 {
-                    type.QualifiedType = "StringBuilder";
+                    throw new NotSupportedException("StringBuilder enum overrides are no longer supported");
                 }
                 else
                 {
@@ -1237,13 +1237,10 @@ namespace Bind
                     var p = wrapper.Parameters[i];
                     if ((p.WrapperType & WrapperTypes.StringParameter) != 0)
                     {
+                        p.QualifiedType = "String";
                         if (p.Flow == FlowDirection.Out)
                         {
-                            p.QualifiedType = "StringBuilder";
-                        }
-                        else
-                        {
-                            p.QualifiedType = "String";
+                            p.Reference = true;
                         }
                     }
 
diff --git a/src/Generator.Rewrite/CountAttribute.cs b/src/Generator.Rewrite/CountAttribute.cs
new file mode 100644 (file)
index 0000000..bed2d5f
--- /dev/null
@@ -0,0 +1,10 @@
+
+namespace OpenTK.Rewrite
+{
+    internal class CountAttribute
+    {
+        public int Count;
+        public string Parameter;
+        public string Computed;
+    }
+}
index fb9b52f..f1d115a 100644 (file)
@@ -53,6 +53,7 @@
     <Reference Include="System.Core" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="CountAttribute.cs" />
     <Compile Include="GeneratedVariableIdentifier.cs" />
     <Compile Include="Program.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
index 70b4162..480485a 100644 (file)
@@ -51,7 +51,6 @@ namespace OpenTK.Rewrite
         private static AssemblyDefinition mscorlib;
 
         private static TypeDefinition TypeMarshal;
-        private static TypeDefinition TypeStringBuilder;
         private static TypeDefinition TypeVoid;
         private static TypeDefinition TypeIntPtr;
         private static TypeDefinition TypeInt32;
@@ -123,7 +122,6 @@ namespace OpenTK.Rewrite
                             return;
                         }
                         TypeMarshal = mscorlib.MainModule.GetType("System.Runtime.InteropServices.Marshal");
-                        TypeStringBuilder = mscorlib.MainModule.GetType("System.Text.StringBuilder");
                         TypeVoid = mscorlib.MainModule.GetType("System.Void");
                         TypeIntPtr = mscorlib.MainModule.GetType("System.IntPtr");
                         TypeInt32 = mscorlib.MainModule.GetType("System.Int32");
@@ -547,9 +545,9 @@ namespace OpenTK.Rewrite
         {
             foreach (var p in wrapper.Parameters)
             {
-                if (p.ParameterType.Name == "StringBuilder")
+                if (!p.ParameterType.IsArray && p.ParameterType.Name == "String" && p.IsOut)
                 {
-                    EmitStringBuilderEpilogue(wrapper, native, p, body, il, GetGeneratedVariable(generatedVariables, p.Name + "_sb_ptr", body));
+                    EmitStringOutEpilogue(wrapper, native, p, body, il, GetGeneratedVariable(generatedVariables, p.Name + "_string_ptr", body));
                 }
 
                 if (!p.ParameterType.IsArray && p.ParameterType.Name == "String")
@@ -577,44 +575,73 @@ namespace OpenTK.Rewrite
                                                            body.Variables.Contains(v.Definition));
         }
 
-        private static GeneratedVariableIdentifier EmitStringBuilderParameter(MethodDefinition method, ParameterDefinition parameter, MethodBody body, ILProcessor il)
+        private static GeneratedVariableIdentifier EmitStringOutParameter(MethodDefinition method, ParameterDefinition parameter, MethodBody body, ILProcessor il)
         {
             var p = parameter.ParameterType;
 
-            // void GetShaderInfoLog(..., StringBuilder foo)
-            // IntPtr foo_sb_ptr;
+            // void GetShaderInfoLog(..., out String foo)
+            // IntPtr foo_string_ptr;
             // try {
-            //  foo_sb_ptr = Marshal.AllocHGlobal(sb.Capacity + 1);
-            //  glGetShaderInfoLog(..., foo_sb_ptr);
-            //  MarshalPtrToStringBuilder(foo_sb_ptr, sb);
+            //  foo_string_ptr = Marshal.AllocHGlobal(count + 1);
+            //  glGetShaderInfoLog(..., foo_string_ptr);
+            //  foo = MarshalPtrToString(foo_string_ptr);
             // }
             // finally {
-            //  Marshal.FreeHGlobal(sb_ptr);
+            //  Marshal.FreeHGlobal(foo_string_ptr);
             // }
-            // Make sure we have imported StringBuilder::Capacity and Marshal::AllocHGlobal
-            var sb_get_capacity = method.Module.ImportReference(TypeStringBuilder.Methods.First(m => m.Name == "get_Capacity"));
+            // Make sure we have imported Marshal::AllocHGlobal
             var alloc_hglobal = method.Module.ImportReference(TypeMarshal.Methods.First(m => m.Name == "AllocHGlobal"));
 
             // IntPtr ptr;
             var variableDefinition = new VariableDefinition(TypeIntPtr);
             body.Variables.Add(variableDefinition);
-            int stringBuilderPtrIndex = body.Variables.Count - 1;
+            int stringPtrIndex = body.Variables.Count - 1;
 
-            GeneratedVariableIdentifier stringBuilderPtrVar = new GeneratedVariableIdentifier(body, variableDefinition, parameter.Name + "_sb_ptr");
+            GeneratedVariableIdentifier stringPtrVar = new GeneratedVariableIdentifier(body, variableDefinition, parameter.Name + "_string_ptr");
+
+            // ptr = Marshal.AllocHGlobal(count + 1);
+            var count = GetCountAttribute(parameter);
+            if (count == null)
+            {
+                // We need a count attribute so we know what size to make the
+                // string buffer. Currently every string out parameter has a
+                // count attribute but this check is in place to make things
+                // clearer if this case is ever hit.
+                throw new InvalidOperationException(string.Format("{0}({1}) doesn't have a count attribute", method.Name, parameter.Name));
+            }
 
-            // ptr = Marshal.AllocHGlobal(sb.Capacity + 1);
-            il.Emit(OpCodes.Callvirt, sb_get_capacity);
+            if (count.Count != 0)
+            {
+                // Fixed size
+                il.Emit(OpCodes.Ldc_I4, count.Count);
+            }
+            else if (count.Parameter != null)
+            {
+                // Parameter sized
+                var countVariable = EmitCountVariable(method, body, il, count.Parameter);
+                il.Emit(OpCodes.Ldloc, countVariable.Index);
+            }
+            else if (count.Computed != null)
+            {
+                // We don't handle count.Computed. Computed counts are hard and
+                // require manual reading of the specification for each one.
+                // But currently no string out parameters require it.
+                throw new NotSupportedException(string.Format("{0}({1}) requires a computed count: {2}", method.Name, parameter.Name, count.Computed));
+            }
+
+            il.Emit(OpCodes.Ldc_I4, 1);
+            il.Emit(OpCodes.Add);
             il.Emit(OpCodes.Call, alloc_hglobal);
-            il.Emit(OpCodes.Stloc, stringBuilderPtrIndex);
-            il.Emit(OpCodes.Ldloc, stringBuilderPtrIndex);
+            il.Emit(OpCodes.Stloc, stringPtrIndex);
+            il.Emit(OpCodes.Ldloc, stringPtrIndex);
 
             // We'll emit the try-finally block in the epilogue implementation,
             // because we haven't yet emitted all necessary instructions here.
 
-            return stringBuilderPtrVar;
+            return stringPtrVar;
         }
 
-        private static void EmitStringBuilderEpilogue(MethodDefinition wrapper, MethodDefinition native,
+        private static void EmitStringOutEpilogue(MethodDefinition wrapper, MethodDefinition native,
             ParameterDefinition parameter, MethodBody body, ILProcessor il, GeneratedVariableIdentifier generatedPtrVar)
         {
             if (generatedPtrVar == null)
@@ -622,38 +649,36 @@ namespace OpenTK.Rewrite
                 throw new ArgumentNullException(nameof(generatedPtrVar));
             }
 
-            var p = parameter.ParameterType;
-            if (p.Name == "StringBuilder")
-            {
-                // void GetShaderInfoLog(..., StringBuilder foo)
-                // try {
-                //  foo_sb_ptr = Marshal.AllocHGlobal(sb.Capacity + 1); -- already emitted
-                //  glGetShaderInfoLog(..., foo_sb_ptr); -- already emitted
-                //  MarshalPtrToStringBuilder(foo_sb_ptr, foo);
-                // }
-                // finally {
-                //  Marshal.FreeHGlobal(foo_sb_ptr);
-                // }
-
-                // Make sure we have imported BindingsBase::MasrhalPtrToStringBuilder and Marshal::FreeHGlobal
-                var ptr_to_sb = wrapper.Module.ImportReference(TypeBindingsBase.Methods.First(m => m.Name == "MarshalPtrToStringBuilder"));
-                var free_hglobal = wrapper.Module.ImportReference(TypeMarshal.Methods.First(m => m.Name == "FreeHGlobal"));
-
-                var block = new ExceptionHandler(ExceptionHandlerType.Finally);
-                block.TryStart = body.Instructions[0];
-
-                il.Emit(OpCodes.Ldloc, generatedPtrVar.Definition.Index);
-                il.Emit(OpCodes.Ldarg, parameter.Index);
-                il.Emit(OpCodes.Call, ptr_to_sb);
+            // void GetShaderInfoLog(..., out String foo)
+            // IntPtr foo_string_ptr;
+            // try {
+            //  foo_string_ptr = Marshal.AllocHGlobal(count + 1);
+            //  glGetShaderInfoLog(..., foo_string_ptr);
+            //  foo = MarshalPtrToString(foo_string_ptr);
+            // }
+            // finally {
+            //  Marshal.FreeHGlobal(foo_string_ptr);
+            // }
 
-                block.TryEnd = body.Instructions.Last();
-                block.HandlerStart = body.Instructions.Last();
+            // Make sure we have imported BindingsBase::MasrhalPtrToString and Marshal::FreeHGlobal
+            var ptr_to_str = wrapper.Module.ImportReference(TypeBindingsBase.Methods.First(m => m.Name == "MarshalPtrToString"));
+            var free_hglobal = wrapper.Module.ImportReference(TypeMarshal.Methods.First(m => m.Name == "FreeHGlobal"));
 
-                il.Emit(OpCodes.Ldloc, generatedPtrVar.Definition.Index);
-                il.Emit(OpCodes.Call, free_hglobal);
+            var block = new ExceptionHandler(ExceptionHandlerType.Finally);
+            block.TryStart = body.Instructions[0];
 
-                block.HandlerEnd = body.Instructions.Last();
-            }
+            il.Emit(OpCodes.Ldloc, generatedPtrVar.Definition.Index);
+            il.Emit(OpCodes.Ldarg, parameter.Index);
+            il.Emit(OpCodes.Call, ptr_to_str);
+            il.Emit(OpCodes.Starg, parameter.Index);
+
+            block.TryEnd = body.Instructions.Last();
+            block.HandlerStart = body.Instructions.Last();
+
+            il.Emit(OpCodes.Ldloc, generatedPtrVar.Definition.Index);
+            il.Emit(OpCodes.Call, free_hglobal);
+
+            block.HandlerEnd = body.Instructions.Last();
         }
 
         private static GeneratedVariableIdentifier EmitStringParameter(MethodDefinition wrapper, ParameterDefinition parameter, MethodBody body,
@@ -817,6 +842,64 @@ namespace OpenTK.Rewrite
             return generatedVariables;
         }
 
+        private static object GetAttributeField(CustomAttribute attribute, string name)
+        {
+            try
+            {
+                var field = attribute.Fields.First(f => f.Name == name);
+                return field.Argument.Value;
+            }
+            catch (InvalidOperationException)
+            {
+                return null;
+            }
+        }
+
+        private static CountAttribute GetCountAttribute(ParameterDefinition parameter)
+        {
+            var attribute = parameter.CustomAttributes
+                        .FirstOrDefault(a => a.AttributeType.Name == "CountAttribute");
+
+            var count = new CountAttribute();
+            if (attribute != null)
+            {
+                count.Count = (int)(GetAttributeField(attribute, "Count") ?? 0);
+                count.Parameter = (string)(GetAttributeField(attribute, "Parameter"));
+                count.Computed = (string)(GetAttributeField(attribute, "Computed"));
+            }
+            return count;
+        }
+
+
+        private static VariableDefinition EmitCountVariable(MethodDefinition method, MethodBody body, ILProcessor il, string countParameter)
+        {
+            var countVariable = new VariableDefinition(TypeInt32);
+            body.Variables.Add(countVariable);
+
+            // Parameter will either by a simple name or an
+            // expression like "name*5"
+            var parameter = method.Parameters.FirstOrDefault(
+                param => param.Name == countParameter);
+            if (parameter != null)
+            {
+                il.Emit(OpCodes.Ldarg, parameter.Index);
+                il.Emit(OpCodes.Stloc, countVariable.Index);
+            }
+            else
+            {
+                var operands = countParameter.Split('*');
+                parameter = method.Parameters.FirstOrDefault(
+                    param => param.Name == operands[0]);
+                var scale = int.Parse(operands[1]);
+
+                il.Emit(OpCodes.Ldarg, parameter.Index);
+                il.Emit(OpCodes.Ldc_I4, scale);
+                il.Emit(OpCodes.Mul);
+                il.Emit(OpCodes.Stloc, countVariable.Index);
+            }
+            return countVariable;
+        }
+
         private static List<GeneratedVariableIdentifier> EmitParameters(MethodDefinition method, MethodDefinition native, MethodBody body, ILProcessor il)
         {
             List<GeneratedVariableIdentifier> generatedVariables = new List<GeneratedVariableIdentifier>();
@@ -832,9 +915,9 @@ namespace OpenTK.Rewrite
                     // We need to convert the loaded argument to IntPtr.
                     il.Emit(OpCodes.Conv_I);
                 }
-                else if (p.Name == "StringBuilder")
+                else if (p.Name == "String" && !p.IsArray && parameter.IsOut)
                 {
-                    generatedVariables.Add(EmitStringBuilderParameter(method, parameter, body, il));
+                    generatedVariables.Add(EmitStringOutParameter(method, parameter, body, il));
                 }
                 else if (p.Name == "String" && !p.IsArray)
                 {
index 5f22735..cafd309 100644 (file)
@@ -76,27 +76,24 @@ namespace OpenTK
         /// This method supports OpenTK and is not intended to be called by user code.
         /// </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 string MarshalPtrToString(IntPtr ptr)
         {
             if (ptr == IntPtr.Zero)
             {
                 throw new ArgumentException("ptr");
             }
-            if (sb == null)
-            {
-                throw new ArgumentNullException("sb");
-            }
 
-            sb.Length = 0;
-            for (int i = 0; ; i++)
+            unsafe
             {
-                byte b = Marshal.ReadByte(ptr, i);
-                if (b == 0)
+                sbyte* str = (sbyte*)ptr.ToPointer();
+                int len = 0;
+                while(*str != 0)
                 {
-                    return;
+                    ++len;
+                    ++str;
                 }
-                sb.Append((char)b);
+
+                return new string(str, 0, len, null);
             }
         }