@@ -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+ 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 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+ GenerateMarshalByRefsBack ( il , argTypes ) ;
570+
532571#pragma warning restore CS0618 // 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+ private static void GenerateMarshalByRefsBack ( ILGenerator il , IReadOnlyList < Type > argTypes )
581+ {
582+ // assumes argument array is in loc_0
583+ for ( int i = 0 ; i < argTypes . Count ; ++ i )
584+ {
585+ var type = 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- public static T ? InvokeMethod < T > ( IPythonDerivedType obj , string methodName , string origMethodName , object [ ] args )
748+ public static T ? InvokeMethod < T > ( IPythonDerivedType obj , string methodName , string origMethodName ,
749+ object [ ] args , RuntimeMethodHandle methodHandle )
676750 {
677751 var self = GetPyObj ( obj ) ;
678752
@@ -698,8 +772,10 @@ public class PythonDerivedType
698772 }
699773
700774 PyObject py_result = method . Invoke ( pyargs ) ;
701- disposeList . Add ( py_result ) ;
702- return py_result . As < T > ( ) ;
775+ PyTuple ? result_tuple = MarshalByRefsBack ( args , methodHandle , py_result , outsOffset : 1 ) ;
776+ return result_tuple is not null
777+ ? result_tuple [ 0 ] . As < T > ( )
778+ : py_result . As < T > ( ) ;
703779 }
704780 }
705781 }
@@ -726,7 +802,7 @@ public class PythonDerivedType
726802 }
727803
728804 public static void InvokeMethodVoid ( IPythonDerivedType obj , string methodName , string origMethodName ,
729- object [ ] args )
805+ object ? [ ] args , RuntimeMethodHandle methodHandle )
730806 {
731807 var self = GetPyObj ( obj ) ;
732808 if ( null != self . Ref )
@@ -736,8 +812,7 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s
736812 try
737813 {
738814 using var pyself = new PyObject ( self . CheckRun ( ) ) ;
739- PyObject method = pyself . GetAttr ( methodName , Runtime . None ) ;
740- disposeList . Add ( method ) ;
815+ using PyObject method = 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 PyObject py_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+ private static PyTuple ? MarshalByRefsBack ( object ? [ ] args , RuntimeMethodHandle methodHandle , PyObject pyResult , int outsOffset )
863+ {
864+ if ( methodHandle == default ) return null ;
865+
866+ var originalMethod = MethodBase . GetMethodFromHandle ( methodHandle ) ;
867+ var parameters = originalMethod . GetParameters ( ) ;
868+ PyTuple ? outs = null ;
869+ int byrefIndex = 0 ;
870+ for ( int i = 0 ; i < parameters . Length ; ++ i )
871+ {
872+ Type type = parameters [ i ] . ParameterType ;
873+ if ( ! type . IsByRef )
874+ {
875+ continue ;
876+ }
877+
878+ type = type . GetElementType ( ) ;
879+
880+ if ( outs is null )
881+ {
882+ outs = new PyTuple ( 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+ throw new ArgumentException ( "Too many output parameters" ) ;
891+
892+ return outs ;
893+ }
894+
782895 public static T ? InvokeGetProperty < T > ( IPythonDerivedType obj , string propertyName )
783896 {
784897 var self = GetPyObj ( obj ) ;
0 commit comments