| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 88850f5 commit 7450c5c
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -13,6 +13,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. | |||
| 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 | 15 | - Ability to implement delegates with `ref` and `out` parameters in Python, by returning the modified parameter values in a tuple. ([#1355][i1355]) | |
| 16 | + - Ability to override .NET methods that have `out` or `ref` in Python by returning the modified parameter values in a tuple. ([#1481][i1481]) | ||
| 16 | 17 | - `PyType` - a wrapper for Python type objects, that also permits creating new heap types from `TypeSpec` | |
| 17 | 18 | - Improved exception handling: | |
| 18 | 19 | * exceptions can now be converted with codecs | |
@@ -879,3 +880,4 @@ This version improves performance on benchmarks significantly compared to 2.3. | |||
| 879 | 880 | [i449]: https://github.com/pythonnet/pythonnet/issues/449 | |
| 880 | 881 | [i1342]: https://github.com/pythonnet/pythonnet/issues/1342 | |
| 881 | 882 | [i238]: https://github.com/pythonnet/pythonnet/issues/238 | |
| 883 | + [i1481]: https://github.com/pythonnet/pythonnet/issues/1481 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -429,24 +429,33 @@ private static void AddVirtualMethod(MethodInfo method, Type baseType, TypeBuild | |||
| 429 | 429 | il.Emit(OpCodes.Ldloc_0); | |
| 430 | 430 | il.Emit(OpCodes.Ldc_I4, i); | |
| 431 | 431 | il.Emit(OpCodes.Ldarg, i + 1); | |
| 432 | - if (parameterTypes[i].IsValueType) | ||
| 432 | + var type = parameterTypes[i]; | ||
| 433 | + if (type.IsByRef) | ||
| 433 | 434 | { | |
| 434 | - il.Emit(OpCodes.Box, parameterTypes[i]); | ||
| 435 | + type = type.GetElementType(); | ||
| 436 | + il.Emit(OpCodes.Ldobj, type); | ||
| 437 | + } | ||
| 438 | + if (type.IsValueType) | ||
| 439 | + { | ||
| 440 | + il.Emit(OpCodes.Box, type); | ||
| 435 | 441 | } | |
| 436 | 442 | il.Emit(OpCodes.Stelem, typeof(object)); | |
| 437 | 443 | } | |
| 438 | 444 | il.Emit(OpCodes.Ldloc_0); | |
| 445 | + | ||
| 446 | + il.Emit(OpCodes.Ldtoken, method); | ||
| 439 | 447 | #pragma warning disable CS0618 // PythonDerivedType is for internal use only | |
| 440 | 448 | if (method.ReturnType == typeof(void)) | |
| 441 | 449 | { | |
| 442 | - il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod("InvokeMethodVoid")); | ||
| 450 | + il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod(nameof(InvokeMethodVoid))); | ||
| 443 | 451 | } | |
| 444 | 452 | else | |
| 445 | 453 | { | |
| 446 | 454 | il.Emit(OpCodes.Call, | |
| 447 | - typeof(PythonDerivedType).GetMethod("InvokeMethod").MakeGenericMethod(method.ReturnType)); | ||
| 455 | + typeof(PythonDerivedType).GetMethod(nameof(InvokeMethod)).MakeGenericMethod(method.ReturnType)); | ||
| 448 | 456 | } | |
| 449 | 457 | #pragma warning restore CS0618 // PythonDerivedType is for internal use only | |
| 458 | + CodeGenerator.GenerateMarshalByRefsBack(il, parameterTypes); | ||
| 450 | 459 | il.Emit(OpCodes.Ret); | |
| 451 | 460 | } | |
| 452 | 461 | ||
@@ -500,35 +509,65 @@ private static void AddPythonMethod(string methodName, PyObject func, TypeBuilde | |||
| 500 | 509 | argTypes.ToArray()); | |
| 501 | 510 | ||
| 502 | 511 | ILGenerator il = methodBuilder.GetILGenerator(); | |
| 512 | + | ||
| 503 | 513 | il.DeclareLocal(typeof(object[])); | |
| 514 | + il.DeclareLocal(typeof(RuntimeMethodHandle)); | ||
| 515 | + | ||
| 516 | + // this | ||
| 504 | 517 | il.Emit(OpCodes.Ldarg_0); | |
| 518 | + | ||
| 519 | + // Python method to call | ||
| 505 | 520 | il.Emit(OpCodes.Ldstr, methodName); | |
| 521 | + | ||
| 522 | + // original method name | ||
| 506 | 523 | il.Emit(OpCodes.Ldnull); // don't fall back to the base type's method | |
| 524 | + | ||
| 525 | + // create args array | ||
| 507 | 526 | il.Emit(OpCodes.Ldc_I4, argTypes.Count); | |
| 508 | 527 | il.Emit(OpCodes.Newarr, typeof(object)); | |
| 509 | 528 | il.Emit(OpCodes.Stloc_0); | |
| 529 | + | ||
| 530 | + // fill args array | ||
| 510 | 531 | for (var i = 0; i < argTypes.Count; ++i) | |
| 511 | 532 | { | |
| 512 | 533 | il.Emit(OpCodes.Ldloc_0); | |
| 513 | 534 | il.Emit(OpCodes.Ldc_I4, i); | |
| 514 | 535 | il.Emit(OpCodes.Ldarg, i + 1); | |
| 515 | - if (argTypes[i].IsValueType) | ||
| 536 | + var type = argTypes[i]; | ||
| 537 | + if (type.IsByRef) | ||
| 516 | 538 | { | |
| 517 | - il.Emit(OpCodes.Box, argTypes[i]); | ||
| 539 | + type = type.GetElementType(); | ||
| 540 | + il.Emit(OpCodes.Ldobj, type); | ||
| 541 | + } | ||
| 542 | + if (type.IsValueType) | ||
| 543 | + { | ||
| 544 | + il.Emit(OpCodes.Box, type); | ||
| 518 | 545 | } | |
| 519 | 546 | il.Emit(OpCodes.Stelem, typeof(object)); | |
| 520 | 547 | } | |
| 548 | + | ||
| 549 | + // args array | ||
| 521 | 550 | il.Emit(OpCodes.Ldloc_0); | |
| 551 | + | ||
| 552 | + // method handle for the base method is null | ||
| 553 | + il.Emit(OpCodes.Ldloca_S, 1); | ||
| 554 | + il.Emit(OpCodes.Initobj, typeof(RuntimeMethodHandle)); | ||
| 555 | + il.Emit(OpCodes.Ldloc_1); | ||
| 522 | 556 | #pragma warning disable CS0618 // PythonDerivedType is for internal use only | |
| 557 | + | ||
| 558 | + // invoke the method | ||
| 523 | 559 | if (returnType == typeof(void)) | |
| 524 | 560 | { | |
| 525 | - il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod("InvokeMethodVoid")); | ||
| 561 | + il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod(nameof(InvokeMethodVoid))); | ||
| 526 | 562 | } | |
| 527 | 563 | else | |
| 528 | 564 | { | |
| 529 | 565 | il.Emit(OpCodes.Call, | |
| 530 | - typeof(PythonDerivedType).GetMethod("InvokeMethod").MakeGenericMethod(returnType)); | ||
| 566 | + typeof(PythonDerivedType).GetMethod(nameof(InvokeMethod)).MakeGenericMethod(returnType)); | ||
| 531 | 567 | } | |
| 568 | + | ||
| 569 | + CodeGenerator.GenerateMarshalByRefsBack(il, argTypes); | ||
| 570 | + | ||
| 532 | 571 | #pragma warning restore CS0618 // PythonDerivedType is for internal use only | |
| 533 | 572 | il.Emit(OpCodes.Ret); | |
| 534 | 573 | } | |
@@ -672,7 +711,8 @@ public class PythonDerivedType | |||
| 672 | 711 | /// method binding (i.e. it has been overridden in the derived python | |
| 673 | 712 | /// class) it calls it, otherwise it calls the base method. | |
| 674 | 713 | /// </summary> | |
| 675 | - public static T? InvokeMethod<T>(IPythonDerivedType obj, string methodName, string origMethodName, object[] args) | ||
| 714 | + public static T? InvokeMethod<T>(IPythonDerivedType obj, string methodName, string origMethodName, | ||
| 715 | + object[] args, RuntimeMethodHandle methodHandle) | ||
| 676 | 716 | { | |
| 677 | 717 | var self = GetPyObj(obj); | |
| 678 | 718 | ||
@@ -698,8 +738,10 @@ public class PythonDerivedType | |||
| 698 | 738 | } | |
| 699 | 739 | ||
| 700 | 740 | PyObject py_result = method.Invoke(pyargs); | |
| 701 | - disposeList.Add(py_result); | ||
| 702 | - return py_result.As<T>(); | ||
| 741 | + PyTuple? result_tuple = MarshalByRefsBack(args, methodHandle, py_result, outsOffset: 1); | ||
| 742 | + return result_tuple is not null | ||
| 743 | + ? result_tuple[0].As<T>() | ||
| 744 | + : py_result.As<T>(); | ||
| 703 | 745 | } | |
| 704 | 746 | } | |
| 705 | 747 | } | |
@@ -726,7 +768,7 @@ public class PythonDerivedType | |||
| 726 | 768 | } | |
| 727 | 769 | ||
| 728 | 770 | public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, string origMethodName, | |
| 729 | - object[] args) | ||
| 771 | + object?[] args, RuntimeMethodHandle methodHandle) | ||
| 730 | 772 | { | |
| 731 | 773 | var self = GetPyObj(obj); | |
| 732 | 774 | if (null != self.Ref) | |
@@ -736,8 +778,7 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s | |||
| 736 | 778 | try | |
| 737 | 779 | { | |
| 738 | 780 | using var pyself = new PyObject(self.CheckRun()); | |
| 739 | - PyObject method = pyself.GetAttr(methodName, Runtime.None); | ||
| 740 | - disposeList.Add(method); | ||
| 781 | + using PyObject method = pyself.GetAttr(methodName, Runtime.None); | ||
| 741 | 782 | if (method.Reference != Runtime.None) | |
| 742 | 783 | { | |
| 743 | 784 | // if the method hasn't been overridden then it will be a managed object | |
@@ -752,7 +793,7 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s | |||
| 752 | 793 | } | |
| 753 | 794 | ||
| 754 | 795 | PyObject py_result = method.Invoke(pyargs); | |
| 755 | - disposeList.Add(py_result); | ||
| 796 | + MarshalByRefsBack(args, methodHandle, py_result, outsOffset: 0); | ||
| 756 | 797 | return; | |
| 757 | 798 | } | |
| 758 | 799 | } | |
@@ -779,6 +820,44 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s | |||
| 779 | 820 | args); | |
| 780 | 821 | } | |
| 781 | 822 | ||
| 823 | + /// <summary> | ||
| 824 | + /// If the method has byref arguments, reinterprets Python return value | ||
| 825 | + /// as a tuple of new values for those arguments, and updates corresponding | ||
| 826 | + /// elements of <paramref name="args"/> array. | ||
| 827 | + /// </summary> | ||
| 828 | + private static PyTuple? MarshalByRefsBack(object?[] args, RuntimeMethodHandle methodHandle, PyObject pyResult, int outsOffset) | ||
| 829 | + { | ||
| 830 | + if (methodHandle == default) return null; | ||
| 831 | + | ||
| 832 | + var originalMethod = MethodBase.GetMethodFromHandle(methodHandle); | ||
| 833 | + var parameters = originalMethod.GetParameters(); | ||
| 834 | + PyTuple? outs = null; | ||
| 835 | + int byrefIndex = 0; | ||
| 836 | + for (int i = 0; i < parameters.Length; ++i) | ||
| 837 | + { | ||
| 838 | + Type type = parameters[i].ParameterType; | ||
| 839 | + if (!type.IsByRef) | ||
| 840 | + { | ||
| 841 | + continue; | ||
| 842 | + } | ||
| 843 | + | ||
| 844 | + type = type.GetElementType(); | ||
| 845 | + | ||
| 846 | + if (outs is null) | ||
| 847 | + { | ||
| 848 | + outs = new PyTuple(pyResult); | ||
| 849 | + pyResult.Dispose(); | ||
| 850 | + } | ||
| 851 | + | ||
| 852 | + args[i] = outs[byrefIndex + outsOffset].AsManagedObject(type); | ||
| 853 | + byrefIndex++; | ||
| 854 | + } | ||
| 855 | + if (byrefIndex > 0 && outs!.Length() > byrefIndex + outsOffset) | ||
| 856 | + throw new ArgumentException("Too many output parameters"); | ||
| 857 | + | ||
| 858 | + return outs; | ||
| 859 | + } | ||
| 860 | + | ||
| 782 | 861 | public static T? InvokeGetProperty<T>(IPythonDerivedType obj, string propertyName) | |
| 783 | 862 | { | |
| 784 | 863 | var self = GetPyObj(obj); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,4 +1,5 @@ | |||
| 1 | 1 | using System; | |
| 2 | + using System.Collections.Generic; | ||
| 2 | 3 | using System.Reflection; | |
| 3 | 4 | using System.Reflection.Emit; | |
| 4 | 5 | using System.Threading; | |
@@ -42,5 +43,39 @@ internal TypeBuilder DefineType(string name, Type basetype) | |||
| 42 | 43 | var attrs = TypeAttributes.Public; | |
| 43 | 44 | return mBuilder.DefineType(name, attrs, basetype); | |
| 44 | 45 | } | |
| 46 | + | ||
| 47 | + /// <summary> | ||
| 48 | + /// Generates code, that copies potentially modified objects in args array | ||
| 49 | + /// back to the corresponding byref arguments | ||
| 50 | + /// </summary> | ||
| 51 | + internal static void GenerateMarshalByRefsBack(ILGenerator il, IReadOnlyList<Type> argTypes) | ||
| 52 | + { | ||
| 53 | + // assumes argument array is in loc_0 | ||
| 54 | + for (int i = 0; i < argTypes.Count; ++i) | ||
| 55 | + { | ||
| 56 | + var type = argTypes[i]; | ||
| 57 | + if (type.IsByRef) | ||
| 58 | + { | ||
| 59 | + type = type.GetElementType(); | ||
| 60 | + | ||
| 61 | + il.Emit(OpCodes.Ldarg, i + 1); // for stobj/stind later at the end | ||
| 62 | + | ||
| 63 | + il.Emit(OpCodes.Ldloc_0); | ||
| 64 | + il.Emit(OpCodes.Ldc_I4, i); | ||
| 65 | + il.Emit(OpCodes.Ldelem_Ref); | ||
| 66 | + | ||
| 67 | + if (type.IsValueType) | ||
| 68 | + { | ||
| 69 | + il.Emit(OpCodes.Unbox_Any, type); | ||
| 70 | + il.Emit(OpCodes.Stobj, type); | ||
| 71 | + } | ||
| 72 | + else | ||
| 73 | + { | ||
| 74 | + il.Emit(OpCodes.Castclass, type); | ||
| 75 | + il.Emit(OpCodes.Stind_Ref); | ||
| 76 | + } | ||
| 77 | + } | ||
| 78 | + } | ||
| 79 | + } | ||
| 45 | 80 | } | |
| 46 | 81 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -135,28 +135,7 @@ private Type GetDispatcher(Type dtype) | |||
| 135 | 135 | if (anyByRef) | |
| 136 | 136 | { | |
| 137 | 137 | // Dispatch() will have modified elements of the args list that correspond to out parameters. | |
| 138 | - for (var c = 0; c < signature.Length; c++) | ||
| 139 | - { | ||
| 140 | - Type t = signature[c]; | ||
| 141 | - if (t.IsByRef) | ||
| 142 | - { | ||
| 143 | - t = t.GetElementType(); | ||
| 144 | - // *arg = args[c] | ||
| 145 | - il.Emit(OpCodes.Ldarg_S, (byte)(c + 1)); | ||
| 146 | - il.Emit(OpCodes.Ldloc_0); | ||
| 147 | - il.Emit(OpCodes.Ldc_I4, c); | ||
| 148 | - il.Emit(OpCodes.Ldelem_Ref); | ||
| 149 | - if (t.IsValueType) | ||
| 150 | - { | ||
| 151 | - il.Emit(OpCodes.Unbox_Any, t); | ||
| 152 | - il.Emit(OpCodes.Stobj, t); | ||
| 153 | - } | ||
| 154 | - else | ||
| 155 | - { | ||
| 156 | - il.Emit(OpCodes.Stind_Ref); | ||
| 157 | - } | ||
| 158 | - } | ||
| 159 | - } | ||
| 138 | + CodeGenerator.GenerateMarshalByRefsBack(il, signature); | ||
| 160 | 139 | } | |
| 161 | 140 | ||
| 162 | 141 | if (method.ReturnType == voidtype) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -79,4 +79,18 @@ private interface IPrivate | |||
| 79 | 79 | { | |
| 80 | 80 | } | |
| 81 | 81 | } | |
| 82 | + | ||
| 83 | + public interface IOutArg | ||
| 84 | + { | ||
| 85 | + string MyMethod_Out(string name, out int index); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + public class OutArgCaller | ||
| 89 | + { | ||
| 90 | + public static int CallMyMethod_Out(IOutArg myInterface) | ||
| 91 | + { | ||
| 92 | + myInterface.MyMethod_Out("myclient", out int index); | ||
| 93 | + return index; | ||
| 94 | + } | ||
| 95 | + } | ||
| 82 | 96 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -93,6 +93,20 @@ def test_interface_object_returned_through_out_param(): | |||
| 93 | 93 | ||
| 94 | 94 | assert hello2.SayHello() == 'hello 2' | |
| 95 | 95 | ||
| 96 | + def test_interface_out_param_python_impl(): | ||
| 97 | + from Python.Test import IOutArg, OutArgCaller | ||
| 98 | + | ||
| 99 | + class MyOutImpl(IOutArg): | ||
| 100 | + __namespace__ = "Python.Test" | ||
| 101 | + | ||
| 102 | + def MyMethod_Out(self, name, index): | ||
| 103 | + other_index = 101 | ||
| 104 | + return ('MyName', other_index) | ||
| 105 | + | ||
| 106 | + py_impl = MyOutImpl() | ||
| 107 | + | ||
| 108 | + assert 101 == OutArgCaller.CallMyMethod_Out(py_impl) | ||
| 109 | + | ||
| 96 | 110 | ||
| 97 | 111 | def test_null_interface_object_returned(): | |
| 98 | 112 | """Test None is used also for methods with interface return types""" | |
| Back | FazBrowse Home | New Git URL |
0 commit comments