From c355bd6995487eb5d0c993821f97a02207290a4c Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 28 Mar 2019 13:10:23 -0700 Subject: [PATCH 01/10] introduced IPyArgumentConverter interface, that controls marshaling of Python objects when calling .NET methods --- src/runtime/methodbinder.cs | 11 +++++--- src/runtime/pyargconverter.cs | 47 +++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 src/runtime/pyargconverter.cs diff --git a/src/runtime/methodbinder.cs b/src/runtime/methodbinder.cs index 8a7fc1930..360461eee 100644 --- a/src/runtime/methodbinder.cs +++ b/src/runtime/methodbinder.cs @@ -19,6 +19,7 @@ internal class MethodBinder public MethodBase[] methods; public bool init = false; public bool allow_threads = true; + readonly IPyArgumentConverter pyArgumentConverter = DefaultPyArgumentConverter.Instance; internal MethodBinder() { @@ -326,7 +327,7 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth continue; } var outs = 0; - var margs = TryConvertArguments(pi, paramsArray, args, pynargs, kwargDict, defaultArgList, + var margs = this.TryConvertArguments(pi, paramsArray, args, pynargs, kwargDict, defaultArgList, needsResolution: _methods.Length > 1, outs: out outs); @@ -382,7 +383,7 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth /// true, if overloading resolution is required /// Returns number of output parameters /// An array of .NET arguments, that can be passed to a method. - static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, + object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, IntPtr args, int pyArgCount, Dictionary kwargDict, ArrayList defaultArgList, @@ -423,7 +424,9 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, } bool isOut; - if (!TryConvertArgument(op, parameter.ParameterType, needsResolution, out margs[paramIndex], out isOut)) + if (!this.pyArgumentConverter.TryConvertArgument( + op, parameter.ParameterType, needsResolution, + out margs[paramIndex], out isOut)) { return null; } @@ -445,7 +448,7 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, return margs; } - static bool TryConvertArgument(IntPtr op, Type parameterType, bool needsResolution, + internal static bool TryConvertArgument(IntPtr op, Type parameterType, bool needsResolution, out object arg, out bool isOut) { arg = null; diff --git a/src/runtime/pyargconverter.cs b/src/runtime/pyargconverter.cs new file mode 100644 index 000000000..16d8ed551 --- /dev/null +++ b/src/runtime/pyargconverter.cs @@ -0,0 +1,47 @@ +namespace Python.Runtime { + using System; + + /// + /// Specifies how to convert Python objects, passed to .NET functions to the expected CLR types. + /// + public interface IPyArgumentConverter + { + /// + /// Attempts to convert an argument passed by Python to the specified parameter type. + /// + /// Unmanaged pointer to the Python argument value + /// The expected type of the parameter + /// true if the method is overloaded + /// This parameter will receive the converted value, matching the specified type + /// This parameter will be set to true, + /// if the final type needs to be marshaled as an out argument. + /// true, if the object matches requested type, + /// and conversion was successful, otherwise false + bool TryConvertArgument(IntPtr pyarg, Type parameterType, + bool needsResolution, out object arg, out bool isOut); + } + + public class DefaultPyArgumentConverter: IPyArgumentConverter { + public static DefaultPyArgumentConverter Instance { get; }= new DefaultPyArgumentConverter(); + + /// + /// Attempts to convert an argument passed by Python to the specified parameter type. + /// + /// Unmanaged pointer to the Python argument value + /// The expected type of the parameter + /// true if the method is overloaded + /// This parameter will receive the converted value, matching the specified type + /// This parameter will be set to true, + /// if the final type needs to be marshaled as an out argument. + /// true, if the object matches requested type, + /// and conversion was successful, otherwise false + public virtual bool TryConvertArgument( + IntPtr pyarg, Type parameterType, bool needsResolution, + out object arg, out bool isOut) + { + return MethodBinder.TryConvertArgument( + pyarg, parameterType, needsResolution, + out arg, out isOut); + } + } +} From b5cae6cd361d2357474527761103b25222140744 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 28 Mar 2019 15:52:38 -0700 Subject: [PATCH 02/10] enabled an ability to override Python to .NET argument coversion using PyArgConverter attribute --- src/embed_tests/TestCustomArgMarshal.cs | 55 +++++++++++++++++++++++++ src/runtime/methodbinder.cs | 39 ++++++++++++++++-- src/runtime/pyargconverter.cs | 44 +++++++++++++++++++- 3 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 src/embed_tests/TestCustomArgMarshal.cs diff --git a/src/embed_tests/TestCustomArgMarshal.cs b/src/embed_tests/TestCustomArgMarshal.cs new file mode 100644 index 000000000..dc8ea36c0 --- /dev/null +++ b/src/embed_tests/TestCustomArgMarshal.cs @@ -0,0 +1,55 @@ +using System; +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest +{ + class TestCustomArgMarshal + { + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + [Test] + public void CustomArgMarshaller() + { + var obj = new CustomArgMarshaling(); + using (Py.GIL()) { + dynamic callWithInt = PythonEngine.Eval("lambda o: o.CallWithInt('42')"); + callWithInt(obj.ToPython()); + } + Assert.AreEqual(expected: 42, actual: obj.LastArgument); + } + } + +[PyArgConverter(typeof(CustomArgConverter))] +class CustomArgMarshaling { + public object LastArgument { get; private set; } + public void CallWithInt(int value) => this.LastArgument = value; +} + + class CustomArgConverter : DefaultPyArgumentConverter { + public override bool TryConvertArgument(IntPtr pyarg, Type parameterType, bool needsResolution, + out object arg, out bool isOut) { + if (parameterType != typeof(int)) + return base.TryConvertArgument(pyarg, parameterType, needsResolution, out arg, out isOut); + + bool isString = base.TryConvertArgument(pyarg, typeof(string), needsResolution, + out arg, out isOut); + if (!isString) return false; + + int number; + if (!int.TryParse((string)arg, out number)) return false; + arg = number; + return true; + } + } +} diff --git a/src/runtime/methodbinder.cs b/src/runtime/methodbinder.cs index 360461eee..c66c5e788 100644 --- a/src/runtime/methodbinder.cs +++ b/src/runtime/methodbinder.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Diagnostics; using System.Reflection; using System.Text; using System.Collections.Generic; @@ -7,6 +8,8 @@ namespace Python.Runtime { + using System.Linq; + /// /// A MethodBinder encapsulates information about a (possibly overloaded) /// managed method, and is responsible for selecting the right method given @@ -19,16 +22,16 @@ internal class MethodBinder public MethodBase[] methods; public bool init = false; public bool allow_threads = true; - readonly IPyArgumentConverter pyArgumentConverter = DefaultPyArgumentConverter.Instance; + IPyArgumentConverter pyArgumentConverter; internal MethodBinder() { list = new ArrayList(); } - internal MethodBinder(MethodInfo mi) + internal MethodBinder(MethodInfo mi): this() { - list = new ArrayList { mi }; + this.AddMethod(mi); } public int Count @@ -38,6 +41,7 @@ public int Count internal void AddMethod(MethodBase m) { + Debug.Assert(!init); list.Add(m); } @@ -165,11 +169,40 @@ internal MethodBase[] GetMethods() // I'm sure this could be made more efficient. list.Sort(new MethodSorter()); methods = (MethodBase[])list.ToArray(typeof(MethodBase)); + pyArgumentConverter = this.GetArgumentConverter(); init = true; } return methods; } + IPyArgumentConverter GetArgumentConverter() { + IPyArgumentConverter converter = null; + Type converterType = null; + foreach (MethodBase method in this.methods) + { + var attribute = method.DeclaringType? + .GetCustomAttributes(typeof(PyArgConverterAttribute), inherit: false) + .OfType() + .SingleOrDefault() + ?? method.DeclaringType?.Assembly + .GetCustomAttributes(typeof(PyArgConverterAttribute), inherit: false) + .OfType() + .SingleOrDefault(); + if (converterType == null) + { + if (attribute == null) continue; + + converterType = attribute.ConverterType; + converter = attribute.Converter; + } else if (converterType != attribute?.ConverterType) + { + throw new NotSupportedException("All methods must have the same IPyArgumentConverter"); + } + } + + return converter ?? DefaultPyArgumentConverter.Instance; + } + /// /// Precedence algorithm largely lifted from Jython - the concerns are /// generally the same so we'll start with this and tweak as necessary. diff --git a/src/runtime/pyargconverter.cs b/src/runtime/pyargconverter.cs index 16d8ed551..69dfc714f 100644 --- a/src/runtime/pyargconverter.cs +++ b/src/runtime/pyargconverter.cs @@ -21,8 +21,14 @@ bool TryConvertArgument(IntPtr pyarg, Type parameterType, bool needsResolution, out object arg, out bool isOut); } + /// + /// The implementation of used by default + /// public class DefaultPyArgumentConverter: IPyArgumentConverter { - public static DefaultPyArgumentConverter Instance { get; }= new DefaultPyArgumentConverter(); + /// + /// Gets the singleton instance. + /// + public static DefaultPyArgumentConverter Instance { get; } = new DefaultPyArgumentConverter(); /// /// Attempts to convert an argument passed by Python to the specified parameter type. @@ -44,4 +50,40 @@ public virtual bool TryConvertArgument( out arg, out isOut); } } + + /// + /// Specifies an argument converter to be used, when methods in this class/assembly are called from Python. + /// + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Struct)] + public class PyArgConverterAttribute : Attribute + { + static readonly Type[] EmptyArgTypeList = new Type[0]; + static readonly object[] EmptyArgList = new object[0]; + + /// + /// Gets the instance of the converter, that will be used when calling methods + /// of this class/assembly from Python + /// + public IPyArgumentConverter Converter { get; } + /// + /// Gets the type of the converter, that will be used when calling methods + /// of this class/assembly from Python + /// + public Type ConverterType { get; } + + /// + /// Specifies an argument converter to be used, when methods + /// in this class/assembly are called from Python. + /// + /// Type of the converter to use. + /// Must implement . + public PyArgConverterAttribute(Type converterType) + { + if (converterType == null) throw new ArgumentNullException(nameof(converterType)); + var ctor = converterType.GetConstructor(EmptyArgTypeList); + if (ctor == null) throw new ArgumentException("Specified converter must have public parameterless constructor"); + this.Converter = (IPyArgumentConverter)ctor.Invoke(EmptyArgList); + this.ConverterType = converterType; + } + } } From bea9e0dfba3b80b6520f7457d73d67ba6d6d0624 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 28 Mar 2019 16:36:12 -0700 Subject: [PATCH 03/10] mentioned PyArgConverter in the CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fd2b1dcf..9febc7974 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added function that sets Py_NoSiteFlag to 1. - Added support for Jetson Nano. - Added support for __len__ for .NET classes that implement ICollection +- Added `IPyArgumentConverter` interface and `PyArgConverter` attribute, that control custom argument marshaling from Python to .NET (#835) ### Changed From 11a3ee636581ad29bd99d775a784585d568bf0a9 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 28 Mar 2019 18:25:48 -0700 Subject: [PATCH 04/10] fixed pyargconverter.cs not included into the older project file --- src/runtime/Python.Runtime.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 0c2f912de..9d7e45ac0 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -122,6 +122,7 @@ + From 9c81661fce5d33a587b7e2c94cdfa6bfdd87ad03 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Sun, 14 Apr 2019 14:46:39 -0700 Subject: [PATCH 05/10] moved default arg converter to its own file; cached converter lookup --- src/runtime/defaultpyargconverter.cs | 111 ++++++++++++++++++++++++ src/runtime/methodbinder.cs | 121 +++++---------------------- src/runtime/pyargconverter.cs | 30 ------- 3 files changed, 131 insertions(+), 131 deletions(-) create mode 100644 src/runtime/defaultpyargconverter.cs diff --git a/src/runtime/defaultpyargconverter.cs b/src/runtime/defaultpyargconverter.cs new file mode 100644 index 000000000..b2dd5d236 --- /dev/null +++ b/src/runtime/defaultpyargconverter.cs @@ -0,0 +1,111 @@ +namespace Python.Runtime { + using System; + + /// + /// The implementation of used by default + /// + public class DefaultPyArgumentConverter: IPyArgumentConverter + { + /// + /// Gets the singleton instance. + /// + public static DefaultPyArgumentConverter Instance { get; } = new DefaultPyArgumentConverter(); + + /// + /// + /// Attempts to convert an argument passed by Python to the specified parameter type. + /// + /// Unmanaged pointer to the Python argument value + /// The expected type of the parameter + /// true if the method is overloaded + /// This parameter will receive the converted value, matching the specified type + /// This parameter will be set to true, + /// if the final type needs to be marshaled as an out argument. + /// true, if the object matches requested type, + /// and conversion was successful, otherwise false + public virtual bool TryConvertArgument( + IntPtr pyarg, Type parameterType, bool needsResolution, + out object arg, out bool isOut) + { + arg = null; + isOut = false; + Type clrType = TryComputeClrArgumentType(parameterType, pyarg, needsResolution: needsResolution); + if (clrType == null) + { + return false; + } + + if (!Converter.ToManaged(pyarg, clrType, out arg, false)) + { + Exceptions.Clear(); + return false; + } + + isOut = clrType.IsByRef; + return true; + } + + static Type TryComputeClrArgumentType(Type parameterType, IntPtr argument, bool needsResolution) + { + // this logic below handles cases when multiple overloading methods + // are ambiguous, hence comparison between Python and CLR types + // is necessary + Type clrType = null; + IntPtr pyArgType; + if (needsResolution) + { + // HACK: each overload should be weighted in some way instead + pyArgType = Runtime.PyObject_Type(argument); + Exceptions.Clear(); + if (pyArgType != IntPtr.Zero) + { + clrType = Converter.GetTypeByAlias(pyArgType); + } + Runtime.XDecref(pyArgType); + } + + if (clrType != null) + { + if ((parameterType != typeof(object)) && (parameterType != clrType)) + { + IntPtr pyParamType = Converter.GetPythonTypeByAlias(parameterType); + pyArgType = Runtime.PyObject_Type(argument); + Exceptions.Clear(); + + bool typeMatch = false; + if (pyArgType != IntPtr.Zero && pyParamType == pyArgType) + { + typeMatch = true; + clrType = parameterType; + } + if (!typeMatch) + { + // this takes care of enum values + TypeCode argTypeCode = Type.GetTypeCode(parameterType); + TypeCode paramTypeCode = Type.GetTypeCode(clrType); + if (argTypeCode == paramTypeCode) + { + typeMatch = true; + clrType = parameterType; + } + } + Runtime.XDecref(pyArgType); + if (!typeMatch) + { + return null; + } + } + else + { + clrType = parameterType; + } + } + else + { + clrType = parameterType; + } + + return clrType; + } + } +} diff --git a/src/runtime/methodbinder.cs b/src/runtime/methodbinder.cs index c66c5e788..96d0cfc5c 100644 --- a/src/runtime/methodbinder.cs +++ b/src/runtime/methodbinder.cs @@ -1,6 +1,8 @@ using System; using System.Collections; +using System.Collections.Concurrent; using System.Diagnostics; +using System.Linq; using System.Reflection; using System.Text; using System.Collections.Generic; @@ -8,8 +10,6 @@ namespace Python.Runtime { - using System.Linq; - /// /// A MethodBinder encapsulates information about a (possibly overloaded) /// managed method, and is responsible for selecting the right method given @@ -180,14 +180,7 @@ IPyArgumentConverter GetArgumentConverter() { Type converterType = null; foreach (MethodBase method in this.methods) { - var attribute = method.DeclaringType? - .GetCustomAttributes(typeof(PyArgConverterAttribute), inherit: false) - .OfType() - .SingleOrDefault() - ?? method.DeclaringType?.Assembly - .GetCustomAttributes(typeof(PyArgConverterAttribute), inherit: false) - .OfType() - .SingleOrDefault(); + PyArgConverterAttribute attribute = TryGetArgConverter(method.DeclaringType); if (converterType == null) { if (attribute == null) continue; @@ -203,6 +196,23 @@ IPyArgumentConverter GetArgumentConverter() { return converter ?? DefaultPyArgumentConverter.Instance; } + static readonly ConcurrentDictionary ArgConverterCache = + new ConcurrentDictionary(); + static PyArgConverterAttribute TryGetArgConverter(Type type) { + if (type == null) return null; + + return ArgConverterCache.GetOrAdd(type, declaringType => + declaringType + .GetCustomAttributes(typeof(PyArgConverterAttribute), inherit: false) + .OfType() + .SingleOrDefault() + ?? declaringType.Assembly + .GetCustomAttributes(typeof(PyArgConverterAttribute), inherit: false) + .OfType() + .SingleOrDefault() + ); + } + /// /// Precedence algorithm largely lifted from Jython - the concerns are /// generally the same so we'll start with this and tweak as necessary. @@ -481,97 +491,6 @@ object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, return margs; } - internal static bool TryConvertArgument(IntPtr op, Type parameterType, bool needsResolution, - out object arg, out bool isOut) - { - arg = null; - isOut = false; - var clrtype = TryComputeClrArgumentType(parameterType, op, needsResolution: needsResolution); - if (clrtype == null) - { - return false; - } - - if (!Converter.ToManaged(op, clrtype, out arg, false)) - { - Exceptions.Clear(); - return false; - } - - isOut = clrtype.IsByRef; - return true; - } - - static Type TryComputeClrArgumentType(Type parameterType, IntPtr argument, bool needsResolution) - { - // this logic below handles cases when multiple overloading methods - // are ambiguous, hence comparison between Python and CLR types - // is necessary - Type clrtype = null; - IntPtr pyoptype; - if (needsResolution) - { - // HACK: each overload should be weighted in some way instead - pyoptype = Runtime.PyObject_Type(argument); - Exceptions.Clear(); - if (pyoptype != IntPtr.Zero) - { - clrtype = Converter.GetTypeByAlias(pyoptype); - } - Runtime.XDecref(pyoptype); - } - - if (clrtype != null) - { - var typematch = false; - if ((parameterType != typeof(object)) && (parameterType != clrtype)) - { - IntPtr pytype = Converter.GetPythonTypeByAlias(parameterType); - pyoptype = Runtime.PyObject_Type(argument); - Exceptions.Clear(); - if (pyoptype != IntPtr.Zero) - { - if (pytype != pyoptype) - { - typematch = false; - } - else - { - typematch = true; - clrtype = parameterType; - } - } - if (!typematch) - { - // this takes care of enum values - TypeCode argtypecode = Type.GetTypeCode(parameterType); - TypeCode paramtypecode = Type.GetTypeCode(clrtype); - if (argtypecode == paramtypecode) - { - typematch = true; - clrtype = parameterType; - } - } - Runtime.XDecref(pyoptype); - if (!typematch) - { - return null; - } - } - else - { - typematch = true; - clrtype = parameterType; - } - } - else - { - clrtype = parameterType; - } - - return clrtype; - } - static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] parameters, Dictionary kwargDict, out bool paramsArray, diff --git a/src/runtime/pyargconverter.cs b/src/runtime/pyargconverter.cs index 69dfc714f..cf6be7b6d 100644 --- a/src/runtime/pyargconverter.cs +++ b/src/runtime/pyargconverter.cs @@ -21,36 +21,6 @@ bool TryConvertArgument(IntPtr pyarg, Type parameterType, bool needsResolution, out object arg, out bool isOut); } - /// - /// The implementation of used by default - /// - public class DefaultPyArgumentConverter: IPyArgumentConverter { - /// - /// Gets the singleton instance. - /// - public static DefaultPyArgumentConverter Instance { get; } = new DefaultPyArgumentConverter(); - - /// - /// Attempts to convert an argument passed by Python to the specified parameter type. - /// - /// Unmanaged pointer to the Python argument value - /// The expected type of the parameter - /// true if the method is overloaded - /// This parameter will receive the converted value, matching the specified type - /// This parameter will be set to true, - /// if the final type needs to be marshaled as an out argument. - /// true, if the object matches requested type, - /// and conversion was successful, otherwise false - public virtual bool TryConvertArgument( - IntPtr pyarg, Type parameterType, bool needsResolution, - out object arg, out bool isOut) - { - return MethodBinder.TryConvertArgument( - pyarg, parameterType, needsResolution, - out arg, out isOut); - } - } - /// /// Specifies an argument converter to be used, when methods in this class/assembly are called from Python. /// From b6421e92c6539ed9d96b44bf31bcbab325612ca8 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Wed, 17 Apr 2019 14:41:32 -0700 Subject: [PATCH 06/10] fixed defaultpyargconverter.cs not included in the older project file --- src/runtime/Python.Runtime.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 9d7e45ac0..4d959e6ab 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -94,6 +94,7 @@ + From 6cd7bd449fa1f052ccd1553a3d7ef6bcd885d527 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 13 Jun 2019 14:27:50 -0700 Subject: [PATCH 07/10] when looking for a custom argument converter look at base types too --- src/runtime/methodbinder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/methodbinder.cs b/src/runtime/methodbinder.cs index 96d0cfc5c..9ca87eb5d 100644 --- a/src/runtime/methodbinder.cs +++ b/src/runtime/methodbinder.cs @@ -203,11 +203,11 @@ static PyArgConverterAttribute TryGetArgConverter(Type type) { return ArgConverterCache.GetOrAdd(type, declaringType => declaringType - .GetCustomAttributes(typeof(PyArgConverterAttribute), inherit: false) + .GetCustomAttributes(typeof(PyArgConverterAttribute), inherit: true) .OfType() .SingleOrDefault() ?? declaringType.Assembly - .GetCustomAttributes(typeof(PyArgConverterAttribute), inherit: false) + .GetCustomAttributes(typeof(PyArgConverterAttribute), inherit: true) .OfType() .SingleOrDefault() ); From 56ca4c995a0018ca48123bebfe4485b144a6f9de Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 13 Jun 2019 14:30:01 -0700 Subject: [PATCH 08/10] a test to ensure it is possible to override argument conversion for inherited methods using an attribute on a derived class --- src/embed_tests/TestCustomArgMarshal.cs | 39 +++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/embed_tests/TestCustomArgMarshal.cs b/src/embed_tests/TestCustomArgMarshal.cs index dc8ea36c0..419b03c9f 100644 --- a/src/embed_tests/TestCustomArgMarshal.cs +++ b/src/embed_tests/TestCustomArgMarshal.cs @@ -28,13 +28,27 @@ public void CustomArgMarshaller() } Assert.AreEqual(expected: 42, actual: obj.LastArgument); } + + [Test] + public void MarshallerOverride() { + var obj = new DerivedMarshaling(); + using (Py.GIL()) { + dynamic callWithInt = PythonEngine.Eval("lambda o: o.CallWithInt({ 'value': 42 })"); + callWithInt(obj.ToPython()); + } + Assert.AreEqual(expected: 42, actual: obj.LastArgument); + } } -[PyArgConverter(typeof(CustomArgConverter))] -class CustomArgMarshaling { - public object LastArgument { get; private set; } - public void CallWithInt(int value) => this.LastArgument = value; -} + [PyArgConverter(typeof(CustomArgConverter))] + class CustomArgMarshaling { + public object LastArgument { get; protected set; } + public virtual void CallWithInt(int value) => this.LastArgument = value; + } + + // this should override original custom marshaling behavior + [PyArgConverter(typeof(CustomArgConverter2))] + class DerivedMarshaling: CustomArgMarshaling { } class CustomArgConverter : DefaultPyArgumentConverter { public override bool TryConvertArgument(IntPtr pyarg, Type parameterType, bool needsResolution, @@ -52,4 +66,19 @@ public override bool TryConvertArgument(IntPtr pyarg, Type parameterType, bool n return true; } } + + class CustomArgConverter2 : DefaultPyArgumentConverter { + public override bool TryConvertArgument(IntPtr pyarg, Type parameterType, bool needsResolution, + out object arg, out bool isOut) { + if (parameterType != typeof(int)) + return base.TryConvertArgument(pyarg, parameterType, needsResolution, out arg, out isOut); + bool isPyObject = base.TryConvertArgument(pyarg, typeof(PyObject), needsResolution, + out arg, out isOut); + if (!isPyObject) return false; + var dict = new PyDict((PyObject)arg); + int number = (dynamic)dict["value"]; + arg = number; + return true; + } + } } From 7f894fad582f7f272ba9c81024c3f22926605464 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 20 Jun 2019 12:37:18 -0700 Subject: [PATCH 09/10] fixed MarshallerOverride test --- src/embed_tests/TestCustomArgMarshal.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/embed_tests/TestCustomArgMarshal.cs b/src/embed_tests/TestCustomArgMarshal.cs index 419b03c9f..d9f22ef9d 100644 --- a/src/embed_tests/TestCustomArgMarshal.cs +++ b/src/embed_tests/TestCustomArgMarshal.cs @@ -46,9 +46,13 @@ class CustomArgMarshaling { public virtual void CallWithInt(int value) => this.LastArgument = value; } - // this should override original custom marshaling behavior + // this should override original custom marshaling behavior for any new methods [PyArgConverter(typeof(CustomArgConverter2))] - class DerivedMarshaling: CustomArgMarshaling { } + class DerivedMarshaling : CustomArgMarshaling { + public override void CallWithInt(int value) { + base.CallWithInt(value); + } + } class CustomArgConverter : DefaultPyArgumentConverter { public override bool TryConvertArgument(IntPtr pyarg, Type parameterType, bool needsResolution, From aaafea82a0af6107a009544ec91c69eeec14bdf1 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 22 Aug 2019 16:56:53 -0700 Subject: [PATCH 10/10] fixed crash in MethodBinder.Bind when MethodInfo to invoke is passed explicitly --- src/runtime/methodbinder.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/runtime/methodbinder.cs b/src/runtime/methodbinder.cs index 9ca87eb5d..ca69a2ba4 100644 --- a/src/runtime/methodbinder.cs +++ b/src/runtime/methodbinder.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; @@ -169,16 +170,16 @@ internal MethodBase[] GetMethods() // I'm sure this could be made more efficient. list.Sort(new MethodSorter()); methods = (MethodBase[])list.ToArray(typeof(MethodBase)); - pyArgumentConverter = this.GetArgumentConverter(); + pyArgumentConverter = GetArgumentConverter(this.methods); init = true; } return methods; } - IPyArgumentConverter GetArgumentConverter() { + static IPyArgumentConverter GetArgumentConverter(IEnumerable methods) { IPyArgumentConverter converter = null; Type converterType = null; - foreach (MethodBase method in this.methods) + foreach (MethodBase method in methods) { PyArgConverterAttribute attribute = TryGetArgConverter(method.DeclaringType); if (converterType == null) @@ -344,14 +345,17 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth var pynargs = (int)Runtime.PyTuple_Size(args); var isGeneric = false; + IPyArgumentConverter argumentConverter; if (info != null) { _methods = new MethodBase[1]; _methods.SetValue(info, 0); + argumentConverter = GetArgumentConverter(_methods); } else { _methods = GetMethods(); + argumentConverter = this.pyArgumentConverter; } // TODO: Clean up @@ -370,7 +374,8 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth continue; } var outs = 0; - var margs = this.TryConvertArguments(pi, paramsArray, args, pynargs, kwargDict, defaultArgList, + var margs = TryConvertArguments(pi, paramsArray, argumentConverter, + args, pynargs, kwargDict, defaultArgList, needsResolution: _methods.Length > 1, outs: out outs); @@ -426,7 +431,8 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth /// true, if overloading resolution is required /// Returns number of output parameters /// An array of .NET arguments, that can be passed to a method. - object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, + static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, + IPyArgumentConverter argumentConverter, IntPtr args, int pyArgCount, Dictionary kwargDict, ArrayList defaultArgList, @@ -467,7 +473,7 @@ object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, } bool isOut; - if (!this.pyArgumentConverter.TryConvertArgument( + if (!argumentConverter.TryConvertArgument( op, parameter.ParameterType, needsResolution, out margs[paramIndex], out isOut)) {