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

Skip to content

Commit bbc6cb7

Browse files
committed
Added the ability to implement delegates with ref and out parameters in Python, by returning the modified parameter values in a tuple.
BREAKING: MethodBinder omits a void return type when returning a tuple of out parameters. DelegateManager unpacks a tuple of out parameters from Python (reversing the logic in MethodBinder) and sets the out parameters of the delegate.
1 parent 92932fd commit bbc6cb7

File tree

8 files changed

+471
-57
lines changed

8 files changed

+471
-57
lines changed

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1212
- Ability to instantiate new .NET arrays using `Array[T](dim1, dim2, ...)` syntax
1313
- Python operator method will call C# operator method for supported binary and unary operators ([#1324][p1324]).
1414
- Add GetPythonThreadID and Interrupt methods in PythonEngine
15+
- Ability to implement delegates with `ref` and `out` parameters in Python, by returning the modified parameter values in a tuple. ([#1355][i1355])
1516

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

3638
### Fixed
3739

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

5557
### Removed

src/runtime/converter.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -338,23 +338,23 @@ internal static bool ToManagedValue(IntPtr value, Type obType,
338338

339339
if (mt != null)
340340
{
341-
if (mt is CLRObject)
341+
if (mt is CLRObject co)
342342
{
343-
object tmp = ((CLRObject)mt).inst;
343+
object tmp = co.inst;
344344
if (obType.IsInstanceOfType(tmp))
345345
{
346346
result = tmp;
347347
return true;
348348
}
349349
if (setError)
350350
{
351-
Exceptions.SetError(Exceptions.TypeError, $"value cannot be converted to {obType}");
351+
string typeString = tmp is null ? "null" : tmp.GetType().ToString();
352+
Exceptions.SetError(Exceptions.TypeError, $"{typeString} value cannot be converted to {obType}");
352353
}
353354
return false;
354355
}
355-
if (mt is ClassBase)
356+
if (mt is ClassBase cb)
356357
{
357-
var cb = (ClassBase)mt;
358358
if (!cb.type.Valid)
359359
{
360360
Exceptions.SetError(Exceptions.TypeError, cb.type.DeletedMessage);

src/runtime/delegatemanager.cs

Lines changed: 166 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System;
2-
using System.Collections;
2+
using System.Collections.Generic;
3+
using System.Linq;
34
using System.Reflection;
45
using System.Reflection.Emit;
6+
using System.Text;
57

68
namespace Python.Runtime
79
{
@@ -11,23 +13,20 @@ namespace Python.Runtime
1113
/// </summary>
1214
internal class DelegateManager
1315
{
14-
private Hashtable cache;
15-
private Type basetype;
16-
private Type listtype;
17-
private Type voidtype;
18-
private Type typetype;
19-
private Type ptrtype;
20-
private CodeGenerator codeGenerator;
16+
private readonly Dictionary<Type,Type> cache = new Dictionary<Type, Type>();
17+
private readonly Type basetype = typeof(Dispatcher);
18+
private readonly Type arrayType = typeof(object[]);
19+
private readonly Type voidtype = typeof(void);
20+
private readonly Type typetype = typeof(Type);
21+
private readonly Type ptrtype = typeof(IntPtr);
22+
private readonly CodeGenerator codeGenerator = new CodeGenerator();
23+
private readonly ConstructorInfo arrayCtor;
24+
private readonly MethodInfo dispatch;
2125

2226
public DelegateManager()
2327
{
24-
basetype = typeof(Dispatcher);
25-
listtype = typeof(ArrayList);
26-
voidtype = typeof(void);
27-
typetype = typeof(Type);
28-
ptrtype = typeof(IntPtr);
29-
cache = new Hashtable();
30-
codeGenerator = new CodeGenerator();
28+
arrayCtor = arrayType.GetConstructor(new[] { typeof(int) });
29+
dispatch = basetype.GetMethod("Dispatch");
3130
}
3231

3332
/// <summary>
@@ -58,10 +57,9 @@ private Type GetDispatcher(Type dtype)
5857
// unique signatures rather than delegate types, since multiple
5958
// delegate types with the same sig could use the same dispatcher.
6059

61-
object item = cache[dtype];
62-
if (item != null)
60+
if (cache.TryGetValue(dtype, out Type item))
6361
{
64-
return (Type)item;
62+
return item;
6563
}
6664

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

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

106-
ConstructorInfo ctor = listtype.GetConstructor(Type.EmptyTypes);
107-
MethodInfo dispatch = basetype.GetMethod("Dispatch");
108-
MethodInfo add = listtype.GetMethod("Add");
109-
110104
il = mb.GetILGenerator();
111-
il.DeclareLocal(listtype);
112-
il.Emit(OpCodes.Newobj, ctor);
105+
// loc_0 = new object[pi.Length]
106+
il.DeclareLocal(arrayType);
107+
il.Emit(OpCodes.Ldc_I4, pi.Length);
108+
il.Emit(OpCodes.Newobj, arrayCtor);
113109
il.Emit(OpCodes.Stloc_0);
114110

111+
bool anyByRef = false;
112+
115113
for (var c = 0; c < signature.Length; c++)
116114
{
117115
Type t = signature[c];
118116
il.Emit(OpCodes.Ldloc_0);
117+
il.Emit(OpCodes.Ldc_I4, c);
119118
il.Emit(OpCodes.Ldarg_S, (byte)(c + 1));
120119

120+
if (t.IsByRef)
121+
{
122+
// The argument is a pointer. We must dereference the pointer to get the value or object it points to.
123+
t = t.GetElementType();
124+
if (t.IsValueType)
125+
{
126+
il.Emit(OpCodes.Ldobj, t);
127+
}
128+
else
129+
{
130+
il.Emit(OpCodes.Ldind_Ref);
131+
}
132+
anyByRef = true;
133+
}
134+
121135
if (t.IsValueType)
122136
{
123137
il.Emit(OpCodes.Box, t);
124138
}
125139

126-
il.Emit(OpCodes.Callvirt, add);
127-
il.Emit(OpCodes.Pop);
140+
// args[c] = arg
141+
il.Emit(OpCodes.Stelem_Ref);
128142
}
129143

130144
il.Emit(OpCodes.Ldarg_0);
131145
il.Emit(OpCodes.Ldloc_0);
132146
il.Emit(OpCodes.Call, dispatch);
133147

148+
if (anyByRef)
149+
{
150+
// Dispatch() will have modified elements of the args list that correspond to out parameters.
151+
for (var c = 0; c < signature.Length; c++)
152+
{
153+
Type t = signature[c];
154+
if (t.IsByRef)
155+
{
156+
t = t.GetElementType();
157+
// *arg = args[c]
158+
il.Emit(OpCodes.Ldarg_S, (byte)(c + 1));
159+
il.Emit(OpCodes.Ldloc_0);
160+
il.Emit(OpCodes.Ldc_I4, c);
161+
il.Emit(OpCodes.Ldelem_Ref);
162+
if (t.IsValueType)
163+
{
164+
il.Emit(OpCodes.Unbox_Any, t);
165+
il.Emit(OpCodes.Stobj, t);
166+
}
167+
else
168+
{
169+
il.Emit(OpCodes.Stind_Ref);
170+
}
171+
}
172+
}
173+
}
174+
134175
if (method.ReturnType == voidtype)
135176
{
136177
il.Emit(OpCodes.Pop);
@@ -218,7 +259,7 @@ public void Dispose()
218259
GC.SuppressFinalize(this);
219260
}
220261

221-
public object Dispatch(ArrayList args)
262+
public object Dispatch(object[] args)
222263
{
223264
IntPtr gs = PythonEngine.AcquireLock();
224265
object ob;
@@ -235,7 +276,7 @@ public object Dispatch(ArrayList args)
235276
return ob;
236277
}
237278

238-
public object TrueDispatch(ArrayList args)
279+
private object TrueDispatch(object[] args)
239280
{
240281
MethodInfo method = dtype.GetMethod("Invoke");
241282
ParameterInfo[] pi = method.GetParameters();
@@ -259,20 +300,108 @@ public object TrueDispatch(ArrayList args)
259300
throw e;
260301
}
261302

262-
if (rtype == typeof(void))
303+
try
263304
{
264-
return null;
265-
}
305+
int byRefCount = pi.Count(parameterInfo => parameterInfo.ParameterType.IsByRef);
306+
if (byRefCount > 0)
307+
{
308+
// By symmetry with MethodBinder.Invoke, when there are out
309+
// parameters we expect to receive a tuple containing
310+
// the result, if any, followed by the out parameters. If there is only
311+
// one out parameter and the return type of the method is void,
312+
// we instead receive the out parameter as the result from Python.
313+
314+
bool isVoid = rtype == typeof(void);
315+
int tupleSize = byRefCount + (isVoid ? 0 : 1);
316+
if (isVoid && byRefCount == 1)
317+
{
318+
// The return type is void and there is a single out parameter.
319+
for (int i = 0; i < pi.Length; i++)
320+
{
321+
Type t = pi[i].ParameterType;
322+
if (t.IsByRef)
323+
{
324+
if (!Converter.ToManaged(op, t, out object newArg, true))
325+
{
326+
Exceptions.RaiseTypeError($"The Python function did not return {t.GetElementType()} (the out parameter type)");
327+
throw new PythonException();
328+
}
329+
args[i] = newArg;
330+
break;
331+
}
332+
}
333+
return null;
334+
}
335+
else if (Runtime.PyTuple_Check(op) && Runtime.PyTuple_Size(op) == tupleSize)
336+
{
337+
int index = isVoid ? 0 : 1;
338+
for (int i = 0; i < pi.Length; i++)
339+
{
340+
Type t = pi[i].ParameterType;
341+
if (t.IsByRef)
342+
{
343+
IntPtr item = Runtime.PyTuple_GetItem(op, index++);
344+
if (!Converter.ToManaged(item, t, out object newArg, true))
345+
{
346+
Exceptions.RaiseTypeError($"The Python function returned a tuple where element {i} was not {t.GetElementType()} (the out parameter type)");
347+
throw new PythonException();
348+
}
349+
args[i] = newArg;
350+
}
351+
}
352+
if (isVoid)
353+
{
354+
return null;
355+
}
356+
IntPtr item0 = Runtime.PyTuple_GetItem(op, 0);
357+
if (!Converter.ToManaged(item0, rtype, out object result0, true))
358+
{
359+
Exceptions.RaiseTypeError($"The Python function returned a tuple where element 0 was not {rtype} (the return type)");
360+
throw new PythonException();
361+
}
362+
return result0;
363+
}
364+
else
365+
{
366+
string tpName = Runtime.PyObject_GetTypeName(op);
367+
if (Runtime.PyTuple_Check(op))
368+
{
369+
tpName += $" of size {Runtime.PyTuple_Size(op)}";
370+
}
371+
StringBuilder sb = new StringBuilder();
372+
if (!isVoid) sb.Append(rtype.FullName);
373+
for (int i = 0; i < pi.Length; i++)
374+
{
375+
Type t = pi[i].ParameterType;
376+
if (t.IsByRef)
377+
{
378+
if (sb.Length > 0) sb.Append(",");
379+
sb.Append(t.GetElementType().FullName);
380+
}
381+
}
382+
string returnValueString = isVoid ? "" : "the return value and ";
383+
Exceptions.RaiseTypeError($"Expected a tuple ({sb}) of {returnValueString}the values for out and ref parameters, got {tpName}.");
384+
throw new PythonException();
385+
}
386+
}
387+
388+
if (rtype == typeof(void))
389+
{
390+
return null;
391+
}
266392

267-
object result;
268-
if (!Converter.ToManaged(op, rtype, out result, true))
393+
object result;
394+
if (!Converter.ToManaged(op, rtype, out result, true))
395+
{
396+
throw new PythonException();
397+
}
398+
399+
return result;
400+
}
401+
finally
269402
{
270403
Runtime.XDecref(op);
271-
throw new PythonException();
272404
}
273-
274-
Runtime.XDecref(op);
275-
return result;
276405
}
277406
}
278407
}

src/runtime/methodbinder.cs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -960,42 +960,43 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i
960960
}
961961

962962
// If there are out parameters, we return a tuple containing
963-
// the result followed by the out parameters. If there is only
963+
// the result, if any, followed by the out parameters. If there is only
964964
// one out parameter and the return type of the method is void,
965965
// we return the out parameter as the result to Python (for
966966
// code compatibility with ironpython).
967967

968968
var mi = (MethodInfo)binding.info;
969969

970-
if (binding.outs == 1 && mi.ReturnType == typeof(void))
971-
{
972-
}
973-
974970
if (binding.outs > 0)
975971
{
976972
ParameterInfo[] pi = mi.GetParameters();
977973
int c = pi.Length;
978974
var n = 0;
979975

980-
IntPtr t = Runtime.PyTuple_New(binding.outs + 1);
981-
IntPtr v = Converter.ToPython(result, mi.ReturnType);
982-
Runtime.PyTuple_SetItem(t, n, v);
983-
n++;
976+
bool isVoid = mi.ReturnType == typeof(void);
977+
int tupleSize = binding.outs + (isVoid ? 0 : 1);
978+
IntPtr t = Runtime.PyTuple_New(tupleSize);
979+
if (!isVoid)
980+
{
981+
IntPtr v = Converter.ToPython(result, mi.ReturnType);
982+
Runtime.PyTuple_SetItem(t, n, v);
983+
n++;
984+
}
984985

985986
for (var i = 0; i < c; i++)
986987
{
987988
Type pt = pi[i].ParameterType;
988-
if (pi[i].IsOut || pt.IsByRef)
989+
if (pt.IsByRef)
989990
{
990-
v = Converter.ToPython(binding.args[i], pt.GetElementType());
991+
IntPtr v = Converter.ToPython(binding.args[i], pt.GetElementType());
991992
Runtime.PyTuple_SetItem(t, n, v);
992993
n++;
993994
}
994995
}
995996

996997
if (binding.outs == 1 && mi.ReturnType == typeof(void))
997998
{
998-
v = Runtime.PyTuple_GetItem(t, 1);
999+
IntPtr v = Runtime.PyTuple_GetItem(t, 0);
9991000
Runtime.XIncref(v);
10001001
Runtime.XDecref(t);
10011002
return v;

0 commit comments

Comments
 (0)