How to receive a callback from Rust in C/C++ (C-API/FFI)


#1

The basic idea is that I have a Qt GUI application which runs a long running method implemented in Rust (through C API) and I want to receive a progress from it.

I have no idea how to implement it.

Maybe it can be implemented without callbacks. I don’t know.


#2

you can for example share AtomicUsize (for C/C++ you need c wrapper to get access),
and increment it in rust code time to time.

Or just pass pointer to c function to rust code.

In similar situtation I user pyqt5 and https://github.com/dgrunwald/rust-cpython to write
code with core on rust, and Qt interface.


#3

Does AtomicUsize will work in a single-threaded mode? I don’t wait in async now.

Passing a function is a good idea, but I’m not familiar with C-style callbacks. And C in general.

Is this a right way to do this? Or is there a better way?

extern "C" {
    typedef void (*callback_t)(MyClass *, int32_t);

    void test_callback(MyClass *, callback_t c);
}

void rust_callback(MyClass *p, int32_t v)
{
    qDebug() << "I'm called from Rust" << p << v;
}

void MyClass::someMethod() 
{
    test_callback(this, rust_callback);
}
#[no_mangle]
pub extern "C" fn test_callback(p: *mut c_void, cb: extern fn(*mut c_void, i32)) {
    println!("I'm called from C");
    cb(p, 3);
}

It works, kinda, by I prefer something like Qt’s connection:

    test_callback(&MyClass::callbackMethod);

#4

The new book doesn’t have an FFI chapter that I can find, but the old book does.

If Qt has main, then you can expose a Rust library function directly that will just return the current progress, and have Qt repeatedly call that until completion.

pub extern fn get_rust_status() -> i32 {
  somehow_acquire_current_status()
}
extern "C" int get_rust_status(void);
while ((int status = get_rust_status()) != 100) {
  display(status);
  sleep(100);
  /* don't actually do this; I'm locking the GUI thread here like an asshole */
}

You can also expose a Rust function that accepts a C function pointer and stores it. You could then call the C function every so often to send it updates, and this C function would deliver updates to Qt.

extern "C" fn rust_worker(void (*cb)(int));
void update_progress(int progress) {
  display(progress);
}
rust_worker(update_progress);
pub extern fn rust_worker(cb: extern fn(i32)) {
  let mut progress: i32 = 0;
  //  begin long work
  //  whenever you want to update progress:
  cb(progress);
}

#5

Your version is for async code, but I need a synchronous version.


#6

The second example is explicitly synchronous. I’ve elided the actual work, but rust_worker simply reports status to C periodically as it goes along. The C program fires up the rust_worker. In the Rust worker, we do some work, callback into C with current status, do some more work, call again, rinse and repeat until rust_worker terminates and the C program resumes.

The first example is assuming that the Rust worker is running in the background though, yeah.


#7

Yes, it does. only performance may be tiny worse in compare usize.

With code generations and template you can hide this code,
but in fact you should have such code in some form.

I’m afraid, but such variant is impossible,
you need pass this someway with member function pointer,
to force it work even if you use C++ instead of rust.

if add this (pointer/reference to object), you can for specific class write code,
and use bindgen to get hint how it should looks like:
cargo install bindgen

class C {
public:
  void f();
  void g();
};

void test(C &c, void (C::*cb)());

bindgen test.h -- -x c++-header


#8

I misunderstand the second example, sorry. But it’s the same as mine and you didn’t pass the object pointer/this. And I need one.

Async example is easier to write, but async execution between C++ and Rust through C-API doesn’t seems like a good/safe idea. I guess.


#9

Yes, it does. only performance may be tiny worse in compare usize.

But how I will knew that value is changed in single-threaded mode? Anyway, it not a very useful way, because I want to pass any kind of data.

With code generations and template you can hide this code

Currently I’m writing bindings by myself, sadly. Only solutuon I know is rusty-cheddar (which is dead) and it’s new fork - moz-cheddar (which I didn’t try yet).

you need pass this someway with member function pointer

Passing this is not a problem. I just want to receive a callback in a class method and not in global function. Just like Qt signal-slots.

and use bindgen to get hint how it should looks like

Thanks. I hadn’t thought about it. It generated:

pub fn test(c: *mut C, cb: ::std::option::Option<unsafe extern "C" fn()>);

from

void test(C &c, void (C::*cb)(int value));

But how to actually execute it? I don’t have the class definition in rust code. I need something like:

pub fn test(c: *mut c_void, cb: extern "C" fn(i32)) {
   (*c).cb(42);
}

Obviously I’m getting an error, because there are no methods in void.


#10

You really don’t want to try to directly invoke C++ member function pointers from Rust; there’s no portable way to do so (they behave very differently on different OSes).

C-style callbacks are the right answer, like in your second post.

EDIT: Actually, since these days bindgen can generate bindings to some regular C++ methods (not pointers), you may want to use that. But it’s broken on Windows…


#11

I see. I’d hear that C++ ABI is not “portable” and you should always stick to C.

C-style callbacks are the right answer, like in your second post.

Isn’t it too verbose?


#12

Isn’t it too verbose?

Depends on how many methods you want to wrap :slight_smile:

A lot of the work is saved if you use a binding generator, either bindgen or *cheddar or both.

One trick that may or may not help: C++ does let you cast a pointer to a static member function into a regular C function pointer, which can then be called from Rust. Since it’s static, you’d still need an explicit ‘this’-like argument, but it may look a bit cleaner to have the callback within the class (and you can access private fields).


#13

Sadly, both “cheddar” are broken, “rusty” doesn’t support ? and “moz” failed to find a modules:

error: module `parser` has not been brought into global scope

thread 'main' panicked at 'errors compiling header file', /home/razr/.cargo/registry/src/github.com-1ecc6299db9ec823/moz-cheddar-0.4.0/src/lib.rs:631

Also they disabled issues on GitHub, so I don’t know where to send bug reports.


#14

Seems they moved to GitLab.


#15

I’m talking about moz-cheddar. rusty-cheddar indeed moved to GitLab, but that repo is also dead.


#16

You should remember previous value, and do something if previous != current.

You should use as a hint, not as is, because of support of C++ still in not good shape in bindgen.

The code that should work is:

#[no_mangle]
pub fn test(c: *mut c_void, cb: Option<unsafe extern "C" fn(*mut c_void)>) {
    println!("rust test: begin");
    cb.map(|v| unsafe { v(c) });
}


struct Boo {
	int a;

	Boo(): a(17) {}
	void method1();
};
extern "C" void test(Boo *boo, void (Boo::*f)(void));

void Boo::method1() {
	printf("Boo::method1, a %d\n", a);
}

int main() {
	Boo boo;
	test(&boo, &Boo::method1);
}

#17

Thanks. That exactly what I’m looking for.


#18

Thanks. That exactly what I’m looking for.

But you should remember that this exact solution of exact problem.

In general you have undefined/unspecified behaviour here in terms of C++.

This solution not handle what happens if you pass pointer to parent c++ object,
and pointer to virtual member function, or about multiply inheritance and so on things.

So this solution works only with concrete compiler and concrete c++ code,
in general case, to make your code portable, you need write wrapper in form of C function,
and pass to Rust code pointer to C function, like gtk+ works for example.


#19

Thanks for explanation. I do not plan to use it heavily. Just to find out that it technically possible.


#20

Sorry, that was an oversight. I’ve enabled issues.