From 668d6394a1d2978bbce939fb0775312eb5112d48 Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Wed, 29 Jan 2020 18:05:22 -0600 Subject: [PATCH 1/6] attempt to add python function decoder --- src/embed_tests/Codecs.cs | 63 ++++++++++++++++++++++------- src/runtime/Codecs/FunctionCodec.cs | 44 ++++++++++++++++++++ 2 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 src/runtime/Codecs/FunctionCodec.cs diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index 600215cf0..25a5ebb87 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -6,28 +6,34 @@ namespace Python.EmbeddingTest { using Python.Runtime; using Python.Runtime.Codecs; - public class Codecs { + public class Codecs + { [SetUp] - public void SetUp() { + public void SetUp() + { PythonEngine.Initialize(); } [TearDown] - public void Dispose() { + public void Dispose() + { PythonEngine.Shutdown(); } [Test] - public void ConversionsGeneric() { + public void ConversionsGeneric() + { ConversionsGeneric, ValueTuple>(); } - static void ConversionsGeneric() { + static void ConversionsGeneric() + { TupleCodec.Register(); var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); T restored = default; using (Py.GIL()) - using (var scope = Py.CreateScope()) { + using (var scope = Py.CreateScope()) + { void Accept(T value) => restored = value; var accept = new Action(Accept).ToPython(); scope.Set(nameof(tuple), tuple); @@ -38,15 +44,18 @@ static void ConversionsGeneric() { } [Test] - public void ConversionsObject() { + public void ConversionsObject() + { ConversionsObject, ValueTuple>(); } - static void ConversionsObject() { + static void ConversionsObject() + { TupleCodec.Register(); var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); T restored = default; using (Py.GIL()) - using (var scope = Py.CreateScope()) { + using (var scope = Py.CreateScope()) + { void Accept(object value) => restored = (T)value; var accept = new Action(Accept).ToPython(); scope.Set(nameof(tuple), tuple); @@ -57,12 +66,15 @@ static void ConversionsObject() { } [Test] - public void TupleRoundtripObject() { + public void TupleRoundtripObject() + { TupleRoundtripObject, ValueTuple>(); } - static void TupleRoundtripObject() { + static void TupleRoundtripObject() + { var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); - using (Py.GIL()) { + using (Py.GIL()) + { var pyTuple = TupleCodec.Instance.TryEncode(tuple); Assert.IsTrue(TupleCodec.Instance.TryDecode(pyTuple, out object restored)); Assert.AreEqual(expected: tuple, actual: restored); @@ -70,17 +82,38 @@ static void TupleRoundtripObject() { } [Test] - public void TupleRoundtripGeneric() { + public void TupleRoundtripGeneric() + { TupleRoundtripGeneric, ValueTuple>(); } - static void TupleRoundtripGeneric() { + static void TupleRoundtripGeneric() + { var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); - using (Py.GIL()) { + using (Py.GIL()) + { var pyTuple = TupleCodec.Instance.TryEncode(tuple); Assert.IsTrue(TupleCodec.Instance.TryDecode(pyTuple, out T restored)); Assert.AreEqual(expected: tuple, actual: restored); } } + + [Test] + public void Function() + { + FunctionCodec.Register(); + var locals = new PyDict(); + + PythonEngine.Exec(@" +def foo(): + return 1 +", null, locals.Handle); + + var func = locals.GetItem("foo"); + + Action action; + Assert.IsTrue(FunctionCodec.Instance.TryDecode(func, out action)); + Assert.DoesNotThrow(() => action()); + } } } diff --git a/src/runtime/Codecs/FunctionCodec.cs b/src/runtime/Codecs/FunctionCodec.cs new file mode 100644 index 000000000..84b5bf12f --- /dev/null +++ b/src/runtime/Codecs/FunctionCodec.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Python.Runtime.Codecs +{ + //converts python functions to C# actions + class FunctionCodec : IPyObjectDecoder + { + public static FunctionCodec Instance { get; } = new FunctionCodec(); + public bool CanDecode(PyObject objectType, Type targetType) + { + if (!objectType.IsCallable()) return false; + + //TODO - handle nonzero arguments. + var args = targetType.GetGenericArguments(); + return args.Length == 0; + } + + public bool TryDecode(PyObject pyObj, out T value) + { + Action action = () => + { + Runtime.XIncref(pyObj.Handle); + PyObject pyAction = new PyObject(pyObj.Handle); + var pyArgs = new PyObject[0]; + using (Py.GIL()) + { + var pyResult = pyAction.Invoke(pyArgs); + Runtime.XIncref(pyResult.Handle); + } + }; + var v = (object)action; + value = (T)v; + return true; + } + + public static void Register() + { + PyObjectConversions.RegisterDecoder(Instance); + } + } +} From e6654244b9b4cb8f6e46465ad1d8daadd0573c1e Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Sat, 1 Feb 2020 16:27:24 -0600 Subject: [PATCH 2/6] decoder for actions --- src/embed_tests/Codecs.cs | 29 ++++++-- src/runtime/Codecs/FunctionCodec.cs | 102 ++++++++++++++++++++++++---- 2 files changed, 113 insertions(+), 18 deletions(-) diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index 25a5ebb87..7005d2213 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -101,19 +101,40 @@ static void TupleRoundtripGeneric() [Test] public void Function() { + FunctionCodec.Register(); + var codec = FunctionCodec.Instance; var locals = new PyDict(); + //non-callables can't be decoded into Action + Assert.IsFalse(codec.CanDecode(locals, typeof(Action))); + PythonEngine.Exec(@" def foo(): return 1 +def bar(a): + return 2 ", null, locals.Handle); - var func = locals.GetItem("foo"); + //foo + var fooFunc = locals.GetItem("foo"); + Assert.IsFalse(codec.CanDecode(fooFunc, typeof(bool))); + Assert.IsFalse(codec.CanDecode(fooFunc, typeof(Action))); + Assert.IsTrue(codec.CanDecode(fooFunc, typeof(Action))); + + Action fooAction; + Assert.IsTrue(codec.TryDecode(fooFunc, out fooAction)); + Assert.DoesNotThrow(() => fooAction()); + + //bar + var barFunc = locals.GetItem("bar"); + Assert.IsFalse(codec.CanDecode(barFunc, typeof(bool))); + Assert.IsFalse(codec.CanDecode(barFunc, typeof(Action))); + Assert.IsTrue(codec.CanDecode(barFunc, typeof(Action))); - Action action; - Assert.IsTrue(FunctionCodec.Instance.TryDecode(func, out action)); - Assert.DoesNotThrow(() => action()); + Action barAction; + Assert.IsTrue(codec.TryDecode(barFunc, out barAction)); + Assert.DoesNotThrow(() => barAction(new[]{ (object)true})); } } } diff --git a/src/runtime/Codecs/FunctionCodec.cs b/src/runtime/Codecs/FunctionCodec.cs index 84b5bf12f..f4b0a2319 100644 --- a/src/runtime/Codecs/FunctionCodec.cs +++ b/src/runtime/Codecs/FunctionCodec.cs @@ -8,32 +8,106 @@ namespace Python.Runtime.Codecs //converts python functions to C# actions class FunctionCodec : IPyObjectDecoder { + private static int GetNumArgs(PyObject pyCallable) + { + var locals = new PyDict(); + locals.SetItem("f", pyCallable); + using (Py.GIL()) + PythonEngine.Exec(@" +from inspect import signature +x = len(signature(f).parameters) +", null, locals.Handle); + + var x = locals.GetItem("x"); + return new PyInt(x).ToInt32(); + } + + private static int GetNumArgs(Type targetType) + { + var args = targetType.GetGenericArguments(); + return args.Length; + } + + private static bool IsAction(Type targetType) + { + return targetType.FullName.StartsWith("System.Action"); + } + + private static bool IsCallable(Type targetType) + { + //TODO - Func, delegate, etc + return IsAction(targetType); + } + public static FunctionCodec Instance { get; } = new FunctionCodec(); public bool CanDecode(PyObject objectType, Type targetType) { + //python object must be callable if (!objectType.IsCallable()) return false; - //TODO - handle nonzero arguments. - var args = targetType.GetGenericArguments(); - return args.Length == 0; + //C# object must be an Action + if (!IsCallable(targetType)) + return false; + + return GetNumArgs(objectType) == GetNumArgs(targetType); } public bool TryDecode(PyObject pyObj, out T value) { - Action action = () => + value = default(T); + var tT = typeof(T); + if (!IsCallable(tT)) + return false; + + var numArgs = GetNumArgs(tT); + + if (IsAction(tT)) { - Runtime.XIncref(pyObj.Handle); - PyObject pyAction = new PyObject(pyObj.Handle); - var pyArgs = new PyObject[0]; - using (Py.GIL()) + object actionObj = null; + if (numArgs == 0) { - var pyResult = pyAction.Invoke(pyArgs); - Runtime.XIncref(pyResult.Handle); + Action action = () => + { + Runtime.XIncref(pyObj.Handle); + PyObject pyAction = new PyObject(pyObj.Handle); + var pyArgs = new PyObject[0]; + using (Py.GIL()) + { + var pyResult = pyAction.Invoke(pyArgs); + Runtime.XIncref(pyResult.Handle); + } + }; + actionObj = (object)action; } - }; - var v = (object)action; - value = (T)v; - return true; + else + { + Action action = (object[] o) => + { + Runtime.XIncref(pyObj.Handle); + PyObject pyAction = new PyObject(pyObj.Handle); + var pyArgs = new PyObject[numArgs]; + int i = 0; + foreach (object obj in o) + { + pyArgs[i++] = new PyObject(Converter.ToPython(obj)); + } + + using (Py.GIL()) + { + var pyResult = pyAction.Invoke(pyArgs); + Runtime.XIncref(pyResult.Handle); + } + }; + actionObj = (object)action; + } + + value = (T)actionObj; + return true; + } + else + { + return false; + } } public static void Register() From 9ca2c057c949c37ace4c55b6a67bc22909dcc271 Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Thu, 5 Mar 2020 20:30:07 -0600 Subject: [PATCH 3/6] encoder --- src/embed_tests/Codecs.cs | 71 ++++++++----- src/runtime/Codecs/FunctionCodec.cs | 152 +++++++++++++++++++++++++++- 2 files changed, 196 insertions(+), 27 deletions(-) diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index 7005d2213..a4b52103d 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -99,42 +99,63 @@ static void TupleRoundtripGeneric() } [Test] - public void Function() + public void FunctionAction() { - FunctionCodec.Register(); var codec = FunctionCodec.Instance; - var locals = new PyDict(); - //non-callables can't be decoded into Action - Assert.IsFalse(codec.CanDecode(locals, typeof(Action))); + //decoding - python functions to C# actions + { + PyInt x = new PyInt(1); + PyDict y = new PyDict(); + //non-callables can't be decoded into Action + Assert.IsFalse(codec.CanDecode(x, typeof(Action))); + Assert.IsFalse(codec.CanDecode(y, typeof(Action))); - PythonEngine.Exec(@" + var locals = new PyDict(); + PythonEngine.Exec(@" def foo(): return 1 def bar(a): return 2 ", null, locals.Handle); - //foo - var fooFunc = locals.GetItem("foo"); - Assert.IsFalse(codec.CanDecode(fooFunc, typeof(bool))); - Assert.IsFalse(codec.CanDecode(fooFunc, typeof(Action))); - Assert.IsTrue(codec.CanDecode(fooFunc, typeof(Action))); - - Action fooAction; - Assert.IsTrue(codec.TryDecode(fooFunc, out fooAction)); - Assert.DoesNotThrow(() => fooAction()); - - //bar - var barFunc = locals.GetItem("bar"); - Assert.IsFalse(codec.CanDecode(barFunc, typeof(bool))); - Assert.IsFalse(codec.CanDecode(barFunc, typeof(Action))); - Assert.IsTrue(codec.CanDecode(barFunc, typeof(Action))); - - Action barAction; - Assert.IsTrue(codec.TryDecode(barFunc, out barAction)); - Assert.DoesNotThrow(() => barAction(new[]{ (object)true})); + //foo, the function with no arguments + var fooFunc = locals.GetItem("foo"); + Assert.IsFalse(codec.CanDecode(fooFunc, typeof(bool))); + Assert.IsFalse(codec.CanDecode(fooFunc, typeof(Action))); + Assert.IsTrue(codec.CanDecode(fooFunc, typeof(Action))); + + Action fooAction; + Assert.IsTrue(codec.TryDecode(fooFunc, out fooAction)); + Assert.DoesNotThrow(() => fooAction()); + + //bar, the function with an argument + var barFunc = locals.GetItem("bar"); + Assert.IsFalse(codec.CanDecode(barFunc, typeof(bool))); + Assert.IsFalse(codec.CanDecode(barFunc, typeof(Action))); + Assert.IsTrue(codec.CanDecode(barFunc, typeof(Action))); + + Action barAction; + Assert.IsTrue(codec.TryDecode(barFunc, out barAction)); + Assert.DoesNotThrow(() => barAction(new[] { (object)true })); + } + + //encoding, C# actions to python functions + { + //can't decode non-actions + Assert.IsFalse(codec.CanEncode(typeof(int))); + Assert.IsFalse(codec.CanEncode(typeof(Dictionary))); + + Action foo = () => { }; + Assert.IsTrue(codec.CanEncode(foo.GetType())); + + Assert.DoesNotThrow(() => { codec.TryEncode(foo); }); + + Action bar = (object[] args) => { var z = args.Length; }; + Assert.IsTrue(codec.CanEncode(bar.GetType())); + Assert.DoesNotThrow(() => { codec.TryEncode(bar); }); + } } } } diff --git a/src/runtime/Codecs/FunctionCodec.cs b/src/runtime/Codecs/FunctionCodec.cs index f4b0a2319..71f6ffcb2 100644 --- a/src/runtime/Codecs/FunctionCodec.cs +++ b/src/runtime/Codecs/FunctionCodec.cs @@ -5,8 +5,123 @@ namespace Python.Runtime.Codecs { + //like MethodWrapper but not static, so we can throw some state into it. + internal class MethodWrapper2 + { + public IntPtr mdef; + public IntPtr ptr; + private bool _disposed = false; + private ThunkInfo _thunk; + + public MethodWrapper2(object instance, string name, string funcType = null) + { + // Turn the managed method into a function pointer + var type = instance.GetType(); + _thunk = GetThunk(instance, type.GetMethod(name), funcType); + + // Allocate and initialize a PyMethodDef structure to represent + // the managed method, then create a PyCFunction. + + mdef = Runtime.PyMem_Malloc(4 * IntPtr.Size); + TypeManager.WriteMethodDef(mdef, name, _thunk.Address, 0x0003); + ptr = Runtime.PyCFunction_NewEx(mdef, IntPtr.Zero, IntPtr.Zero); + } + + internal static ThunkInfo GetThunk(object instance, System.Reflection.MethodInfo method, string funcType = null) + { + Type dt; + if (funcType != null) + dt = typeof(Interop).GetNestedType(funcType) as Type; + else + dt = Interop.GetPrototype(method.Name); + + if (dt == null) + { + return ThunkInfo.Empty; + } + Delegate d = Delegate.CreateDelegate(dt, instance, method); + var info = new ThunkInfo(d); + return info; + } + + /*public IntPtr Call(IntPtr args, IntPtr kw) + { + return Runtime.PyCFunction_Call(ptr, args, kw); + }*/ + + public void Release() + { + if (_disposed) + { + return; + } + _disposed = true; + bool freeDef = Runtime.Refcount(ptr) == 1; + Runtime.XDecref(ptr); + if (freeDef && mdef != IntPtr.Zero) + { + Runtime.PyMem_Free(mdef); + mdef = IntPtr.Zero; + } + } + } + + //class which wraps a thing + internal class ActionWrapper + { + Action _action = null; + internal ActionWrapper(Action action) + { + _action = (object[] args) => { action(); }; + } + + internal ActionWrapper(Action action) + { + _action = action; + } + + public virtual IntPtr RunAction(IntPtr self, IntPtr args) + { + var numArgs = Runtime.PyTuple_Size(args); + { + object[] managedArgs = null; + if (numArgs > 0) + { + managedArgs = new object[numArgs]; + for (int idx = 0; idx < numArgs; ++idx) + { + IntPtr item = Runtime.PyTuple_GetItem(args, idx); + object result; + //this will cause an exception to be raised if there is a failure, + // so we can safely return IntPtr.Zero + bool setError = true; + if (!Converter.ToManaged(item, typeof(object), out result, setError)) + return IntPtr.Zero; + + managedArgs[idx] = result; + } + } + + //get the args out, convert them each to C# one by one. + + //call the action with the C# args + try + { + _action(managedArgs); + } + catch + { + Exceptions.SetError(Exceptions.TypeError, "Action threw an exception"); + return IntPtr.Zero; + } + } + + return Runtime.PyNone; + } + } + //converts python functions to C# actions - class FunctionCodec : IPyObjectDecoder + class FunctionCodec : IPyObjectDecoder, IPyObjectEncoder { private static int GetNumArgs(PyObject pyCallable) { @@ -28,9 +143,19 @@ private static int GetNumArgs(Type targetType) return args.Length; } + private static bool IsUnaryAction(Type targetType) + { + return targetType == typeof(Action); + } + + private static bool IsVariadicObjectAction(Type targetType) + { + return targetType == typeof(Action); + } + private static bool IsAction(Type targetType) { - return targetType.FullName.StartsWith("System.Action"); + return IsUnaryAction(targetType) || IsVariadicObjectAction(targetType); } private static bool IsCallable(Type targetType) @@ -113,6 +238,29 @@ public bool TryDecode(PyObject pyObj, out T value) public static void Register() { PyObjectConversions.RegisterDecoder(Instance); + PyObjectConversions.RegisterEncoder(Instance); + } + + public bool CanEncode(Type type) + { + return IsCallable(type); + } + + public PyObject TryEncode(object value) + { + if (value == null) return null; + + var targetType = value.GetType(); + ActionWrapper wrapper = null; + if (IsUnaryAction(targetType)) + wrapper = new ActionWrapper(value as Action); + else if (IsVariadicObjectAction(targetType)) + wrapper = new ActionWrapper(value as Action); + + var methodWrapper = new MethodWrapper2(wrapper, "RunAction", "BinaryFunc"); + + //TODO - lifetime?? + return new PyObject(methodWrapper.ptr); } } } From 4cc4e645d33bc8223651fd5a7775148822ff93bf Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Thu, 5 Mar 2020 21:14:55 -0600 Subject: [PATCH 4/6] Func --- src/embed_tests/Codecs.cs | 96 +++++++++- src/runtime/Codecs/FunctionCodec.cs | 263 ++++++++++++++++++++-------- 2 files changed, 281 insertions(+), 78 deletions(-) diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index a4b52103d..2a10f5d59 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -123,7 +123,9 @@ def bar(a): //foo, the function with no arguments var fooFunc = locals.GetItem("foo"); Assert.IsFalse(codec.CanDecode(fooFunc, typeof(bool))); - Assert.IsFalse(codec.CanDecode(fooFunc, typeof(Action))); + + //CanDecode does not work for variadic actions + //Assert.IsFalse(codec.CanDecode(fooFunc, typeof(Action))); Assert.IsTrue(codec.CanDecode(fooFunc, typeof(Action))); Action fooAction; @@ -133,7 +135,7 @@ def bar(a): //bar, the function with an argument var barFunc = locals.GetItem("bar"); Assert.IsFalse(codec.CanDecode(barFunc, typeof(bool))); - Assert.IsFalse(codec.CanDecode(barFunc, typeof(Action))); + //Assert.IsFalse(codec.CanDecode(barFunc, typeof(Action))); Assert.IsTrue(codec.CanDecode(barFunc, typeof(Action))); Action barAction; @@ -157,5 +159,95 @@ def bar(a): Assert.DoesNotThrow(() => { codec.TryEncode(bar); }); } } + + [Test] + public void FunctionFunc() + { + FunctionCodec.Register(); + var codec = FunctionCodec.Instance; + + //decoding - python functions to C# funcs + { + PyInt x = new PyInt(1); + PyDict y = new PyDict(); + //non-callables can't be decoded into Func + Assert.IsFalse(codec.CanDecode(x, typeof(Func))); + Assert.IsFalse(codec.CanDecode(y, typeof(Func))); + + var locals = new PyDict(); + PythonEngine.Exec(@" +def foo(): + return 1 +def bar(a): + return 2 +", null, locals.Handle); + + //foo, the function with no arguments + var fooFunc = locals.GetItem("foo"); + Assert.IsFalse(codec.CanDecode(fooFunc, typeof(bool))); + + //CanDecode does not work for variadic actions + //Assert.IsFalse(codec.CanDecode(fooFunc, typeof(Func))); + Assert.IsTrue(codec.CanDecode(fooFunc, typeof(Func))); + + Func foo; + Assert.IsTrue(codec.TryDecode(fooFunc, out foo)); + object res1 = null; + Assert.DoesNotThrow(() => res1 = foo()); + Assert.AreEqual(res1, 1); + + //bar, the function with an argument + var barFunc = locals.GetItem("bar"); + Assert.IsFalse(codec.CanDecode(barFunc, typeof(bool))); + //Assert.IsFalse(codec.CanDecode(barFunc, typeof(Func))); + Assert.IsTrue(codec.CanDecode(barFunc, typeof(Func))); + + Func bar; + Assert.IsTrue(codec.TryDecode(barFunc, out bar)); + object res2 = null; + Assert.DoesNotThrow(() => res2 = bar(new[] { (object)true })); + Assert.AreEqual(res2, 2); + } + + //encoding, C# funcs to python functions + { + Func foo = () => { return 1; }; + Assert.IsTrue(codec.CanEncode(foo.GetType())); + + PyObject ret1 = null; + Assert.DoesNotThrow(() => { ret1 = codec.TryEncode(foo); }); + //call ret1 + Assert.IsTrue(ret1.IsCallable()); + + var pyArgs1 = new PyObject[0]; + using (Py.GIL()) + { + var pyResult = ret1.Invoke(pyArgs1); + Runtime.XIncref(pyResult.Handle); + object result; + Converter.ToManaged(pyResult.Handle, typeof(object), out result, true); + Assert.AreEqual(result, 1); + } + + Func bar = (object[] args) => { + return args.Length; + }; + Assert.IsTrue(codec.CanEncode(bar.GetType())); + PyObject ret2 = null; + Assert.DoesNotThrow(() => { ret2 = codec.TryEncode(bar); }); + //call ret2 + Assert.IsTrue(ret2.IsCallable()); + + var pyArgs2 = new PyObject[2] { new PyInt(1), new PyFloat(2.2) }; + using (Py.GIL()) + { + var pyResult = ret2.Invoke(pyArgs2); + Runtime.XIncref(pyResult.Handle); + object result; + Converter.ToManaged(pyResult.Handle, typeof(object), out result, true); + Assert.AreEqual(result, 2); + } + } + } } } diff --git a/src/runtime/Codecs/FunctionCodec.cs b/src/runtime/Codecs/FunctionCodec.cs index 71f6ffcb2..491ee063a 100644 --- a/src/runtime/Codecs/FunctionCodec.cs +++ b/src/runtime/Codecs/FunctionCodec.cs @@ -66,8 +66,36 @@ public void Release() } } + internal class CallableHelper + { + public static object[] ConvertArgs(IntPtr args) + { + var numArgs = Runtime.PyTuple_Size(args); + object[] managedArgs = null; + if (numArgs > 0) + { + managedArgs = new object[numArgs]; + for (int idx = 0; idx < numArgs; ++idx) + { + IntPtr item = Runtime.PyTuple_GetItem(args, idx); + object result; + if (!Converter.ToManaged(item, typeof(object), out result, false)) + throw new Exception(); + + managedArgs[idx] = result; + } + } + return managedArgs; + } + } + + interface ICallable + { + IntPtr RunAction(IntPtr self, IntPtr args); + } + //class which wraps a thing - internal class ActionWrapper + internal class ActionWrapper : ICallable { Action _action = null; internal ActionWrapper(Action action) @@ -82,44 +110,51 @@ internal ActionWrapper(Action action) public virtual IntPtr RunAction(IntPtr self, IntPtr args) { - var numArgs = Runtime.PyTuple_Size(args); + try { - object[] managedArgs = null; - if (numArgs > 0) - { - managedArgs = new object[numArgs]; - for (int idx = 0; idx < numArgs; ++idx) - { - IntPtr item = Runtime.PyTuple_GetItem(args, idx); - object result; - //this will cause an exception to be raised if there is a failure, - // so we can safely return IntPtr.Zero - bool setError = true; - if (!Converter.ToManaged(item, typeof(object), out result, setError)) - return IntPtr.Zero; - - managedArgs[idx] = result; - } - } - - //get the args out, convert them each to C# one by one. - - //call the action with the C# args - try - { - _action(managedArgs); - } - catch - { - Exceptions.SetError(Exceptions.TypeError, "Action threw an exception"); - return IntPtr.Zero; - } + object[] managedArgs = CallableHelper.ConvertArgs(args); + _action(managedArgs); + } + catch + { + Exceptions.SetError(Exceptions.TypeError, "Error in RunAction"); + return IntPtr.Zero; } return Runtime.PyNone; } } + internal class FunctionWrapper : ICallable + { + Func _func = null; + + internal FunctionWrapper(Func func) + { + _func = (object[] args) => { return func(); }; + } + + internal FunctionWrapper(Func func) + { + _func = func; + } + + public virtual IntPtr RunAction(IntPtr self, IntPtr args) + { + try + { + object[] managedArgs = CallableHelper.ConvertArgs(args); + var retVal = _func(managedArgs); + return Converter.ToPython(retVal); + } + catch + { + Exceptions.SetError(Exceptions.TypeError, "Error in RunAction"); + return IntPtr.Zero; + } + } + } + //converts python functions to C# actions class FunctionCodec : IPyObjectDecoder, IPyObjectEncoder { @@ -137,12 +172,6 @@ from inspect import signature return new PyInt(x).ToInt32(); } - private static int GetNumArgs(Type targetType) - { - var args = targetType.GetGenericArguments(); - return args.Length; - } - private static bool IsUnaryAction(Type targetType) { return targetType == typeof(Action); @@ -153,15 +182,30 @@ private static bool IsVariadicObjectAction(Type targetType) return targetType == typeof(Action); } + private static bool IsUnaryFunc(Type targetType) + { + return targetType == typeof(Func); + } + + private static bool IsVariadicObjectFunc(Type targetType) + { + return targetType == typeof(Func); + } + private static bool IsAction(Type targetType) { return IsUnaryAction(targetType) || IsVariadicObjectAction(targetType); } + private static bool IsFunc(Type targetType) + { + return IsUnaryFunc(targetType) || IsVariadicObjectFunc(targetType); + } + private static bool IsCallable(Type targetType) { - //TODO - Func, delegate, etc - return IsAction(targetType); + //TODO - delegate, event, ... + return IsAction(targetType) || IsFunc(targetType); } public static FunctionCodec Instance { get; } = new FunctionCodec(); @@ -174,7 +218,69 @@ public bool CanDecode(PyObject objectType, Type targetType) if (!IsCallable(targetType)) return false; - return GetNumArgs(objectType) == GetNumArgs(targetType); + //We don't know if it will work without the instance + //The number of arguments of a unary or variadic object callable + //is always going to be 0 or 1 + return true; + } + + private static object ConvertUnaryAction(PyObject pyObj) + { + Func func = (Func)ConvertUnaryFunc(pyObj); + Action action = () => { func(); }; + return (object)action; + } + + private static object ConvertVariadicObjectAction(PyObject pyObj, int numArgs) + { + Func func = (Func)ConvertVariadicObjectFunc(pyObj, numArgs); + Action action = (object[] args) => { func(args); }; + return (object)action; + } + + //TODO share code between ConvertUnaryFunc and ConvertVariadicObjectFunc + private static object ConvertUnaryFunc(PyObject pyObj) + { + Func func = () => + { + Runtime.XIncref(pyObj.Handle); + PyObject pyAction = new PyObject(pyObj.Handle); + var pyArgs = new PyObject[0]; + using (Py.GIL()) + { + var pyResult = pyAction.Invoke(pyArgs); + Runtime.XIncref(pyResult.Handle); + object result; + Converter.ToManaged(pyResult.Handle, typeof(object), out result, true); + return result; + } + }; + return (object)func; + } + + private static object ConvertVariadicObjectFunc(PyObject pyObj, int numArgs) + { + Func func = (object[] o) => + { + Runtime.XIncref(pyObj.Handle); + PyObject pyAction = new PyObject(pyObj.Handle); + var pyArgs = new PyObject[numArgs]; + int i = 0; + foreach (object obj in o) + { + pyArgs[i++] = new PyObject(Converter.ToPython(obj)); + } + + using (Py.GIL()) + { + var pyResult = pyAction.Invoke(pyArgs); + Runtime.XIncref(pyResult.Handle); + object result; + Converter.ToManaged(pyResult.Handle, typeof(object), out result, true); + return result; + } + }; + return (object)func; } public bool TryDecode(PyObject pyObj, out T value) @@ -184,51 +290,39 @@ public bool TryDecode(PyObject pyObj, out T value) if (!IsCallable(tT)) return false; - var numArgs = GetNumArgs(tT); + var numArgs = GetNumArgs(pyObj); if (IsAction(tT)) { object actionObj = null; if (numArgs == 0) { - Action action = () => - { - Runtime.XIncref(pyObj.Handle); - PyObject pyAction = new PyObject(pyObj.Handle); - var pyArgs = new PyObject[0]; - using (Py.GIL()) - { - var pyResult = pyAction.Invoke(pyArgs); - Runtime.XIncref(pyResult.Handle); - } - }; - actionObj = (object)action; + actionObj = ConvertUnaryAction(pyObj); } else { - Action action = (object[] o) => - { - Runtime.XIncref(pyObj.Handle); - PyObject pyAction = new PyObject(pyObj.Handle); - var pyArgs = new PyObject[numArgs]; - int i = 0; - foreach (object obj in o) - { - pyArgs[i++] = new PyObject(Converter.ToPython(obj)); - } - - using (Py.GIL()) - { - var pyResult = pyAction.Invoke(pyArgs); - Runtime.XIncref(pyResult.Handle); - } - }; - actionObj = (object)action; + actionObj = ConvertVariadicObjectAction(pyObj, numArgs); } value = (T)actionObj; return true; } + else if (IsFunc(tT)) + { + + object funcObj = null; + if (numArgs == 0) + { + funcObj = ConvertUnaryFunc(pyObj); + } + else + { + funcObj = ConvertVariadicObjectFunc(pyObj, numArgs); + } + + value = (T)funcObj; + return true; + } else { return false; @@ -251,13 +345,30 @@ public PyObject TryEncode(object value) if (value == null) return null; var targetType = value.GetType(); - ActionWrapper wrapper = null; + ICallable callable = null; if (IsUnaryAction(targetType)) - wrapper = new ActionWrapper(value as Action); + { + callable = new ActionWrapper(value as Action); + + } else if (IsVariadicObjectAction(targetType)) - wrapper = new ActionWrapper(value as Action); + { + callable = new ActionWrapper(value as Action); + } + else if (IsUnaryFunc(targetType)) + { + callable = new FunctionWrapper(value as Func); + } + else if (IsVariadicObjectFunc(targetType)) + { + callable = new FunctionWrapper(value as Func); + } + else + { + throw new Exception("object cannot be encoded!"); + } - var methodWrapper = new MethodWrapper2(wrapper, "RunAction", "BinaryFunc"); + var methodWrapper = new MethodWrapper2(callable, "RunAction", "BinaryFunc"); //TODO - lifetime?? return new PyObject(methodWrapper.ptr); From e7169495d73e0879650838681873fb4cece214d8 Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Sun, 8 Mar 2020 19:35:31 -0500 Subject: [PATCH 5/6] fix some issues from PR comments --- src/embed_tests/Codecs.cs | 56 -------- src/embed_tests/TestCallbacks.cs | 72 ++++++++++ src/runtime/Codecs/FunctionCodec.cs | 206 ++-------------------------- 3 files changed, 80 insertions(+), 254 deletions(-) diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index 2a10f5d59..7ee6c3c30 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -142,22 +142,6 @@ def bar(a): Assert.IsTrue(codec.TryDecode(barFunc, out barAction)); Assert.DoesNotThrow(() => barAction(new[] { (object)true })); } - - //encoding, C# actions to python functions - { - //can't decode non-actions - Assert.IsFalse(codec.CanEncode(typeof(int))); - Assert.IsFalse(codec.CanEncode(typeof(Dictionary))); - - Action foo = () => { }; - Assert.IsTrue(codec.CanEncode(foo.GetType())); - - Assert.DoesNotThrow(() => { codec.TryEncode(foo); }); - - Action bar = (object[] args) => { var z = args.Length; }; - Assert.IsTrue(codec.CanEncode(bar.GetType())); - Assert.DoesNotThrow(() => { codec.TryEncode(bar); }); - } } [Test] @@ -208,46 +192,6 @@ def bar(a): Assert.DoesNotThrow(() => res2 = bar(new[] { (object)true })); Assert.AreEqual(res2, 2); } - - //encoding, C# funcs to python functions - { - Func foo = () => { return 1; }; - Assert.IsTrue(codec.CanEncode(foo.GetType())); - - PyObject ret1 = null; - Assert.DoesNotThrow(() => { ret1 = codec.TryEncode(foo); }); - //call ret1 - Assert.IsTrue(ret1.IsCallable()); - - var pyArgs1 = new PyObject[0]; - using (Py.GIL()) - { - var pyResult = ret1.Invoke(pyArgs1); - Runtime.XIncref(pyResult.Handle); - object result; - Converter.ToManaged(pyResult.Handle, typeof(object), out result, true); - Assert.AreEqual(result, 1); - } - - Func bar = (object[] args) => { - return args.Length; - }; - Assert.IsTrue(codec.CanEncode(bar.GetType())); - PyObject ret2 = null; - Assert.DoesNotThrow(() => { ret2 = codec.TryEncode(bar); }); - //call ret2 - Assert.IsTrue(ret2.IsCallable()); - - var pyArgs2 = new PyObject[2] { new PyInt(1), new PyFloat(2.2) }; - using (Py.GIL()) - { - var pyResult = ret2.Invoke(pyArgs2); - Runtime.XIncref(pyResult.Handle); - object result; - Converter.ToManaged(pyResult.Handle, typeof(object), out result, true); - Assert.AreEqual(result, 2); - } - } } } } diff --git a/src/embed_tests/TestCallbacks.cs b/src/embed_tests/TestCallbacks.cs index 220b0a86a..eb23c74d0 100644 --- a/src/embed_tests/TestCallbacks.cs +++ b/src/embed_tests/TestCallbacks.cs @@ -31,5 +31,77 @@ public void TestNoOverloadException() { StringAssert.EndsWith(expectedArgTypes, error.Message); } } + + private class Callables + { + internal object CallFunction0(Func func) + { + return func(); + } + + internal object CallFunction1(Func func, object arg) + { + return func(new[] { arg}); + } + + internal void CallAction0(Action func) + { + func(); + } + + internal void CallAction1(Action func, object arg) + { + func(new[] { arg }); + } + } + + [Test] + public void TestPythonFunctionPassedIntoCLRMethod() + { + var locals = new PyDict(); + PythonEngine.Exec(@" +def ret_1(): + return 1 +def str_len(a): + return len(a) +", null, locals.Handle); + + var ret1 = locals.GetItem("ret_1"); + var strLen = locals.GetItem("str_len"); + + var callables = new Callables(); + + Python.Runtime.Codecs.FunctionCodec.Register(); + + //ret1. A function with no arguments that returns an integer + //it must be convertible to Action or Func and not to Func + { + Assert.IsTrue(Converter.ToManaged(ret1.Handle, typeof(Action), out var result1, false)); + Assert.IsTrue(Converter.ToManaged(ret1.Handle, typeof(Func), out var result2, false)); + + Assert.DoesNotThrow(() => { callables.CallAction0((Action)result1); }); + object ret2 = null; + Assert.DoesNotThrow(() => { ret2 = callables.CallFunction0((Func)result2); }); + Assert.AreEqual(ret2, 1); + } + + //strLen. A function that takes something with a __len__ and returns the result of that function + //It must be convertible to an Action and Func) and not to an Action or Func + { + Assert.IsTrue(Converter.ToManaged(strLen.Handle, typeof(Action), out var result3, false)); + Assert.IsTrue(Converter.ToManaged(strLen.Handle, typeof(Func), out var result4, false)); + + //try using both func and action to show you can get __len__ of a string but not an integer + Assert.Throws(() => { callables.CallAction1((Action)result3, 2); }); + Assert.DoesNotThrow(() => { callables.CallAction1((Action)result3, "hello"); }); + Assert.Throws(() => { callables.CallFunction1((Func)result4, 2); }); + + object ret2 = null; + Assert.DoesNotThrow(() => { ret2 = callables.CallFunction1((Func)result4, "hello"); }); + Assert.AreEqual(ret2, 5); + } + + PyObjectConversions.Reset(); + } } } diff --git a/src/runtime/Codecs/FunctionCodec.cs b/src/runtime/Codecs/FunctionCodec.cs index 491ee063a..fb6f64683 100644 --- a/src/runtime/Codecs/FunctionCodec.cs +++ b/src/runtime/Codecs/FunctionCodec.cs @@ -1,162 +1,9 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace Python.Runtime.Codecs { - //like MethodWrapper but not static, so we can throw some state into it. - internal class MethodWrapper2 - { - public IntPtr mdef; - public IntPtr ptr; - private bool _disposed = false; - private ThunkInfo _thunk; - - public MethodWrapper2(object instance, string name, string funcType = null) - { - // Turn the managed method into a function pointer - var type = instance.GetType(); - _thunk = GetThunk(instance, type.GetMethod(name), funcType); - - // Allocate and initialize a PyMethodDef structure to represent - // the managed method, then create a PyCFunction. - - mdef = Runtime.PyMem_Malloc(4 * IntPtr.Size); - TypeManager.WriteMethodDef(mdef, name, _thunk.Address, 0x0003); - ptr = Runtime.PyCFunction_NewEx(mdef, IntPtr.Zero, IntPtr.Zero); - } - - internal static ThunkInfo GetThunk(object instance, System.Reflection.MethodInfo method, string funcType = null) - { - Type dt; - if (funcType != null) - dt = typeof(Interop).GetNestedType(funcType) as Type; - else - dt = Interop.GetPrototype(method.Name); - - if (dt == null) - { - return ThunkInfo.Empty; - } - Delegate d = Delegate.CreateDelegate(dt, instance, method); - var info = new ThunkInfo(d); - return info; - } - - /*public IntPtr Call(IntPtr args, IntPtr kw) - { - return Runtime.PyCFunction_Call(ptr, args, kw); - }*/ - - public void Release() - { - if (_disposed) - { - return; - } - _disposed = true; - bool freeDef = Runtime.Refcount(ptr) == 1; - Runtime.XDecref(ptr); - if (freeDef && mdef != IntPtr.Zero) - { - Runtime.PyMem_Free(mdef); - mdef = IntPtr.Zero; - } - } - } - - internal class CallableHelper - { - public static object[] ConvertArgs(IntPtr args) - { - var numArgs = Runtime.PyTuple_Size(args); - object[] managedArgs = null; - if (numArgs > 0) - { - managedArgs = new object[numArgs]; - for (int idx = 0; idx < numArgs; ++idx) - { - IntPtr item = Runtime.PyTuple_GetItem(args, idx); - object result; - if (!Converter.ToManaged(item, typeof(object), out result, false)) - throw new Exception(); - - managedArgs[idx] = result; - } - } - return managedArgs; - } - } - - interface ICallable - { - IntPtr RunAction(IntPtr self, IntPtr args); - } - - //class which wraps a thing - internal class ActionWrapper : ICallable - { - Action _action = null; - internal ActionWrapper(Action action) - { - _action = (object[] args) => { action(); }; - } - - internal ActionWrapper(Action action) - { - _action = action; - } - - public virtual IntPtr RunAction(IntPtr self, IntPtr args) - { - try - { - object[] managedArgs = CallableHelper.ConvertArgs(args); - _action(managedArgs); - } - catch - { - Exceptions.SetError(Exceptions.TypeError, "Error in RunAction"); - return IntPtr.Zero; - } - - return Runtime.PyNone; - } - } - - internal class FunctionWrapper : ICallable - { - Func _func = null; - - internal FunctionWrapper(Func func) - { - _func = (object[] args) => { return func(); }; - } - - internal FunctionWrapper(Func func) - { - _func = func; - } - - public virtual IntPtr RunAction(IntPtr self, IntPtr args) - { - try - { - object[] managedArgs = CallableHelper.ConvertArgs(args); - var retVal = _func(managedArgs); - return Converter.ToPython(retVal); - } - catch - { - Exceptions.SetError(Exceptions.TypeError, "Error in RunAction"); - return IntPtr.Zero; - } - } - } - //converts python functions to C# actions - class FunctionCodec : IPyObjectDecoder, IPyObjectEncoder + class FunctionCodec : IPyObjectDecoder { private static int GetNumArgs(PyObject pyCallable) { @@ -165,7 +12,10 @@ private static int GetNumArgs(PyObject pyCallable) using (Py.GIL()) PythonEngine.Exec(@" from inspect import signature -x = len(signature(f).parameters) +try: + x = len(signature(f).parameters) +except: + x = 0 ", null, locals.Handle); var x = locals.GetItem("x"); @@ -214,7 +64,7 @@ public bool CanDecode(PyObject objectType, Type targetType) //python object must be callable if (!objectType.IsCallable()) return false; - //C# object must be an Action + //C# object must be callable if (!IsCallable(targetType)) return false; @@ -250,8 +100,7 @@ private static object ConvertUnaryFunc(PyObject pyObj) { var pyResult = pyAction.Invoke(pyArgs); Runtime.XIncref(pyResult.Handle); - object result; - Converter.ToManaged(pyResult.Handle, typeof(object), out result, true); + Converter.ToManaged(pyResult.Handle, typeof(object), out var result, true); return result; } }; @@ -332,46 +181,7 @@ public bool TryDecode(PyObject pyObj, out T value) public static void Register() { PyObjectConversions.RegisterDecoder(Instance); - PyObjectConversions.RegisterEncoder(Instance); - } - - public bool CanEncode(Type type) - { - return IsCallable(type); - } - - public PyObject TryEncode(object value) - { - if (value == null) return null; - - var targetType = value.GetType(); - ICallable callable = null; - if (IsUnaryAction(targetType)) - { - callable = new ActionWrapper(value as Action); - - } - else if (IsVariadicObjectAction(targetType)) - { - callable = new ActionWrapper(value as Action); - } - else if (IsUnaryFunc(targetType)) - { - callable = new FunctionWrapper(value as Func); - } - else if (IsVariadicObjectFunc(targetType)) - { - callable = new FunctionWrapper(value as Func); - } - else - { - throw new Exception("object cannot be encoded!"); - } - - var methodWrapper = new MethodWrapper2(callable, "RunAction", "BinaryFunc"); - - //TODO - lifetime?? - return new PyObject(methodWrapper.ptr); + PyObjectConversions.RegisterDecoder(Instance); } } } From e231ff2987f181cd266fdfc4e38d49ca708be843 Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Mon, 9 Mar 2020 18:47:57 -0500 Subject: [PATCH 6/6] improve function codec --- src/embed_tests/Codecs.cs | 124 +++++++++++++--------------- src/runtime/Codecs/FunctionCodec.cs | 16 ++-- src/runtime/classmanager.cs | 7 +- 3 files changed, 74 insertions(+), 73 deletions(-) diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index 7ee6c3c30..90d13fb1c 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -101,97 +101,89 @@ static void TupleRoundtripGeneric() [Test] public void FunctionAction() { - FunctionCodec.Register(); var codec = FunctionCodec.Instance; - //decoding - python functions to C# actions - { - PyInt x = new PyInt(1); - PyDict y = new PyDict(); - //non-callables can't be decoded into Action - Assert.IsFalse(codec.CanDecode(x, typeof(Action))); - Assert.IsFalse(codec.CanDecode(y, typeof(Action))); - - var locals = new PyDict(); - PythonEngine.Exec(@" + PyInt x = new PyInt(1); + PyDict y = new PyDict(); + //non-callables can't be decoded into Action + Assert.IsFalse(codec.CanDecode(x, typeof(Action))); + Assert.IsFalse(codec.CanDecode(y, typeof(Action))); + + var locals = new PyDict(); + PythonEngine.Exec(@" def foo(): return 1 def bar(a): return 2 ", null, locals.Handle); - //foo, the function with no arguments - var fooFunc = locals.GetItem("foo"); - Assert.IsFalse(codec.CanDecode(fooFunc, typeof(bool))); + //foo, the function with no arguments + var fooFunc = locals.GetItem("foo"); + Assert.IsFalse(codec.CanDecode(fooFunc, typeof(bool))); - //CanDecode does not work for variadic actions - //Assert.IsFalse(codec.CanDecode(fooFunc, typeof(Action))); - Assert.IsTrue(codec.CanDecode(fooFunc, typeof(Action))); + //CanDecode does not work for variadic actions + //Assert.IsFalse(codec.CanDecode(fooFunc, typeof(Action))); + Assert.IsTrue(codec.CanDecode(fooFunc, typeof(Action))); - Action fooAction; - Assert.IsTrue(codec.TryDecode(fooFunc, out fooAction)); - Assert.DoesNotThrow(() => fooAction()); + Action fooAction; + Assert.IsTrue(codec.TryDecode(fooFunc, out fooAction)); + Assert.DoesNotThrow(() => fooAction()); - //bar, the function with an argument - var barFunc = locals.GetItem("bar"); - Assert.IsFalse(codec.CanDecode(barFunc, typeof(bool))); - //Assert.IsFalse(codec.CanDecode(barFunc, typeof(Action))); - Assert.IsTrue(codec.CanDecode(barFunc, typeof(Action))); + //bar, the function with an argument + var barFunc = locals.GetItem("bar"); + Assert.IsFalse(codec.CanDecode(barFunc, typeof(bool))); + //Assert.IsFalse(codec.CanDecode(barFunc, typeof(Action))); + Assert.IsTrue(codec.CanDecode(barFunc, typeof(Action))); - Action barAction; - Assert.IsTrue(codec.TryDecode(barFunc, out barAction)); - Assert.DoesNotThrow(() => barAction(new[] { (object)true })); - } + Action barAction; + Assert.IsTrue(codec.TryDecode(barFunc, out barAction)); + Assert.DoesNotThrow(() => barAction(new[] { (object)true })); } [Test] public void FunctionFunc() { - FunctionCodec.Register(); var codec = FunctionCodec.Instance; - //decoding - python functions to C# funcs - { - PyInt x = new PyInt(1); - PyDict y = new PyDict(); - //non-callables can't be decoded into Func - Assert.IsFalse(codec.CanDecode(x, typeof(Func))); - Assert.IsFalse(codec.CanDecode(y, typeof(Func))); - - var locals = new PyDict(); - PythonEngine.Exec(@" + PyInt x = new PyInt(1); + PyDict y = new PyDict(); + //non-callables can't be decoded into Func + Assert.IsFalse(codec.CanDecode(x, typeof(Func))); + Assert.IsFalse(codec.CanDecode(y, typeof(Func))); + + var locals = new PyDict(); + PythonEngine.Exec(@" def foo(): return 1 def bar(a): return 2 ", null, locals.Handle); - //foo, the function with no arguments - var fooFunc = locals.GetItem("foo"); - Assert.IsFalse(codec.CanDecode(fooFunc, typeof(bool))); - - //CanDecode does not work for variadic actions - //Assert.IsFalse(codec.CanDecode(fooFunc, typeof(Func))); - Assert.IsTrue(codec.CanDecode(fooFunc, typeof(Func))); - - Func foo; - Assert.IsTrue(codec.TryDecode(fooFunc, out foo)); - object res1 = null; - Assert.DoesNotThrow(() => res1 = foo()); - Assert.AreEqual(res1, 1); - - //bar, the function with an argument - var barFunc = locals.GetItem("bar"); - Assert.IsFalse(codec.CanDecode(barFunc, typeof(bool))); - //Assert.IsFalse(codec.CanDecode(barFunc, typeof(Func))); - Assert.IsTrue(codec.CanDecode(barFunc, typeof(Func))); - - Func bar; - Assert.IsTrue(codec.TryDecode(barFunc, out bar)); - object res2 = null; - Assert.DoesNotThrow(() => res2 = bar(new[] { (object)true })); - Assert.AreEqual(res2, 2); - } + //foo, the function with no arguments + var fooFunc = locals.GetItem("foo"); + Assert.IsFalse(codec.CanDecode(fooFunc, typeof(bool))); + + //CanDecode does not work for variadic actions + //Assert.IsFalse(codec.CanDecode(fooFunc, typeof(Func))); + Assert.IsTrue(codec.CanDecode(fooFunc, typeof(Func))); + + Func foo; + Assert.IsTrue(codec.TryDecode(fooFunc, out foo)); + object res1 = null; + Assert.DoesNotThrow(() => res1 = foo()); + Assert.AreEqual(res1, 1); + + //bar, the function with an argument + var barFunc = locals.GetItem("bar"); + Assert.IsFalse(codec.CanDecode(barFunc, typeof(bool))); + //Assert.IsFalse(codec.CanDecode(barFunc, typeof(Func))); + Assert.IsTrue(codec.CanDecode(barFunc, typeof(Func))); + + Func bar; + Assert.IsTrue(codec.TryDecode(barFunc, out bar)); + object res2 = null; + Assert.DoesNotThrow(() => res2 = bar(new[] { (object)true })); + Assert.AreEqual(res2, 2); } } } diff --git a/src/runtime/Codecs/FunctionCodec.cs b/src/runtime/Codecs/FunctionCodec.cs index fb6f64683..ba75ecf60 100644 --- a/src/runtime/Codecs/FunctionCodec.cs +++ b/src/runtime/Codecs/FunctionCodec.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; namespace Python.Runtime.Codecs { @@ -22,6 +23,12 @@ from inspect import signature return new PyInt(x).ToInt32(); } + private static int GetNumArgs(Type targetType) + { + MethodInfo invokeMethod = targetType.GetMethod("Invoke"); + return invokeMethod.GetParameters().Length; + } + private static bool IsUnaryAction(Type targetType) { return targetType == typeof(Action); @@ -54,8 +61,7 @@ private static bool IsFunc(Type targetType) private static bool IsCallable(Type targetType) { - //TODO - delegate, event, ... - return IsAction(targetType) || IsFunc(targetType); + return ClassManager.IsDelegate(targetType); } public static FunctionCodec Instance { get; } = new FunctionCodec(); @@ -68,9 +74,6 @@ public bool CanDecode(PyObject objectType, Type targetType) if (!IsCallable(targetType)) return false; - //We don't know if it will work without the instance - //The number of arguments of a unary or variadic object callable - //is always going to be 0 or 1 return true; } @@ -140,6 +143,8 @@ public bool TryDecode(PyObject pyObj, out T value) return false; var numArgs = GetNumArgs(pyObj); + if (numArgs != GetNumArgs(tT)) + return false; if (IsAction(tT)) { @@ -181,7 +186,6 @@ public bool TryDecode(PyObject pyObj, out T value) public static void Register() { PyObjectConversions.RegisterDecoder(Instance); - PyObjectConversions.RegisterDecoder(Instance); } } } diff --git a/src/runtime/classmanager.cs b/src/runtime/classmanager.cs index 0b084a49d..aad768536 100644 --- a/src/runtime/classmanager.cs +++ b/src/runtime/classmanager.cs @@ -39,6 +39,11 @@ public static void Reset() cache = new Dictionary(128); } + public static bool IsDelegate(Type type) + { + return type.IsSubclassOf(dtype); + } + /// /// Return the ClassBase-derived instance that implements a particular /// reflected managed type, creating it if it doesn't yet exist. @@ -83,7 +88,7 @@ private static ClassBase CreateClass(Type type) impl = new GenericType(type); } - else if (type.IsSubclassOf(dtype)) + else if (IsDelegate(type)) { impl = new DelegateObject(type); } 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