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

Commitdecc95f

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 commitdecc95f

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 Python 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: 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+
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+
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
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+
CodeGenerator.GenerateMarshalByRefsBack(il,argTypes);
570+
532571
#pragma warning restoreCS0618// 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-
publicstaticT?InvokeMethod<T>(IPythonDerivedTypeobj,stringmethodName,stringorigMethodName,object[]args)
714+
publicstaticT?InvokeMethod<T>(IPythonDerivedTypeobj,stringmethodName,stringorigMethodName,
715+
object[]args,RuntimeMethodHandlemethodHandle)
676716
{
677717
varself=GetPyObj(obj);
678718

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

700740
PyObjectpy_result=method.Invoke(pyargs);
701-
disposeList.Add(py_result);
702-
returnpy_result.As<T>();
741+
PyTuple?result_tuple=MarshalByRefsBack(args,methodHandle,py_result,outsOffset:1);
742+
returnresult_tupleis notnull
743+
?result_tuple[0].As<T>()
744+
:py_result.As<T>();
703745
}
704746
}
705747
}
@@ -726,7 +768,7 @@ public class PythonDerivedType
726768
}
727769

728770
publicstaticvoidInvokeMethodVoid(IPythonDerivedTypeobj,stringmethodName,stringorigMethodName,
729-
object[]args)
771+
object?[]args,RuntimeMethodHandlemethodHandle)
730772
{
731773
varself=GetPyObj(obj);
732774
if(null!=self.Ref)
@@ -736,8 +778,7 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s
736778
try
737779
{
738780
usingvarpyself=newPyObject(self.CheckRun());
739-
PyObjectmethod=pyself.GetAttr(methodName,Runtime.None);
740-
disposeList.Add(method);
781+
usingPyObjectmethod=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
PyObjectpy_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+
privatestaticPyTuple?MarshalByRefsBack(object?[]args,RuntimeMethodHandlemethodHandle,PyObjectpyResult,intoutsOffset)
829+
{
830+
if(methodHandle==default)returnnull;
831+
832+
varoriginalMethod=MethodBase.GetMethodFromHandle(methodHandle);
833+
varparameters=originalMethod.GetParameters();
834+
PyTuple?outs=null;
835+
intbyrefIndex=0;
836+
for(inti=0;i<parameters.Length;++i)
837+
{
838+
Typetype=parameters[i].ParameterType;
839+
if(!type.IsByRef)
840+
{
841+
continue;
842+
}
843+
844+
type=type.GetElementType();
845+
846+
if(outsisnull)
847+
{
848+
outs=newPyTuple(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+
thrownewArgumentException("Too many output parameters");
857+
858+
returnouts;
859+
}
860+
782861
publicstaticT?InvokeGetProperty<T>(IPythonDerivedTypeobj,stringpropertyName)
783862
{
784863
varself=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
usingSystem;
2+
usingSystem.Collections.Generic;
23
usingSystem.Reflection;
34
usingSystem.Reflection.Emit;
45
usingSystem.Threading;
@@ -42,5 +43,39 @@ internal TypeBuilder DefineType(string name, Type basetype)
4243
varattrs=TypeAttributes.Public;
4344
returnmBuilder.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+
internalstaticvoidGenerateMarshalByRefsBack(ILGeneratoril,IReadOnlyList<Type>argTypes)
52+
{
53+
// assumes argument array is in loc_0
54+
for(inti=0;i<argTypes.Count;++i)
55+
{
56+
vartype=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(varc=0;c<signature.Length;c++)
139-
{
140-
Typet=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+
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