@@ -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 disableCS0618 // 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 restoreCS0618 // PythonDerivedType is for internal use only
458
+ GenerateMarshalByRefsBack ( il , parameterTypes ) ;
450
459
il . Emit ( OpCodes . Ret ) ;
451
460
}
452
461
@@ -500,40 +509,104 @@ 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 disableCS0618 // 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
+ GenerateMarshalByRefsBack ( il , argTypes ) ;
570
+
532
571
#pragma warning restoreCS0618 // PythonDerivedType is for internal use only
533
572
il . Emit ( OpCodes . Ret ) ;
534
573
}
535
574
}
536
575
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
+
537
610
/// <summary>
538
611
/// Python properties may have the following function attributes set to control how they're exposed:
539
612
/// - _clr_property_type_ - property type (required)
@@ -672,7 +745,8 @@ public class PythonDerivedType
672
745
/// method binding (i.e. it has been overridden in the derived python
673
746
/// class) it calls it, otherwise it calls the base method.
674
747
/// </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 )
676
750
{
677
751
var self = GetPyObj ( obj ) ;
678
752
@@ -698,8 +772,10 @@ public class PythonDerivedType
698
772
}
699
773
700
774
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 notnull
777
+ ? result_tuple [ 0 ] . As < T > ( )
778
+ : py_result . As < T > ( ) ;
703
779
}
704
780
}
705
781
}
@@ -726,7 +802,7 @@ public class PythonDerivedType
726
802
}
727
803
728
804
public static void InvokeMethodVoid ( IPythonDerivedType obj , string methodName , string origMethodName ,
729
- object [ ] args )
805
+ object ? [ ] args , RuntimeMethodHandle methodHandle )
730
806
{
731
807
var self = GetPyObj ( obj ) ;
732
808
if ( null != self . Ref )
@@ -736,8 +812,7 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s
736
812
try
737
813
{
738
814
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 ) ;
741
816
if ( method . Reference != Runtime . None )
742
817
{
743
818
// 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
752
827
}
753
828
754
829
PyObject py_result = method . Invoke ( pyargs ) ;
755
- disposeList . Add ( py_result ) ;
830
+ MarshalByRefsBack ( args , methodHandle , py_result , outsOffset : 0 ) ;
756
831
return ;
757
832
}
758
833
}
@@ -779,6 +854,44 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s
779
854
args ) ;
780
855
}
781
856
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
+
782
895
public static T ? InvokeGetProperty < T > ( IPythonDerivedType obj , string propertyName )
783
896
{
784
897
var self = GetPyObj ( obj ) ;