From 188167c4b3ab42f1d0dc793365819afe28719df5 Mon Sep 17 00:00:00 2001 From: Matthias Einwag Date: Sun, 12 Jan 2014 00:47:30 +0100 Subject: [PATCH 1/4] Introduced a chapter that describes how to perform callbacks from C to Rust --- doc/guide-ffi.md | 169 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) diff --git a/doc/guide-ffi.md b/doc/guide-ffi.md index 43ba0c5d443..4387d5d764e 100644 --- a/doc/guide-ffi.md +++ b/doc/guide-ffi.md @@ -249,6 +249,175 @@ fn main() { } ~~~~ +# Callbacks from C code to Rust functions + +Some external libraries require the usage of callbacks. +E.g. because they start background threads and use callbacks to signal events +like the availability of new data. +It is possible to pass functions defined in Rust to an external library. +The requirement for this is that the callback function is marked as `extern` +with the correct calling convention to make it callable from C code. + +The callback function that can then be sent to through a registration call +to the C library and afterwards be invoked from there. + +A basic example is: + +Rust code: +~~~~ +extern "C" fn callback(a:i32) { + println!("I'm called from C with value {0}", a); +} + +#[link(name = "extlib")] +extern { + fn register_callback(cb: extern "C" fn(i32)) -> i32; +} + +fn main() { + unsafe { + register_callback(callback); + } + ... // Do sth. and wait for callbacks +} +~~~~ + +C-Code: +~~~~ +typedef void (*rust_callback)(int32_t); +rust_callback cb; + +int32_t register_callback(rust_callback callback) { + cb = callback; + return 1; +} + +void thread() { + // do sth + cb(7); // Will call callback(7) in Rust +} +~~~~ + +Keep in mind that `callback()` will be called from a C thread and not from +a Rust thread or even your main thread. Therefore each data access is +especially unsafe and synchronization mechanisms must be used. + +## Targetting callbacks to Rust objects + +The former example showed how a global function can be called from C-Code. +However it is often desired that the callback is targetted to a special +Rust object. This could be the object that represents the wrapper for the +respective C object. + +This can be achieved by passing an unsafe pointer to the object down to the +C library. The C library can then include the pointer to the Rust object in +the notification. This will provide a unsafe possibility to access the +referenced Rust object in callback. + +If this mechanism is used it is absolutely necessary that no more callbacks +are performed by C library after the respective Rust object get's +destroyed. This can be achieved by unregistering the callback it the object's +destructor and designing the library in a way that guarantees that no +callback will be performed after unregistration. + +## Sychronzing callbacks with channels + +As already explained accessing data of a Rust object in a callback is unsafe +without synchronisation. Channels in Rust provide a mechanism +which can be used to forward events into Rust tasks. The idea is to create a +channel where the writing end (`Chan`) is used exclusively from the C callback +to queue events. The reading end (`Port`) is used in the Rust task which owns +the wrapper object. + +Putting this together a wrapper for a library that uses a background thread +that sends events could look like: + +Rust code: +~~~~ + +#[link(name = "extlib")] +extern { + fn init(target: *ExtLibWrapper, cb: extern "C" fn(*ExtLibWrapper, EventData)); + fn unregister(); +} + +struct EventData { + ... // Contains data that describes the event +} + +pub struct ExtLibWrapper { + // Channel is used privately + priv chan: comm::Chan, + + // The port is used by the Rust task to receive notifications + port: comm::Port +} + +impl ExtLibWrapper { + pub fn new() -> ~ EventData { + let (p,c):(Port,Chan) + = comm::Chan::new(); + + let wrapper = ~ExtLibWrapper{chan:c, port:p}; + + unsafe { + let wrapper_addr:*ExtLibWrapper = ptr::to_unsafe_ptr(wrapper); + init(wrapper_addr, callback); + } + } +} + +impl Drop for ExtLibWrapper { + fn drop(&mut self) { + // Unregister to avoid further callbacks + unsafe { unregister(); } + } +} + +extern "C" fn callback(target: *ExtLibWrapper, data: EventData) { + unsafe { + (*target).chan.send(data); // Forward the event data through channel + } +} +~~~~ + +C-Code: +~~~~ +typedef void (*rust_callback)(void* target, EventData data); +void* rust_target; +rust_callback cb; +mutex mtx; // Example mutex + +void init(void* target, rust_callback callback) { + rust_target = target; + cb = callback; +} + +void background_thread() { + // do sth + + // Lock the mutex to guarantee that callback is not performed after Rust + // object is destroyed + mutex_lock(mtx); + if (rust_target != 0) cb(rust_target, event_data); + mutex_unlock(mtx); + + // do sth +} + +void unregister() { + mutex_lock(mtx); + rust_target = 0; + mutex_unlock(mtx); +} +~~~~ + +Remark: This example will not work correctly if more than a single +`ExtLibWrapper` object is created. If this is required additional handles +have to be introduced which identify each object. E.g. `ExtLibWrapper` would +have to store the member of the associated C object as member and pass it +on each function call. + # Linking The `link` attribute on `extern` blocks provides the basic building block for From 67d83ac40af2c2e20c44e4c16b53e500880a7439 Mon Sep 17 00:00:00 2001 From: Matthias Einwag Date: Sun, 12 Jan 2014 01:10:31 +0100 Subject: [PATCH 2/4] Further details on channel idea --- doc/guide-ffi.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/guide-ffi.md b/doc/guide-ffi.md index 4387d5d764e..a33593007dc 100644 --- a/doc/guide-ffi.md +++ b/doc/guide-ffi.md @@ -329,6 +329,12 @@ channel where the writing end (`Chan`) is used exclusively from the C callback to queue events. The reading end (`Port`) is used in the Rust task which owns the wrapper object. +Depending on the type of data in the event you might want to convert callback +data from C into a more suitable Rust structure before sending it into the +channel. E.g. it makes sense to convert C strings (`char*`) into Rust strings. +You could also use Rust enumerations to differentiate between multiple types +of events and their data. + Putting this together a wrapper for a library that uses a background thread that sends events could look like: From 393191d914a14aa1a0ad10ffe54ff23940671353 Mon Sep 17 00:00:00 2001 From: Matthias Einwag Date: Sun, 12 Jan 2014 13:27:59 +0100 Subject: [PATCH 3/4] Update guide-ffi.md Simplified the first examples to demonstrate callbacks without other threads involved and shortened the elaboration about async callbacks. --- doc/guide-ffi.md | 162 ++++++++++++++++++----------------------------- 1 file changed, 62 insertions(+), 100 deletions(-) diff --git a/doc/guide-ffi.md b/doc/guide-ffi.md index a33593007dc..02c4059dc37 100644 --- a/doc/guide-ffi.md +++ b/doc/guide-ffi.md @@ -251,9 +251,8 @@ fn main() { # Callbacks from C code to Rust functions -Some external libraries require the usage of callbacks. -E.g. because they start background threads and use callbacks to signal events -like the availability of new data. +Some external libraries require the usage of callbacks to report back their +current state or intermediate data to the caller. It is possible to pass functions defined in Rust to an external library. The requirement for this is that the callback function is marked as `extern` with the correct calling convention to make it callable from C code. @@ -265,24 +264,25 @@ A basic example is: Rust code: ~~~~ -extern "C" fn callback(a:i32) { +extern fn callback(a:i32) { println!("I'm called from C with value {0}", a); } #[link(name = "extlib")] extern { fn register_callback(cb: extern "C" fn(i32)) -> i32; + fn trigger_callback(); } fn main() { unsafe { register_callback(callback); + trigger_callback(); // Triggers the callback } - ... // Do sth. and wait for callbacks } ~~~~ -C-Code: +C code: ~~~~ typedef void (*rust_callback)(int32_t); rust_callback cb; @@ -292,15 +292,14 @@ int32_t register_callback(rust_callback callback) { return 1; } -void thread() { - // do sth +void trigger_callback() { cb(7); // Will call callback(7) in Rust } ~~~~ -Keep in mind that `callback()` will be called from a C thread and not from -a Rust thread or even your main thread. Therefore each data access is -especially unsafe and synchronization mechanisms must be used. +In this example will Rust's `main()` will call `do_callback()` in C, +which would call back to `callback()` in Rust. + ## Targetting callbacks to Rust objects @@ -314,115 +313,78 @@ C library. The C library can then include the pointer to the Rust object in the notification. This will provide a unsafe possibility to access the referenced Rust object in callback. -If this mechanism is used it is absolutely necessary that no more callbacks -are performed by C library after the respective Rust object get's -destroyed. This can be achieved by unregistering the callback it the object's -destructor and designing the library in a way that guarantees that no -callback will be performed after unregistration. - -## Sychronzing callbacks with channels - -As already explained accessing data of a Rust object in a callback is unsafe -without synchronisation. Channels in Rust provide a mechanism -which can be used to forward events into Rust tasks. The idea is to create a -channel where the writing end (`Chan`) is used exclusively from the C callback -to queue events. The reading end (`Port`) is used in the Rust task which owns -the wrapper object. - -Depending on the type of data in the event you might want to convert callback -data from C into a more suitable Rust structure before sending it into the -channel. E.g. it makes sense to convert C strings (`char*`) into Rust strings. -You could also use Rust enumerations to differentiate between multiple types -of events and their data. - -Putting this together a wrapper for a library that uses a background thread -that sends events could look like: - Rust code: ~~~~ +struct RustObject { + a: i32, + // other members +} + +extern fn callback(target: *RustObject, a:i32) { + println!("I'm called from C with value {0}", a); + (*target).a = a; // Update the value in RustObject with the value received from the callback +} + #[link(name = "extlib")] extern { - fn init(target: *ExtLibWrapper, cb: extern "C" fn(*ExtLibWrapper, EventData)); - fn unregister(); + fn register_callback(target: *RustObject, cb: extern "C" fn(*RustObject, i32)) -> i32; + fn trigger_callback(); } -struct EventData { - ... // Contains data that describes the event -} - -pub struct ExtLibWrapper { - // Channel is used privately - priv chan: comm::Chan, - - // The port is used by the Rust task to receive notifications - port: comm::Port -} - -impl ExtLibWrapper { - pub fn new() -> ~ EventData { - let (p,c):(Port,Chan) - = comm::Chan::new(); - - let wrapper = ~ExtLibWrapper{chan:c, port:p}; - - unsafe { - let wrapper_addr:*ExtLibWrapper = ptr::to_unsafe_ptr(wrapper); - init(wrapper_addr, callback); - } - } -} - -impl Drop for ExtLibWrapper { - fn drop(&mut self) { - // Unregister to avoid further callbacks - unsafe { unregister(); } - } -} - -extern "C" fn callback(target: *ExtLibWrapper, data: EventData) { - unsafe { - (*target).chan.send(data); // Forward the event data through channel +fn main() { + // Create the object that will be referenced in the callback + let rust_object = ~RustObject{a: 5, ...}; + + unsafe { + // Gets a raw pointer to the object + let target_addr:*RustObject = ptr::to_unsafe_ptr(rust_object); + register_callback(target_addr, callback); + trigger_callback(); // Triggers the callback } } ~~~~ -C-Code: +C code: ~~~~ -typedef void (*rust_callback)(void* target, EventData data); -void* rust_target; +typedef void (*rust_callback)(int32_t); +void* cb_target; rust_callback cb; -mutex mtx; // Example mutex -void init(void* target, rust_callback callback) { - rust_target = target; +int32_t register_callback(void* callback_target, rust_callback callback) { + cb_target = callback_target; cb = callback; + return 1; } -void background_thread() { - // do sth - - // Lock the mutex to guarantee that callback is not performed after Rust - // object is destroyed - mutex_lock(mtx); - if (rust_target != 0) cb(rust_target, event_data); - mutex_unlock(mtx); - - // do sth -} - -void unregister() { - mutex_lock(mtx); - rust_target = 0; - mutex_unlock(mtx); +void trigger_callback() { + cb(cb_target, 7); // Will call callback(&rustObject, 7) in Rust } ~~~~ -Remark: This example will not work correctly if more than a single -`ExtLibWrapper` object is created. If this is required additional handles -have to be introduced which identify each object. E.g. `ExtLibWrapper` would -have to store the member of the associated C object as member and pass it -on each function call. +## Asynchronous callbacks + +In the already given examples the callbacks are invoked as a direct reaction +to a function call to the external C library. +The control over the current thread switched from Rust to C to Rust for the +execution of the callback, but in the end the callback is executed on the +same thread (and Rust task) that lead called the function which triggered +the callback. + +Things get more complicated when the external library spawns it's own threads +and invokes callbacks from there. +In these cases access to Rust data structures inside he callbacks is +especially unsafe and proper synchronization mechanisms must be used. +Besides classical synchronization mechanisms like mutexes one possibility in +Rust is to use channels (in `std::comm`) to forward data from the C thread +that invoked the callback into a Rust task. + +If an asychronous callback targets a special object in the Rust address space +it is also absolutely necessary that no more callbacks are performed by the +C library after the respective Rust object get's destroyed. +This can be achieved by unregistering the callback it the object's +destructor and designing the library in a way that guarantees that no +callback will be performed after unregistration. # Linking From 112d01a9510192c1e410c6e51e2b2f4d36bc6b63 Mon Sep 17 00:00:00 2001 From: Matthias Einwag Date: Mon, 20 Jan 2014 21:44:41 +0100 Subject: [PATCH 4/4] Disabled the tests for the new code blocks --- doc/guide-ffi.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/guide-ffi.md b/doc/guide-ffi.md index 02c4059dc37..236da56588e 100644 --- a/doc/guide-ffi.md +++ b/doc/guide-ffi.md @@ -263,7 +263,7 @@ to the C library and afterwards be invoked from there. A basic example is: Rust code: -~~~~ +~~~~ {.xfail-test} extern fn callback(a:i32) { println!("I'm called from C with value {0}", a); } @@ -283,7 +283,7 @@ fn main() { ~~~~ C code: -~~~~ +~~~~ {.xfail-test} typedef void (*rust_callback)(int32_t); rust_callback cb; @@ -314,7 +314,7 @@ the notification. This will provide a unsafe possibility to access the referenced Rust object in callback. Rust code: -~~~~ +~~~~ {.xfail-test} struct RustObject { a: i32, @@ -346,7 +346,7 @@ fn main() { ~~~~ C code: -~~~~ +~~~~ {.xfail-test} typedef void (*rust_callback)(int32_t); void* cb_target; rust_callback cb;