8000 added support for byref parameters when overriding .NET methods from … · pythonnet/pythonnet@c3ace5b · GitHub
[go: up one dir, main page]

Skip to content
  • Commit c3ace5b

    Browse files
    committed
    added support for byref parameters when overriding .NET methods from Python
    new code is emitted to 1. unpack the tuple returned from Python to extract new values for byref parameters and modify args array correspondingly 2. marshal those new values from the args array back into arguments in IL fixes #1481
    1 parent 88850f5 commit c3ace5b

    File tree

    6 files changed

    +160
    -37
    lines changed

    6 files changed

    +160
    -37
    lines changed

    CHANGELOG.md

    Lines changed: 2 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -13,6 +13,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
    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
    1515
    - 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 Pyhton by returning the modified parameter values in a tuple. ([#1481][i1481])
    1617
    - `PyType` - a wrapper for Python type objects, that also permits creating new heap types from `TypeSpec`
    1718
    - Improved exception handling:
    1819
    * exceptions can now be converted with codecs
    @@ -879,3 +880,4 @@ This version improves performance on benchmarks significantly compared to 2.3.
    879880
    [i449]: https://github.com/pythonnet/pythonnet/issues/449
    880881
    [i1342]: https://github.com/pythonnet/pythonnet/issues/1342
    881882
    [i238]: https://github.com/pythonnet/pythonnet/issues/238
    883+
    [i1481]: https://github.com/pythonnet/pythonnet/issues/1481

    src/runtime/classderived.cs

    Copy file name to clipboard
    Lines changed: 94 additions & 15 deletions
    Original file line numberDiff line numberDiff line change
    @@ -429,24 +429,33 @@ private static void AddVirtualMethod(MethodInfo method, Type baseType, TypeBuild
    429429
    il.Emit(OpCodes.Ldloc_0);
    430430
    il.Emit(OpCodes.Ldc_I4, i);
    431431
    il.Emit(OpCodes.Ldarg, i + 1);
    432-
    if (parameterTypes[i].IsValueType)
    432+
    var type = parameterTypes[i];
    433+
    if (type.IsByRef)
    433434
    {
    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);
    435441
    }
    436442
    il.Emit(OpCodes.Stelem, typeof(object));
    437443
    }
    438444
    il.Emit(OpCodes.Ldloc_0);
    445+
    446+
    il.Emit(OpCodes.Ldtoken, method);
    439447
    #pragma warning disable CS0618 // PythonDerivedType is for internal use only
    440448
    if (method.ReturnType == typeof(void))
    441449
    {
    442-
    il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod("InvokeMethodVoid"));
    450+
    il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod(nameof(InvokeMethodVoid)));
    443451
    }
    444452
    else
    445453
    {
    446454
    il.Emit(OpCodes.Call,
    447-
    typeof(PythonDerivedType).GetMethod("InvokeMethod").MakeGenericMethod(method.ReturnType));
    455+
    typeof(PythonDerivedType).GetMethod(nameof(InvokeMethod)).MakeGenericMethod(method.ReturnType));
    448456
    }
    449457
    #pragma warning restore CS0618 // PythonDerivedType is for internal use only
    458+
    CodeGenerator.GenerateMarshalByRefsBack(il, parameterTypes);
    450459
    il.Emit(OpCodes.Ret);
    451460
    }
    452461

    @@ -500,35 +509,65 @@ private static void AddPythonMethod(string methodName, PyObject func, TypeBuilde
    500509
    argTypes.ToArray());
    501510

    502511
    ILGenerator il = methodBuilder.GetILGenerator();
    512+
    503513
    il.DeclareLocal(typeof(object[]));
    514+
    il.DeclareLocal(typeof(RuntimeMethodHandle));
    515+
    516+
    // this
    504517
    il.Emit(OpCodes.Ldarg_0);
    518+
    519+
    // Python method to call
    505520
    il.Emit(OpCodes.Ldstr, methodName);
    521+
    522+
    // original method name
    506523
    il.Emit(OpCodes.Ldnull); // don't fall back to the base type's method
    524+
    525+
    // create args array
    507526
    il.Emit(OpCodes.Ldc_I4, argTypes.Count);
    508527
    il.Emit(OpCodes.Newarr, typeof(object));
    509528
    il.Emit(OpCodes.Stloc_0);
    529+
    530+
    // fill args array
    510531
    for (var i = 0; i < argTypes.Count; ++i)
    511532
    {
    512533
    il.Emit(OpCodes.Ldloc_0);
    513534
    il.Emit(OpCodes.Ldc_I4, i);
    514535
    il.Emit(OpCodes.Ldarg, i + 1);
    515-
    if (argTypes[i].IsValueType)
    536+
    var type = argTypes[i];
    537+
    if (type.IsByRef)
    516538
    {
    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);
    518545
    }
    519546
    il.Emit(OpCodes.Stelem, typeof(object));
    520547
    }
    548+
    549+
    // args array
    521550
    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);
    522556
    #pragma warning disable CS0618 // PythonDerivedType is for internal use only
    557+
    558+
    // invoke the method
    523559
    if (returnType == typeof(void))
    524560
    {
    525-
    il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod("InvokeMethodVoid"));
    561+
    il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod(nameof(InvokeMethodVoid)));
    526562
    }
    527563
    else
    528564
    {
    529565
    il.Emit(OpCodes.Call,
    530-
    typeof(PythonDerivedType).GetMethod("InvokeMethod").MakeGenericMethod(returnType));
    566+
    typeof(PythonDerivedType).GetMethod(nameof(InvokeMethod)).MakeGenericMethod(returnType));
    531567
    }
    568+
    569+
    CodeGenerator.GenerateMarshalByRefsBack(il, argTypes);
    570+
    532571
    #pragma warning restore CS0618 // PythonDerivedType is for internal use only
    533572
    il.Emit(OpCodes.Ret);
    534573
    }
    @@ -672,7 +711,8 @@ public class PythonDerivedType
    672711
    /// method binding (i.e. it has been overridden in the derived python
    673712
    /// class) it calls it, otherwise it calls the base method.
    674713
    /// </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)
    676716
    {
    677717
    var self = GetPyObj(obj);
    678718

    @@ -698,8 +738,10 @@ public class PythonDerivedType
    698738
    }
    699739

    700740
    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>();
    703745
    }
    704746
    }
    705747
    }
    @@ -726,7 +768,7 @@ public class PythonDerivedType
    726768
    }
    727769

    728770
    public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, string origMethodName,
    729-
    object[] args)
    771+
    object?[] args, RuntimeMethodHandle methodHandle)
    730772
    {
    731773
    var self = GetPyObj(obj);
    732774
    if (null != self.Ref)
    @@ -736,8 +778,7 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s
    736778
    try
    737779
    {
    738780
    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);
    741782
    if (method.Reference != Runtime.None)
    742783
    {
    743784
    // 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
    752793
    }
    753794

    754795
    PyObject py_result = method.Invoke(pyargs);
    755-
    disposeList.Add(py_result);
    796+
    MarshalByRefsBack(args, methodHandle, py_result, outsOffset: 0);
    756797
    return;
    757798
    }
    758799
    }
    @@ -779,6 +820,44 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s
    779820
    args);
    780821
    }
    781822

    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+
    782861
    public static T? InvokeGetProperty<T>(IPythonDerivedType obj, string propertyName)
    783862
    {
    784863
    var self = GetPyObj(obj);

    src/runtime/codegenerator.cs

    Lines changed: 35 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -1,4 +1,5 @@
    11
    using System;
    2+
    using System.Collections.Generic;
    23
    using System.Reflection;
    34
    using System.Reflection.Emit;
    45
    using System.Threading;
    @@ -42,5 +43,39 @@ internal TypeBuilder DefineType(string name, Type basetype)
    4243
    var attrs = TypeAttributes.Public;
    4344
    return mBuilder.DefineType(name, attrs, basetype);
    4445
    }
    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+
    }
    4580
    }
    4681
    }

    src/runtime/delegatemanager.cs

    Lines changed: 1 addition & 22 deletions
    Original file line numberDiff line numberDiff line change
    @@ -135,28 +135,7 @@ private Type GetDispatcher(Type dtype)
    135135
    if (anyByRef)
    136136
    {
    137137
    // 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);
    160139
    }
    161140

    162141
    if (method.ReturnType == voidtype)

    src/testing/interfacetest.cs

    Lines changed: 14 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -79,4 +79,18 @@ private interface IPrivate
    7979
    {
    8080
    }
    8181
    }
    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+
    }
    8296
    }

    tests/test_interface.py

    Lines changed: 14 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -93,6 +93,20 @@ def test_interface_object_returned_through_out_param():
    9393

    9494
    assert hello2.SayHello() == 'hello 2'
    9595

    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+
    96110

    97111
    def test_null_interface_object_returned():
    98112
    """Test None is used also for methods with interface return types"""

    0 commit comments

    Comments
     (0)
    0