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); }