- Notifications
You must be signed in to change notification settings - Fork750
Support ByRef arguments in event handlers#1364
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Reflection.Emit; | ||
using System.Text; | ||
namespace Python.Runtime | ||
{ | ||
@@ -11,23 +13,20 @@ namespace Python.Runtime | ||
/// </summary> | ||
internal class DelegateManager | ||
{ | ||
private readonly Dictionary<Type,Type> cache = new Dictionary<Type, Type>(); | ||
private readonly Type basetype = typeof(Dispatcher); | ||
private readonly Type arrayType = typeof(object[]); | ||
private readonly Type voidtype = typeof(void); | ||
private readonly Type typetype = typeof(Type); | ||
private readonly Type ptrtype = typeof(IntPtr); | ||
private readonly CodeGenerator codeGenerator = new CodeGenerator(); | ||
private readonly ConstructorInfo arrayCtor; | ||
private readonly MethodInfo dispatch; | ||
public DelegateManager() | ||
{ | ||
arrayCtor = arrayType.GetConstructor(new[] { typeof(int) }); | ||
dispatch = basetype.GetMethod("Dispatch"); | ||
} | ||
/// <summary> | ||
@@ -58,10 +57,9 @@ private Type GetDispatcher(Type dtype) | ||
// unique signatures rather than delegate types, since multiple | ||
// delegate types with the same sig could use the same dispatcher. | ||
if (cache.TryGetValue(dtype, out Type item)) | ||
{ | ||
return item; | ||
} | ||
string name = $"__{dtype.FullName}Dispatcher"; | ||
@@ -103,34 +101,77 @@ private Type GetDispatcher(Type dtype) | ||
MethodBuilder mb = tb.DefineMethod("Invoke", MethodAttributes.Public, method.ReturnType, signature); | ||
il = mb.GetILGenerator(); | ||
// loc_0 = new object[pi.Length] | ||
il.DeclareLocal(arrayType); | ||
il.Emit(OpCodes.Ldc_I4, pi.Length); | ||
il.Emit(OpCodes.Newobj, arrayCtor); | ||
il.Emit(OpCodes.Stloc_0); | ||
bool anyByRef = false; | ||
for (var c = 0; c < signature.Length; c++) | ||
{ | ||
Type t = signature[c]; | ||
il.Emit(OpCodes.Ldloc_0); | ||
il.Emit(OpCodes.Ldc_I4, c); | ||
il.Emit(OpCodes.Ldarg_S, (byte)(c + 1)); | ||
if (t.IsByRef) | ||
{ | ||
// The argument is a pointer. We must dereference the pointer to get the value or object it points to. | ||
t = t.GetElementType(); | ||
if (t.IsValueType) | ||
{ | ||
il.Emit(OpCodes.Ldobj, t); | ||
} | ||
else | ||
{ | ||
il.Emit(OpCodes.Ldind_Ref); | ||
} | ||
anyByRef = true; | ||
} | ||
if (t.IsValueType) | ||
{ | ||
il.Emit(OpCodes.Box, t); | ||
} | ||
// args[c] = arg | ||
il.Emit(OpCodes.Stelem_Ref); | ||
} | ||
il.Emit(OpCodes.Ldarg_0); | ||
il.Emit(OpCodes.Ldloc_0); | ||
il.Emit(OpCodes.Call, dispatch); | ||
if (anyByRef) | ||
{ | ||
// Dispatch() will have modified elements of the args list that correspond to out parameters. | ||
for (var c = 0; c < signature.Length; c++) | ||
{ | ||
Type t = signature[c]; | ||
if (t.IsByRef) | ||
{ | ||
t = t.GetElementType(); | ||
// *arg = args[c] | ||
il.Emit(OpCodes.Ldarg_S, (byte)(c + 1)); | ||
il.Emit(OpCodes.Ldloc_0); | ||
il.Emit(OpCodes.Ldc_I4, c); | ||
il.Emit(OpCodes.Ldelem_Ref); | ||
if (t.IsValueType) | ||
{ | ||
il.Emit(OpCodes.Unbox_Any, t); | ||
il.Emit(OpCodes.Stobj, t); | ||
} | ||
else | ||
{ | ||
il.Emit(OpCodes.Stind_Ref); | ||
} | ||
} | ||
} | ||
} | ||
if (method.ReturnType == voidtype) | ||
{ | ||
il.Emit(OpCodes.Pop); | ||
@@ -218,7 +259,7 @@ public void Dispose() | ||
GC.SuppressFinalize(this); | ||
} | ||
public object Dispatch(object[] args) | ||
{ | ||
IntPtr gs = PythonEngine.AcquireLock(); | ||
object ob; | ||
@@ -235,7 +276,7 @@ public object Dispatch(ArrayList args) | ||
return ob; | ||
} | ||
private object TrueDispatch(object[] args) | ||
{ | ||
MethodInfo method = dtype.GetMethod("Invoke"); | ||
ParameterInfo[] pi = method.GetParameters(); | ||
@@ -259,20 +300,108 @@ public object TrueDispatch(ArrayList args) | ||
throw e; | ||
} | ||
try | ||
{ | ||
int byRefCount = pi.Count(parameterInfo => parameterInfo.ParameterType.IsByRef); | ||
if (byRefCount > 0) | ||
{ | ||
// By symmetry with MethodBinder.Invoke, when there are out | ||
// parameters we expect to receive a tuple containing | ||
// the result, if any, followed by the out parameters. If there is only | ||
// one out parameter and the return type of the method is void, | ||
// we instead receive the out parameter as the result from Python. | ||
bool isVoid = rtype == typeof(void); | ||
int tupleSize = byRefCount + (isVoid ? 0 : 1); | ||
if (isVoid && byRefCount == 1) | ||
{ | ||
// The return type is void and there is a single out parameter. | ||
for (int i = 0; i < pi.Length; i++) | ||
{ | ||
Type t = pi[i].ParameterType; | ||
if (t.IsByRef) | ||
{ | ||
if (!Converter.ToManaged(op, t, out object newArg, true)) | ||
{ | ||
Exceptions.RaiseTypeError($"The Python function did not return {t.GetElementType()} (the out parameter type)"); | ||
throw new PythonException(); | ||
} | ||
args[i] = newArg; | ||
break; | ||
} | ||
} | ||
return null; | ||
} | ||
else if (Runtime.PyTuple_Check(op) && Runtime.PyTuple_Size(op) == tupleSize) | ||
{ | ||
int index = isVoid ? 0 : 1; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. NIT: retValueIndex | ||
for (int i = 0; i < pi.Length; i++) | ||
{ | ||
Type t = pi[i].ParameterType; | ||
if (t.IsByRef) | ||
{ | ||
IntPtr item = Runtime.PyTuple_GetItem(op, index++); | ||
if (!Converter.ToManaged(item, t, out object newArg, true)) | ||
{ | ||
Exceptions.RaiseTypeError($"The Python function returned a tuple where element {i} was not {t.GetElementType()} (the out parameter type)"); | ||
throw new PythonException(); | ||
Comment on lines +346 to +347 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. This is my only concern which I am not sure about. I think this should be a .NET exception class. What do you think? | ||
} | ||
args[i] = newArg; | ||
} | ||
} | ||
if (isVoid) | ||
{ | ||
return null; | ||
} | ||
IntPtr item0 = Runtime.PyTuple_GetItem(op, 0); | ||
if (!Converter.ToManaged(item0, rtype, out object result0, true)) | ||
{ | ||
Exceptions.RaiseTypeError($"The Python function returned a tuple where element 0 was not {rtype} (the return type)"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. NIT: "was not" -> "is not convertible to" | ||
throw new PythonException(); | ||
} | ||
return result0; | ||
} | ||
else | ||
{ | ||
string tpName = Runtime.PyObject_GetTypeName(op); | ||
if (Runtime.PyTuple_Check(op)) | ||
{ | ||
tpName += $" of size {Runtime.PyTuple_Size(op)}"; | ||
} | ||
StringBuilder sb = new StringBuilder(); | ||
if (!isVoid) sb.Append(rtype.FullName); | ||
for (int i = 0; i < pi.Length; i++) | ||
{ | ||
Type t = pi[i].ParameterType; | ||
if (t.IsByRef) | ||
{ | ||
if (sb.Length > 0) sb.Append(","); | ||
sb.Append(t.GetElementType().FullName); | ||
} | ||
} | ||
string returnValueString = isVoid ? "" : "the return value and "; | ||
Exceptions.RaiseTypeError($"Expected a tuple ({sb}) of {returnValueString}the values for out and ref parameters, got {tpName}."); | ||
throw new PythonException(); | ||
} | ||
} | ||
if (rtype == typeof(void)) | ||
{ | ||
return null; | ||
} | ||
object result; | ||
if (!Converter.ToManaged(op, rtype, out result, true)) | ||
{ | ||
throw new PythonException(); | ||
} | ||
return result; | ||
} | ||
finally | ||
{ | ||
Runtime.XDecref(op); | ||
} | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading.Please reload this page.