8

I am trying to use C# classes from python, using python.net on mono / ubuntu.

So far I managed to do a simple function call with one argument work. What I am now trying to do is pass a python callback to the C# function call.

I tried the following variations below, none worked. Can someone show how to make that work?

// C# - testlib.cs
class MC {
    public double method1(int n) {
        Console.WriteLine("Executing method1" );
        /* .. */
    }

    public double method2(Delegate f) {
        Console.WriteLine("Executing method2" );
        /* ... do f() at some point ... */
        /* also tried f.DynamicInvoke() */
        Console.WriteLine("Done executing method2" );
    }
}

Python script

import testlib, System
mc = testlib.MC()
mc.method1(10) # that works

def f():
    print "Executing f"

mc.method2(f)  
# does not know of method2 with that signature, fair enough...

# is this the right way to turn it into a callback?
f2 = System.AssemblyLoad(f) 
# no error message, but f does not seem to be invoked
mc.method2(f2)              
joelhoro
  • 890
  • 1
  • 9
  • 20

2 Answers2

7

Try to pass Action or Func instead of just raw function:

I used IronPython here (because right now I don't have mono installed on any of my machines but according of Python.NET documentation I think it should work Actually your code is almost ok but you need to import Action or Func delegate depends on what you need.

python code:

import clr
from types import *
from System import Action
clr.AddReferenceToFileAndPath(r"YourPath\TestLib.dll")
import TestLib
print("Hello")
mc = TestLib.MC()
print(mc.method1(10))

def f(fakeparam):
    print "exec f" 

mc.method2(Action[int](f))

This is a console output:

Hello
Executing method1
42.0
Executing method2
exec f
Done executing method2

C# code:

using System;


namespace TestLib
{
    public class MC
    {
        public double method1(int n)
        {
            Console.WriteLine("Executing method1");
            return 42.0;
            /* .. */
        }

        public double method2(Delegate f)
        {
            Console.WriteLine("Executing method2");
            object[] paramToPass = new object[1];
            paramToPass[0] = new int();
            f.DynamicInvoke(paramToPass);
            Console.WriteLine("Done executing method2");
            return 24.0;

        }
    }
}

I read docs for Python.net Using Generics again and also found this Python.NET Naming and resolution of generic types look like you need to specify parameter type explicitly

quote from there:

a (reflected) generic type definition (if there exists a generic type definition with the given base name, and no non-generic type with that name). This generic type definition can be bound into a closed generic type using the [] syntax. Trying to instantiate a generic type def using () raises a TypeError.

Alexander V.
  • 1,518
  • 14
  • 14
  • Thx for unfortunately just doing `f2 = Action(f)` returns `TypeError: cannot instantiate an open generic type`. And importing Func from System doesn't work at all. – joelhoro Mar 21 '14 at 09:23
  • I read docs for Python.net [Using Generics] (http://pythonnet.sourceforge.net/readme.html#generics) again and also found this [Python.NET Naming and resolution of generic types](https://mail.python.org/pipermail/pythondotnet/2006-April/000472.html) look like you need to specify parameter type explicitly. I made a change to my answer – Alexander V. Mar 21 '14 at 09:52
  • I finished edits to the answer please let me know if you'll have any issues – Alexander V. Mar 21 '14 at 10:05
  • 1
    Thx! This works fine. My only issue is that I really need a Func, because my C# method needs to use the return value of the python func. When I do `var g=f.DynamicInvoke(paramsToPass)` the g seems empty. I guess this is a separate problem though. However I will award the bounty! – joelhoro Mar 21 '14 at 12:35
  • 1
    Thanks! I think in that case try to replace `Action` with `Func` like Func[int,int]. I'll also try to look into it a little bit later – Alexander V. Mar 21 '14 at 12:43
  • 1
    The issue is that `from System import Func` returns an error so I can't even do what you suggest. – joelhoro Mar 22 '14 at 15:18
2

It looks like you should define your Delegate explicitly:

class MC {
    // Define a delegate type
    public delegate void Callback();

    public double method2(Callback f) {
        Console.WriteLine("Executing method2" );
        /* ... do f() at some point ... */
        /* also tried f.DynamicInvoke() */
        Console.WriteLine("Done executing method2" );
    }
}

Then from the Python code (this is a rough guess based from the docs):

def f():
    print "Executing f"

# instantiate a delegate
f2 = testlib.MC.Callback(f)

# use it
mc.method2(f2)
ysalmi
  • 529
  • 4
  • 16
  • I don't really get your answer. The first line in the c# class does not make sense. Then you add 'Callback f' as an argument, when Callback is a field, not a type? Finally you try to set Callback by calling testlib.MC.Callback as if it were a fn. None of these work. What I did try is to create a delegate member, and set it via a member fn, but that did not work. Clearly f, as it is, is not recognized as a delegate when passed to c# – joelhoro Mar 16 '14 at 19:05
  • Sorry, didn't run my c# code and I've never used Python.net, but I think there is a problem the way you define your callback. I fixed the first line in my example which is actually defining a delegate type (and not a field). That can be exposed to your python program using the `testlib.MC.Callback`. This is based on the example in the docs which suggests you instantiate an instance of the EventHandler/Delegate type (in the docs example, it's an event handler of type `AssemblyLoadEventHandler`). – ysalmi Mar 16 '14 at 22:55
  • Thx FWIW I tried a Func instead of delegate but then I am running into errors of missing System.Func... will need to continue experimenting – joelhoro Mar 17 '14 at 08:34
  • btw have you tried IronPython or you stuck with Python.NET? Also check out this answer here: http://stackoverflow.com/questions/3958623/assigning-an-ironpython-method-to-a-c-sharp-delegate – ysalmi Mar 18 '14 at 21:18
  • I could try ironpython but I am using ubuntu and it doesn't look straightforward. I can give it a try. – joelhoro Mar 21 '14 at 09:25
  • It worked! I use SuSE Linux 43.3, mono 4.6.1, python 3.4, pythonnew 2.3.0. Some related caveats: I had to use clr.FindAssebmly() and clr.AddReference(), otherwise python would not import anything (warning: the file paths must not contain symbolic links). I also had to encapsulate the classes in a namespace, and then import the namespace. The classes and methods must be declared public, using the public keyword. – cyberthanasis May 20 '18 at 08:07