Skip to content

Commit 188167c

Browse files
committed
Introduced a chapter that describes how to perform callbacks from C to Rust
1 parent 33e8663 commit 188167c

File tree

1 file changed

+169
-0
lines changed

1 file changed

+169
-0
lines changed

doc/guide-ffi.md

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,175 @@ fn main() {
249249
}
250250
~~~~
251251

252+
# Callbacks from C code to Rust functions
253+
254+
Some external libraries require the usage of callbacks.
255+
E.g. because they start background threads and use callbacks to signal events
256+
like the availability of new data.
257+
It is possible to pass functions defined in Rust to an external library.
258+
The requirement for this is that the callback function is marked as `extern`
259+
with the correct calling convention to make it callable from C code.
260+
261+
The callback function that can then be sent to through a registration call
262+
to the C library and afterwards be invoked from there.
263+
264+
A basic example is:
265+
266+
Rust code:
267+
~~~~
268+
extern "C" fn callback(a:i32) {
269+
println!("I'm called from C with value {0}", a);
270+
}
271+
272+
#[link(name = "extlib")]
273+
extern {
274+
fn register_callback(cb: extern "C" fn(i32)) -> i32;
275+
}
276+
277+
fn main() {
278+
unsafe {
279+
register_callback(callback);
280+
}
281+
... // Do sth. and wait for callbacks
282+
}
283+
~~~~
284+
285+
C-Code:
286+
~~~~
287+
typedef void (*rust_callback)(int32_t);
288+
rust_callback cb;
289+
290+
int32_t register_callback(rust_callback callback) {
291+
cb = callback;
292+
return 1;
293+
}
294+
295+
void thread() {
296+
// do sth
297+
cb(7); // Will call callback(7) in Rust
298+
}
299+
~~~~
300+
301+
Keep in mind that `callback()` will be called from a C thread and not from
302+
a Rust thread or even your main thread. Therefore each data access is
303+
especially unsafe and synchronization mechanisms must be used.
304+
305+
## Targetting callbacks to Rust objects
306+
307+
The former example showed how a global function can be called from C-Code.
308+
However it is often desired that the callback is targetted to a special
309+
Rust object. This could be the object that represents the wrapper for the
310+
respective C object.
311+
312+
This can be achieved by passing an unsafe pointer to the object down to the
313+
C library. The C library can then include the pointer to the Rust object in
314+
the notification. This will provide a unsafe possibility to access the
315+
referenced Rust object in callback.
316+
317+
If this mechanism is used it is absolutely necessary that no more callbacks
318+
are performed by C library after the respective Rust object get's
319+
destroyed. This can be achieved by unregistering the callback it the object's
320+
destructor and designing the library in a way that guarantees that no
321+
callback will be performed after unregistration.
322+
323+
## Sychronzing callbacks with channels
324+
325+
As already explained accessing data of a Rust object in a callback is unsafe
326+
without synchronisation. Channels in Rust provide a mechanism
327+
which can be used to forward events into Rust tasks. The idea is to create a
328+
channel where the writing end (`Chan`) is used exclusively from the C callback
329+
to queue events. The reading end (`Port`) is used in the Rust task which owns
330+
the wrapper object.
331+
332+
Putting this together a wrapper for a library that uses a background thread
333+
that sends events could look like:
334+
335+
Rust code:
336+
~~~~
337+
338+
#[link(name = "extlib")]
339+
extern {
340+
fn init(target: *ExtLibWrapper, cb: extern "C" fn(*ExtLibWrapper, EventData));
341+
fn unregister();
342+
}
343+
344+
struct EventData {
345+
... // Contains data that describes the event
346+
}
347+
348+
pub struct ExtLibWrapper {
349+
// Channel is used privately
350+
priv chan: comm::Chan<EventData>,
351+
352+
// The port is used by the Rust task to receive notifications
353+
port: comm::Port<EventData>
354+
}
355+
356+
impl ExtLibWrapper {
357+
pub fn new() -> ~ EventData {
358+
let (p,c):(Port<EventData>,Chan<EventData>)
359+
= comm::Chan::new();
360+
361+
let wrapper = ~ExtLibWrapper{chan:c, port:p};
362+
363+
unsafe {
364+
let wrapper_addr:*ExtLibWrapper = ptr::to_unsafe_ptr(wrapper);
365+
init(wrapper_addr, callback);
366+
}
367+
}
368+
}
369+
370+
impl Drop for ExtLibWrapper {
371+
fn drop(&mut self) {
372+
// Unregister to avoid further callbacks
373+
unsafe { unregister(); }
374+
}
375+
}
376+
377+
extern "C" fn callback(target: *ExtLibWrapper, data: EventData) {
378+
unsafe {
379+
(*target).chan.send(data); // Forward the event data through channel
380+
}
381+
}
382+
~~~~
383+
384+
C-Code:
385+
~~~~
386+
typedef void (*rust_callback)(void* target, EventData data);
387+
void* rust_target;
388+
rust_callback cb;
389+
mutex mtx; // Example mutex
390+
391+
void init(void* target, rust_callback callback) {
392+
rust_target = target;
393+
cb = callback;
394+
}
395+
396+
void background_thread() {
397+
// do sth
398+
399+
// Lock the mutex to guarantee that callback is not performed after Rust
400+
// object is destroyed
401+
mutex_lock(mtx);
402+
if (rust_target != 0) cb(rust_target, event_data);
403+
mutex_unlock(mtx);
404+
405+
// do sth
406+
}
407+
408+
void unregister() {
409+
mutex_lock(mtx);
410+
rust_target = 0;
411+
mutex_unlock(mtx);
412+
}
413+
~~~~
414+
415+
Remark: This example will not work correctly if more than a single
416+
`ExtLibWrapper` object is created. If this is required additional handles
417+
have to be introduced which identify each object. E.g. `ExtLibWrapper` would
418+
have to store the member of the associated C object as member and pass it
419+
on each function call.
420+
252421
# Linking
253422

254423
The `link` attribute on `extern` blocks provides the basic building block for

0 commit comments

Comments
 (0)