Allowing python to call the output of cfunction

question

#1

This is really a PyCall question instead of a pure Julia question but maybe others will find it useful. Basically, I’m interested in the following steps:

  • Define a one-argument Julia function.
  • Obtain a Ptr{Void} to it using cfunction.
  • Store the function pointer in a python class (using PyCall).
  • Sometime later, call it from the python class with an argument (Ptr{Void} again) sent from Julia.

Is this doable and/or coherent? Keywords and links to resources that could help me better understand this type of problem would be welcome too.


#2

PyCall already does this. If you have a Julia function f(x), just pass it to a Python function (or, more explicitly, call PyObject(f)) to convert it to a callable Python object. Internally, the Python object is a C struct with a tp_call member that is a pointer to a cfunction pointer, which calls back to the Julia function f (which is compiled like any other Julia function).


#3

Thanks for the quick reply. The issue for me with the current PyCall setup is that the callback is wrapped in a different Julia function. I’m trying to do a inter-thread notification system using uv_async_send and so I can’t run Julia functions from the python-invoked callback.

Would it be feasible to transfer the callback wrapping you did in Julia to the python side? Any pointers/references for going that direction?


#4

Here’s a trivial example of this setup. Save both files in the same directory and just run julia PyCB.jl. Note that this doesn’t work on julia master, I assume because of PyCall#343.

I assume this could work if I could somehow run cfunction on cb and pass it to be called directly by python.

PyCB.jl:

module PyCB

using PyCall

function cb(handle)
    ccall(:uv_async_send, Cint, (Ptr{Void},), handle)
end

function go()
    pycb_obj = pyimport("pycb")["CB"](cb)

    c = Base.AsyncCondition()
    @async begin
        wait(c)
        println("Notified!")
    end

    pycb_obj[:call](c.handle)
    sleep(1)
end

function __init__()
    pypath = PyVector(pyimport("sys")["path"])
    if ! (dirname(@__FILE__) in pypath)
        unshift!(pypath, dirname(@__FILE__))
    end
end

end

if ! isinteractive()
    PyCB.go()
end

pycb.py:

import threading
import time

class CB(object):
    def __init__(self, cb):
        self._cb = cb

    def call(self, arg):
        print("Starting thread...")
        t = threading.Thread(target=self._cb, args=(arg,))
        t.start()
        time.sleep(1)
        print("Exiting callback")

#5

Doesn’t the python ctypes module have code to invoke a C function pointer? This should work fine with a cfunction.


#6

Thanks, that pointer was what I needed. After a bit of googling I’ve got it working nicely.