Thanks to visit codestin.com
Credit goes to github.com

Skip to content

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
- Ability to instantiate new .NET arrays using `Array[T](dim1, dim2, ...)` syntax
- Python operator method will call C# operator method for supported binary and unary operators ([#1324][p1324]).
- Add GetPythonThreadID and Interrupt methods in PythonEngine
- Ability to implement delegates with `ref` and `out` parameters in Python, by returning the modified parameter values in a tuple. ([#1355][i1355])

### Changed
- Drop support for Python 2, 3.4, and 3.5
Expand All @@ -32,6 +33,7 @@ details about the cause of the failure
- floating point values passed from Python are no longer silently truncated
when .NET expects an integer [#1342][i1342]
- More specific error messages for method argument mismatch
- BREAKING: Methods with `ref` or `out` parameters and void return type return a tuple of only the `ref` and `out` parameters.

### Fixed

Expand All @@ -48,8 +50,8 @@ when .NET expects an integer [#1342][i1342]
- Fixed issue when calling PythonException.Format where another exception would be raise for unnormalized exceptions
- Made it possible to call `ToString`, `GetHashCode`, and `GetType` on inteface objects
- Fixed objects returned by enumerating `PyObject` being disposed too soon
- Incorrectly using a non-generic type with type parameters now produces a helpful Python error instead of throwing NullReferenceException
- `import` may now raise errors with more detail than "No module named X"
- Incorrectly using a non-generic type with type parameters now produces a helpful Python error instead of throwing NullReferenceException ([#1325][i1325])
- `import` may now raise errors with more detail than "No module named X"
- Providing an invalid type parameter to a generic type or method produces a helpful Python error

### Removed
Expand Down
10 changes: 5 additions & 5 deletions src/runtime/converter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -338,23 +338,23 @@ internal static bool ToManagedValue(IntPtr value, Type obType,

if (mt != null)
{
if (mt is CLRObject)
if (mt is CLRObject co)
{
object tmp = ((CLRObject)mt).inst;
object tmp = co.inst;
if (obType.IsInstanceOfType(tmp))
{
result = tmp;
return true;
}
if (setError)
{
Exceptions.SetError(Exceptions.TypeError, $"value cannot be converted to {obType}");
string typeString = tmp is null ? "null" : tmp.GetType().ToString();
Exceptions.SetError(Exceptions.TypeError, $"{typeString} value cannot be converted to {obType}");
}
return false;
}
if (mt is ClassBase)
if (mt is ClassBase cb)
{
var cb = (ClassBase)mt;
if (!cb.type.Valid)
{
Exceptions.SetError(Exceptions.TypeError, cb.type.DeletedMessage);
Expand Down
203 changes: 166 additions & 37 deletions src/runtime/delegatemanager.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace Python.Runtime
{
Expand All @@ -11,23 +13,20 @@ namespace Python.Runtime
/// </summary>
internal class DelegateManager
{
private Hashtable cache;
private Type basetype;
private Type listtype;
private Type voidtype;
private Type typetype;
private Type ptrtype;
private CodeGenerator codeGenerator;
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()
{
basetype = typeof(Dispatcher);
listtype = typeof(ArrayList);
voidtype = typeof(void);
typetype = typeof(Type);
ptrtype = typeof(IntPtr);
cache = new Hashtable();
codeGenerator = new CodeGenerator();
arrayCtor = arrayType.GetConstructor(new[] { typeof(int) });
dispatch = basetype.GetMethod("Dispatch");
}

/// <summary>
Expand Down Expand Up @@ -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.

object item = cache[dtype];
if (item != null)
if (cache.TryGetValue(dtype, out Type item))
{
return (Type)item;
return item;
}

string name = $"__{dtype.FullName}Dispatcher";
Expand Down Expand Up @@ -103,34 +101,77 @@ private Type GetDispatcher(Type dtype)

MethodBuilder mb = tb.DefineMethod("Invoke", MethodAttributes.Public, method.ReturnType, signature);

ConstructorInfo ctor = listtype.GetConstructor(Type.EmptyTypes);
MethodInfo dispatch = basetype.GetMethod("Dispatch");
MethodInfo add = listtype.GetMethod("Add");

il = mb.GetILGenerator();
il.DeclareLocal(listtype);
il.Emit(OpCodes.Newobj, ctor);
// 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);
}

il.Emit(OpCodes.Callvirt, add);
il.Emit(OpCodes.Pop);
// 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);
Expand Down Expand Up @@ -218,7 +259,7 @@ public void Dispose()
GC.SuppressFinalize(this);
}

public object Dispatch(ArrayList args)
public object Dispatch(object[] args)
{
IntPtr gs = PythonEngine.AcquireLock();
object ob;
Expand All @@ -235,7 +276,7 @@ public object Dispatch(ArrayList args)
return ob;
}

public object TrueDispatch(ArrayList args)
private object TrueDispatch(object[] args)
{
MethodInfo method = dtype.GetMethod("Invoke");
ParameterInfo[] pi = method.GetParameters();
Expand All @@ -259,20 +300,108 @@ public object TrueDispatch(ArrayList args)
throw e;
}

if (rtype == typeof(void))
try
{
return null;
}
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;
Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The 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)");
Copy link
Member

Choose a reason for hiding this comment

The 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))
object result;
if (!Converter.ToManaged(op, rtype, out result, true))
{
throw new PythonException();
}

return result;
}
finally
{
Runtime.XDecref(op);
throw new PythonException();
}

Runtime.XDecref(op);
return result;
}
}
}
25 changes: 13 additions & 12 deletions src/runtime/methodbinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -960,42 +960,43 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i
}

// If there are out parameters, we return a tuple containing
// the result followed by the out parameters. If there is only
// 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 return the out parameter as the result to Python (for
// code compatibility with ironpython).

var mi = (MethodInfo)binding.info;

if (binding.outs == 1 && mi.ReturnType == typeof(void))
{
}

if (binding.outs > 0)
{
ParameterInfo[] pi = mi.GetParameters();
int c = pi.Length;
var n = 0;

IntPtr t = Runtime.PyTuple_New(binding.outs + 1);
IntPtr v = Converter.ToPython(result, mi.ReturnType);
Runtime.PyTuple_SetItem(t, n, v);
n++;
bool isVoid = mi.ReturnType == typeof(void);
int tupleSize = binding.outs + (isVoid ? 0 : 1);
IntPtr t = Runtime.PyTuple_New(tupleSize);
if (!isVoid)
{
IntPtr v = Converter.ToPython(result, mi.ReturnType);
Runtime.PyTuple_SetItem(t, n, v);
n++;
}

for (var i = 0; i < c; i++)
{
Type pt = pi[i].ParameterType;
if (pi[i].IsOut || pt.IsByRef)
if (pt.IsByRef)
{
v = Converter.ToPython(binding.args[i], pt.GetElementType());
IntPtr v = Converter.ToPython(binding.args[i], pt.GetElementType());
Runtime.PyTuple_SetItem(t, n, v);
n++;
}
}

if (binding.outs == 1 && mi.ReturnType == typeof(void))
{
v = Runtime.PyTuple_GetItem(t, 1);
IntPtr v = Runtime.PyTuple_GetItem(t, 0);
Runtime.XIncref(v);
Runtime.XDecref(t);
return v;
Expand Down
Loading