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)) { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy