Use "out String" parameters instead of "StringBuilder"

This commit is contained in:
Fraser Waters 2017-07-23 11:54:40 +01:00
parent c296ebc0f5
commit d547a81cab
5 changed files with 155 additions and 67 deletions

View file

@ -358,7 +358,7 @@ namespace Bind
} }
else if (enum_override.Value == "StringBuilder") else if (enum_override.Value == "StringBuilder")
{ {
type.QualifiedType = "StringBuilder"; throw new NotSupportedException("StringBuilder enum overrides are no longer supported");
} }
else else
{ {
@ -1237,13 +1237,10 @@ namespace Bind
var p = wrapper.Parameters[i]; var p = wrapper.Parameters[i];
if ((p.WrapperType & WrapperTypes.StringParameter) != 0) if ((p.WrapperType & WrapperTypes.StringParameter) != 0)
{ {
p.QualifiedType = "String";
if (p.Flow == FlowDirection.Out) if (p.Flow == FlowDirection.Out)
{ {
p.QualifiedType = "StringBuilder"; p.Reference = true;
}
else
{
p.QualifiedType = "String";
} }
} }

View file

@ -0,0 +1,10 @@

namespace OpenTK.Rewrite
{
internal class CountAttribute
{
public int Count;
public string Parameter;
public string Computed;
}
}

View file

@ -53,6 +53,7 @@
<Reference Include="System.Core" /> <Reference Include="System.Core" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="CountAttribute.cs" />
<Compile Include="GeneratedVariableIdentifier.cs" /> <Compile Include="GeneratedVariableIdentifier.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />

View file

@ -51,7 +51,6 @@ namespace OpenTK.Rewrite
private static AssemblyDefinition mscorlib; private static AssemblyDefinition mscorlib;
private static TypeDefinition TypeMarshal; private static TypeDefinition TypeMarshal;
private static TypeDefinition TypeStringBuilder;
private static TypeDefinition TypeVoid; private static TypeDefinition TypeVoid;
private static TypeDefinition TypeIntPtr; private static TypeDefinition TypeIntPtr;
private static TypeDefinition TypeInt32; private static TypeDefinition TypeInt32;
@ -123,7 +122,6 @@ namespace OpenTK.Rewrite
return; return;
} }
TypeMarshal = mscorlib.MainModule.GetType("System.Runtime.InteropServices.Marshal"); TypeMarshal = mscorlib.MainModule.GetType("System.Runtime.InteropServices.Marshal");
TypeStringBuilder = mscorlib.MainModule.GetType("System.Text.StringBuilder");
TypeVoid = mscorlib.MainModule.GetType("System.Void"); TypeVoid = mscorlib.MainModule.GetType("System.Void");
TypeIntPtr = mscorlib.MainModule.GetType("System.IntPtr"); TypeIntPtr = mscorlib.MainModule.GetType("System.IntPtr");
TypeInt32 = mscorlib.MainModule.GetType("System.Int32"); TypeInt32 = mscorlib.MainModule.GetType("System.Int32");
@ -547,9 +545,9 @@ namespace OpenTK.Rewrite
{ {
foreach (var p in wrapper.Parameters) 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") if (!p.ParameterType.IsArray && p.ParameterType.Name == "String")
@ -577,44 +575,73 @@ namespace OpenTK.Rewrite
body.Variables.Contains(v.Definition)); 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; var p = parameter.ParameterType;
// void GetShaderInfoLog(..., StringBuilder foo) // void GetShaderInfoLog(..., out String foo)
// IntPtr foo_sb_ptr; // IntPtr foo_string_ptr;
// try { // try {
// foo_sb_ptr = Marshal.AllocHGlobal(sb.Capacity + 1); // foo_string_ptr = Marshal.AllocHGlobal(count + 1);
// glGetShaderInfoLog(..., foo_sb_ptr); // glGetShaderInfoLog(..., foo_string_ptr);
// MarshalPtrToStringBuilder(foo_sb_ptr, sb); // foo = MarshalPtrToString(foo_string_ptr);
// } // }
// finally { // finally {
// Marshal.FreeHGlobal(sb_ptr); // Marshal.FreeHGlobal(foo_string_ptr);
// } // }
// Make sure we have imported StringBuilder::Capacity and Marshal::AllocHGlobal // Make sure we have imported Marshal::AllocHGlobal
var sb_get_capacity = method.Module.ImportReference(TypeStringBuilder.Methods.First(m => m.Name == "get_Capacity"));
var alloc_hglobal = method.Module.ImportReference(TypeMarshal.Methods.First(m => m.Name == "AllocHGlobal")); var alloc_hglobal = method.Module.ImportReference(TypeMarshal.Methods.First(m => m.Name == "AllocHGlobal"));
// IntPtr ptr; // IntPtr ptr;
var variableDefinition = new VariableDefinition(TypeIntPtr); var variableDefinition = new VariableDefinition(TypeIntPtr);
body.Variables.Add(variableDefinition); 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(sb.Capacity + 1); // ptr = Marshal.AllocHGlobal(count + 1);
il.Emit(OpCodes.Callvirt, sb_get_capacity); 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));
}
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.Call, alloc_hglobal);
il.Emit(OpCodes.Stloc, stringBuilderPtrIndex); il.Emit(OpCodes.Stloc, stringPtrIndex);
il.Emit(OpCodes.Ldloc, stringBuilderPtrIndex); il.Emit(OpCodes.Ldloc, stringPtrIndex);
// We'll emit the try-finally block in the epilogue implementation, // We'll emit the try-finally block in the epilogue implementation,
// because we haven't yet emitted all necessary instructions here. // 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) ParameterDefinition parameter, MethodBody body, ILProcessor il, GeneratedVariableIdentifier generatedPtrVar)
{ {
if (generatedPtrVar == null) if (generatedPtrVar == null)
@ -622,38 +649,36 @@ namespace OpenTK.Rewrite
throw new ArgumentNullException(nameof(generatedPtrVar)); throw new ArgumentNullException(nameof(generatedPtrVar));
} }
var p = parameter.ParameterType; // void GetShaderInfoLog(..., out String foo)
if (p.Name == "StringBuilder") // IntPtr foo_string_ptr;
{ // try {
// void GetShaderInfoLog(..., StringBuilder foo) // foo_string_ptr = Marshal.AllocHGlobal(count + 1);
// try { // glGetShaderInfoLog(..., foo_string_ptr);
// foo_sb_ptr = Marshal.AllocHGlobal(sb.Capacity + 1); -- already emitted // foo = MarshalPtrToString(foo_string_ptr);
// glGetShaderInfoLog(..., foo_sb_ptr); -- already emitted // }
// MarshalPtrToStringBuilder(foo_sb_ptr, foo); // finally {
// } // Marshal.FreeHGlobal(foo_string_ptr);
// finally { // }
// Marshal.FreeHGlobal(foo_sb_ptr);
// }
// Make sure we have imported BindingsBase::MasrhalPtrToStringBuilder and Marshal::FreeHGlobal // Make sure we have imported BindingsBase::MasrhalPtrToString and Marshal::FreeHGlobal
var ptr_to_sb = wrapper.Module.ImportReference(TypeBindingsBase.Methods.First(m => m.Name == "MarshalPtrToStringBuilder")); 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")); var free_hglobal = wrapper.Module.ImportReference(TypeMarshal.Methods.First(m => m.Name == "FreeHGlobal"));
var block = new ExceptionHandler(ExceptionHandlerType.Finally); var block = new ExceptionHandler(ExceptionHandlerType.Finally);
block.TryStart = body.Instructions[0]; block.TryStart = body.Instructions[0];
il.Emit(OpCodes.Ldloc, generatedPtrVar.Definition.Index); il.Emit(OpCodes.Ldloc, generatedPtrVar.Definition.Index);
il.Emit(OpCodes.Ldarg, parameter.Index); il.Emit(OpCodes.Ldarg, parameter.Index);
il.Emit(OpCodes.Call, ptr_to_sb); il.Emit(OpCodes.Call, ptr_to_str);
il.Emit(OpCodes.Starg, parameter.Index);
block.TryEnd = body.Instructions.Last(); block.TryEnd = body.Instructions.Last();
block.HandlerStart = body.Instructions.Last(); block.HandlerStart = body.Instructions.Last();
il.Emit(OpCodes.Ldloc, generatedPtrVar.Definition.Index); il.Emit(OpCodes.Ldloc, generatedPtrVar.Definition.Index);
il.Emit(OpCodes.Call, free_hglobal); il.Emit(OpCodes.Call, free_hglobal);
block.HandlerEnd = body.Instructions.Last(); block.HandlerEnd = body.Instructions.Last();
}
} }
private static GeneratedVariableIdentifier EmitStringParameter(MethodDefinition wrapper, ParameterDefinition parameter, MethodBody body, private static GeneratedVariableIdentifier EmitStringParameter(MethodDefinition wrapper, ParameterDefinition parameter, MethodBody body,
@ -817,6 +842,64 @@ namespace OpenTK.Rewrite
return generatedVariables; 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) private static List<GeneratedVariableIdentifier> EmitParameters(MethodDefinition method, MethodDefinition native, MethodBody body, ILProcessor il)
{ {
List<GeneratedVariableIdentifier> generatedVariables = new List<GeneratedVariableIdentifier>(); List<GeneratedVariableIdentifier> generatedVariables = new List<GeneratedVariableIdentifier>();
@ -832,9 +915,9 @@ namespace OpenTK.Rewrite
// We need to convert the loaded argument to IntPtr. // We need to convert the loaded argument to IntPtr.
il.Emit(OpCodes.Conv_I); 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) else if (p.Name == "String" && !p.IsArray)
{ {

View file

@ -76,27 +76,24 @@ namespace OpenTK
/// This method supports OpenTK and is not intended to be called by user code. /// 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="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 string MarshalPtrToString(IntPtr ptr)
protected static void MarshalPtrToStringBuilder(IntPtr ptr, StringBuilder sb)
{ {
if (ptr == IntPtr.Zero) if (ptr == IntPtr.Zero)
{ {
throw new ArgumentException("ptr"); throw new ArgumentException("ptr");
} }
if (sb == null)
{
throw new ArgumentNullException("sb");
}
sb.Length = 0; unsafe
for (int i = 0; ; i++)
{ {
byte b = Marshal.ReadByte(ptr, i); sbyte* str = (sbyte*)ptr.ToPointer();
if (b == 0) int len = 0;
while(*str != 0)
{ {
return; ++len;
++str;
} }
sb.Append((char)b);
return new string(str, 0, len, null);
} }
} }