Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit893f0c9

Browse files
committed
added support for byref parameters when overriding .NET methods from Python
new code is emitted to1. unpack the tuple returned from Python to extract new values for byref parameters and modify args array correspondingly2. marshal those new values from the args array back into arguments in ILfixes#1481
1 parent88850f5 commit893f0c9

File tree

4 files changed

+158
-15
lines changed

4 files changed

+158
-15
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

Lines changed: 128 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+
vartype=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 disableCS0618// 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 restoreCS0618// PythonDerivedType is for internal use only
458+
GenerateMarshalByRefsBack(il,parameterTypes);
450459
il.Emit(OpCodes.Ret);
451460
}
452461

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

502511
ILGeneratoril=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(vari=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+
vartype=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 disableCS0618// 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+
GenerateMarshalByRefsBack(il,argTypes);
570+
532571
#pragma warning restoreCS0618// PythonDerivedType is for internal use only
533572
il.Emit(OpCodes.Ret);
534573
}
535574
}
536575

576+
/// <summary>
577+
/// Generates code, that copies potentially modified objects in args array
578+
/// back to the corresponding byref arguments
579+
/// </summary>
580+
privatestaticvoidGenerateMarshalByRefsBack(ILGeneratoril,IReadOnlyList<Type>argTypes)
581+
{
582+
// assumes argument array is in loc_0
583+
for(inti=0;i<argTypes.Count;++i)
584+
{
585+
vartype=argTypes[i];
586+
if(type.IsByRef)
587+
{
588+
type=type.GetElementType();
589+
590+
il.Emit(OpCodes.Ldarg,i+1);// for stobj/stind later at the end
591+
592+
il.Emit(OpCodes.Ldloc_0);
593+
il.Emit(OpCodes.Ldc_I4,i);
594+
il.Emit(OpCodes.Ldelem_Ref);
595+
596+
if(type.IsValueType)
597+
{
598+
il.Emit(OpCodes.Unbox_Any,type);
599+
il.Emit(OpCodes.Stobj,type);
600+
}
601+
else
602+
{
603+
il.Emit(OpCodes.Castclass,type);
604+
il.Emit(OpCodes.Stind_Ref);
605+
}
606+
}
607+
}
608+
}
609+
537610
/// <summary>
538611
/// Python properties may have the following function attributes set to control how they're exposed:
539612
/// - _clr_property_type_ - property type (required)
@@ -672,7 +745,8 @@ public class PythonDerivedType
672745
/// method binding (i.e. it has been overridden in the derived python
673746
/// class) it calls it, otherwise it calls the base method.
674747
/// </summary>
675-
publicstaticT?InvokeMethod<T>(IPythonDerivedTypeobj,stringmethodName,stringorigMethodName,object[]args)
748+
publicstaticT?InvokeMethod<T>(IPythonDerivedTypeobj,stringmethodName,stringorigMethodName,
749+
object[]args,RuntimeMethodHandlemethodHandle)
676750
{
677751
varself=GetPyObj(obj);
678752

@@ -698,8 +772,10 @@ public class PythonDerivedType
698772
}
699773

700774
PyObjectpy_result=method.Invoke(pyargs);
701-
disposeList.Add(py_result);
702-
returnpy_result.As<T>();
775+
PyTuple?result_tuple=MarshalByRefsBack(args,methodHandle,py_result,outsOffset:1);
776+
returnresult_tupleis notnull
777+
?result_tuple[0].As<T>()
778+
:py_result.As<T>();
703779
}
704780
}
705781
}
@@ -726,7 +802,7 @@ public class PythonDerivedType
726802
}
727803

728804
publicstaticvoidInvokeMethodVoid(IPythonDerivedTypeobj,stringmethodName,stringorigMethodName,
729-
object[]args)
805+
object?[]args,RuntimeMethodHandlemethodHandle)
730806
{
731807
varself=GetPyObj(obj);
732808
if(null!=self.Ref)
@@ -736,8 +812,7 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s
736812
try
737813
{
738814
usingvarpyself=newPyObject(self.CheckRun());
739-
PyObjectmethod=pyself.GetAttr(methodName,Runtime.None);
740-
disposeList.Add(method);
815+
usingPyObjectmethod=pyself.GetAttr(methodName,Runtime.None);
741816
if(method.Reference!=Runtime.None)
742817
{
743818
// if the method hasn't been overridden then it will be a managed object
@@ -752,7 +827,7 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s
752827
}
753828

754829
PyObjectpy_result=method.Invoke(pyargs);
755-
disposeList.Add(py_result);
830+
MarshalByRefsBack(args,methodHandle,py_result,outsOffset:0);
756831
return;
757832
}
758833
}
@@ -779,6 +854,44 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s
779854
args);
780855
}
781856

857+
/// <summary>
858+
/// If the method has byref arguments, reinterprets Python return value
859+
/// as a tuple of new values for those arguments, and updates corresponding
860+
/// elements of <paramref name="args"/> array.
861+
/// </summary>
862+
privatestaticPyTuple?MarshalByRefsBack(object?[]args,RuntimeMethodHandlemethodHandle,PyObjectpyResult,intoutsOffset)
863+
{
864+
if(methodHandle==default)returnnull;
865+
866+
varoriginalMethod=MethodBase.GetMethodFromHandle(methodHandle);
867+
varparameters=originalMethod.GetParameters();
868+
PyTuple?outs=null;
869+
intbyrefIndex=0;
870+
for(inti=0;i<parameters.Length;++i)
871+
{
872+
Typetype=parameters[i].ParameterType;
873+
if(!type.IsByRef)
874+
{
875+
continue;
876+
}
877+
878+
type=type.GetElementType();
879+
880+
if(outsisnull)
881+
{
882+
outs=newPyTuple(pyResult);
883+
pyResult.Dispose();
884+
}
885+
886+
args[i]=outs[byrefIndex+outsOffset].AsManagedObject(type);
887+
byrefIndex++;
888+
}
889+
if(byrefIndex>0&&outs!.Length()>byrefIndex+outsOffset)
890+
thrownewArgumentException("Too many output parameters");
891+
892+
returnouts;
893+
}
894+
782895
publicstaticT?InvokeGetProperty<T>(IPythonDerivedTypeobj,stringpropertyName)
783896
{
784897
varself=GetPyObj(obj);

‎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+
publicinterfaceIOutArg
84+
{
85+
stringMyMethod_Out(stringname,outintindex);
86+
}
87+
88+
publicclassOutArgCaller
89+
{
90+
publicstaticintCallMyMethod_Out(IOutArgmyInterface)
91+
{
92+
myInterface.MyMethod_Out("myclient",outintindex);
93+
returnindex;
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
asserthello2.SayHello()=='hello 2'
9595

96+
deftest_interface_out_param_python_impl():
97+
fromPython.TestimportIOutArg,OutArgCaller
98+
99+
classMyOutImpl(IOutArg):
100+
__namespace__="Python.Test"
101+
102+
defMyMethod_Out(self,name,index):
103+
other_index=101
104+
return ('MyName',other_index)
105+
106+
py_impl=MyOutImpl()
107+
108+
assert101==OutArgCaller.CallMyMethod_Out(py_impl)
109+
96110

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

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp