| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 92932fd commit 063a674
8 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -12,6 +12,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. | |||
| 12 | 12 | - Ability to instantiate new .NET arrays using `Array[T](dim1, dim2, ...)` syntax | |
| 13 | 13 | - Python operator method will call C# operator method for supported binary and unary operators ([#1324][p1324]). | |
| 14 | 14 | - 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]) | ||
| 15 | 16 | ||
| 16 | 17 | ### Changed | |
| 17 | 18 | - Drop support for Python 2, 3.4, and 3.5 | |
@@ -32,6 +33,7 @@ details about the cause of the failure | |||
| 32 | 33 | - floating point values passed from Python are no longer silently truncated | |
| 33 | 34 | when .NET expects an integer [#1342][i1342] | |
| 34 | 35 | - 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. | ||
| 35 | 37 | ||
| 36 | 38 | ### Fixed | |
| 37 | 39 | ||
@@ -48,8 +50,8 @@ when .NET expects an integer [#1342][i1342] | |||
| 48 | 50 | - Fixed issue when calling PythonException.Format where another exception would be raise for unnormalized exceptions | |
| 49 | 51 | - Made it possible to call `ToString`, `GetHashCode`, and `GetType` on inteface objects | |
| 50 | 52 | - 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" | ||
| 53 | 55 | - Providing an invalid type parameter to a generic type or method produces a helpful Python error | |
| 54 | 56 | ||
| 55 | 57 | ### Removed | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -338,23 +338,23 @@ internal static bool ToManagedValue(IntPtr value, Type obType, | |||
| 338 | 338 | ||
| 339 | 339 | if (mt != null) | |
| 340 | 340 | { | |
| 341 | - if (mt is CLRObject) | ||
| 341 | + if (mt is CLRObject co) | ||
| 342 | 342 | { | |
| 343 | - object tmp = ((CLRObject)mt).inst; | ||
| 343 | + object tmp = co.inst; | ||
| 344 | 344 | if (obType.IsInstanceOfType(tmp)) | |
| 345 | 345 | { | |
| 346 | 346 | result = tmp; | |
| 347 | 347 | return true; | |
| 348 | 348 | } | |
| 349 | 349 | if (setError) | |
| 350 | 350 | { | |
| 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}"); | ||
| 352 | 353 | } | |
| 353 | 354 | return false; | |
| 354 | 355 | } | |
| 355 | - if (mt is ClassBase) | ||
| 356 | + if (mt is ClassBase cb) | ||
| 356 | 357 | { | |
| 357 | - var cb = (ClassBase)mt; | ||
| 358 | 358 | if (!cb.type.Valid) | |
| 359 | 359 | { | |
| 360 | 360 | Exceptions.SetError(Exceptions.TypeError, cb.type.DeletedMessage); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,7 +1,9 @@ | |||
| 1 | 1 | using System; | |
| 2 | - using System.Collections; | ||
| 2 | + using System.Collections.Generic; | ||
| 3 | + using System.Linq; | ||
| 3 | 4 | using System.Reflection; | |
| 4 | 5 | using System.Reflection.Emit; | |
| 6 | + using System.Text; | ||
| 5 | 7 | ||
| 6 | 8 | namespace Python.Runtime | |
| 7 | 9 | { | |
@@ -11,23 +13,20 @@ namespace Python.Runtime | |||
| 11 | 13 | /// </summary> | |
| 12 | 14 | internal class DelegateManager | |
| 13 | 15 | { | |
| 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; | ||
| 21 | 25 | ||
| 22 | 26 | public DelegateManager() | |
| 23 | 27 | { | |
| 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"); | ||
| 31 | 30 | } | |
| 32 | 31 | ||
| 33 | 32 | /// <summary> | |
@@ -58,10 +57,9 @@ private Type GetDispatcher(Type dtype) | |||
| 58 | 57 | // unique signatures rather than delegate types, since multiple | |
| 59 | 58 | // delegate types with the same sig could use the same dispatcher. | |
| 60 | 59 | ||
| 61 | - object item = cache[dtype]; | ||
| 62 | - if (item != null) | ||
| 60 | + if (cache.TryGetValue(dtype, out Type item)) | ||
| 63 | 61 | { | |
| 64 | - return (Type)item; | ||
| 62 | + return item; | ||
| 65 | 63 | } | |
| 66 | 64 | ||
| 67 | 65 | string name = $"__{dtype.FullName}Dispatcher"; | |
@@ -103,34 +101,77 @@ private Type GetDispatcher(Type dtype) | |||
| 103 | 101 | ||
| 104 | 102 | MethodBuilder mb = tb.DefineMethod("Invoke", MethodAttributes.Public, method.ReturnType, signature); | |
| 105 | 103 | ||
| 106 | - ConstructorInfo ctor = listtype.GetConstructor(Type.EmptyTypes); | ||
| 107 | - MethodInfo dispatch = basetype.GetMethod("Dispatch"); | ||
| 108 | - MethodInfo add = listtype.GetMethod("Add"); | ||
| 109 | - | ||
| 110 | 104 | 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); | ||
| 113 | 109 | il.Emit(OpCodes.Stloc_0); | |
| 114 | 110 | ||
| 111 | + bool anyByRef = false; | ||
| 112 | + | ||
| 115 | 113 | for (var c = 0; c < signature.Length; c++) | |
| 116 | 114 | { | |
| 117 | 115 | Type t = signature[c]; | |
| 118 | 116 | il.Emit(OpCodes.Ldloc_0); | |
| 117 | + il.Emit(OpCodes.Ldc_I4, c); | ||
| 119 | 118 | il.Emit(OpCodes.Ldarg_S, (byte)(c + 1)); | |
| 120 | 119 | ||
| 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 | + | ||
| 121 | 135 | if (t.IsValueType) | |
| 122 | 136 | { | |
| 123 | 137 | il.Emit(OpCodes.Box, t); | |
| 124 | 138 | } | |
| 125 | 139 | ||
| 126 | - il.Emit(OpCodes.Callvirt, add); | ||
| 127 | - il.Emit(OpCodes.Pop); | ||
| 140 | + // args[c] = arg | ||
| 141 | + il.Emit(OpCodes.Stelem_Ref); | ||
| 128 | 142 | } | |
| 129 | 143 | ||
| 130 | 144 | il.Emit(OpCodes.Ldarg_0); | |
| 131 | 145 | il.Emit(OpCodes.Ldloc_0); | |
| 132 | 146 | il.Emit(OpCodes.Call, dispatch); | |
| 133 | 147 | ||
| 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 | + | ||
| 134 | 175 | if (method.ReturnType == voidtype) | |
| 135 | 176 | { | |
| 136 | 177 | il.Emit(OpCodes.Pop); | |
@@ -218,7 +259,7 @@ public void Dispose() | |||
| 218 | 259 | GC.SuppressFinalize(this); | |
| 219 | 260 | } | |
| 220 | 261 | ||
| 221 | - public object Dispatch(ArrayList args) | ||
| 262 | + public object Dispatch(object[] args) | ||
| 222 | 263 | { | |
| 223 | 264 | IntPtr gs = PythonEngine.AcquireLock(); | |
| 224 | 265 | object ob; | |
@@ -235,7 +276,7 @@ public object Dispatch(ArrayList args) | |||
| 235 | 276 | return ob; | |
| 236 | 277 | } | |
| 237 | 278 | ||
| 238 | - public object TrueDispatch(ArrayList args) | ||
| 279 | + private object TrueDispatch(object[] args) | ||
| 239 | 280 | { | |
| 240 | 281 | MethodInfo method = dtype.GetMethod("Invoke"); | |
| 241 | 282 | ParameterInfo[] pi = method.GetParameters(); | |
@@ -259,20 +300,108 @@ public object TrueDispatch(ArrayList args) | |||
| 259 | 300 | throw e; | |
| 260 | 301 | } | |
| 261 | 302 | ||
| 262 | - if (rtype == typeof(void)) | ||
| 303 | + try | ||
| 263 | 304 | { | |
| 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 | + } | ||
| 266 | 392 | ||
| 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 | ||
| 269 | 402 | { | |
| 270 | 403 | Runtime.XDecref(op); | |
| 271 | - throw new PythonException(); | ||
| 272 | 404 | } | |
| 273 | - | ||
| 274 | - Runtime.XDecref(op); | ||
| 275 | - return result; | ||
| 276 | 405 | } | |
| 277 | 406 | } | |
| 278 | 407 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -960,42 +960,43 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i | |||
| 960 | 960 | } | |
| 961 | 961 | ||
| 962 | 962 | // 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 | ||
| 964 | 964 | // one out parameter and the return type of the method is void, | |
| 965 | 965 | // we return the out parameter as the result to Python (for | |
| 966 | 966 | // code compatibility with ironpython). | |
| 967 | 967 | ||
| 968 | 968 | var mi = (MethodInfo)binding.info; | |
| 969 | 969 | ||
| 970 | - if (binding.outs == 1 && mi.ReturnType == typeof(void)) | ||
| 971 | - { | ||
| 972 | - } | ||
| 973 | - | ||
| 974 | 970 | if (binding.outs > 0) | |
| 975 | 971 | { | |
| 976 | 972 | ParameterInfo[] pi = mi.GetParameters(); | |
| 977 | 973 | int c = pi.Length; | |
| 978 | 974 | var n = 0; | |
| 979 | 975 | ||
| 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 | + } | ||
| 984 | 985 | ||
| 985 | 986 | for (var i = 0; i < c; i++) | |
| 986 | 987 | { | |
| 987 | 988 | Type pt = pi[i].ParameterType; | |
| 988 | - if (pi[i].IsOut || pt.IsByRef) | ||
| 989 | + if (pt.IsByRef) | ||
| 989 | 990 | { | |
| 990 | - v = Converter.ToPython(binding.args[i], pt.GetElementType()); | ||
| 991 | + IntPtr v = Converter.ToPython(binding.args[i], pt.GetElementType()); | ||
| 991 | 992 | Runtime.PyTuple_SetItem(t, n, v); | |
| 992 | 993 | n++; | |
| 993 | 994 | } | |
| 994 | 995 | } | |
| 995 | 996 | ||
| 996 | 997 | if (binding.outs == 1 && mi.ReturnType == typeof(void)) | |
| 997 | 998 | { | |
| 998 | - v = Runtime.PyTuple_GetItem(t, 1); | ||
| 999 | + IntPtr v = Runtime.PyTuple_GetItem(t, 0); | ||
| 999 | 1000 | Runtime.XIncref(v); | |
| 1000 | 1001 | Runtime.XDecref(t); | |
| 1001 | 1002 | return v; | |
| Back | FazBrowse Home | New Git URL |
0 commit comments