Passing a Rust function pointer to C for logging library's stderr output (libxml FFI)

Having successfully implemented the FFI to be able to parse XML docs, I am now trying to redirect libxml's stderr output into my own log function inside my Rust application (wrapping the log crate macros) so that I can keep any errors together with the rest of the debug logging.

According to the libxml docs, I need to use initGenericErrorDefaultFunc (xmlGenericErrorFunc * handler) to specify the handler for errors, which I think takes a pointer to my Rust function, but I am unsure how to pass my Rust function to C via the FFI. Using the first edition of The Book, I ended up with:

pub enum XmlGenericErrorFunc {}

#[link(name = "xml2")]
extern "C" {
    pub fn initGenericErrorDefaultFunc(handler: XmlGenericErrorFunc);
    pub fn xmlGenericErrorFunc(
        ctx: *mut XmlValidCtxtPtr,
        msg: *const c_char,
    ) -> XmlGenericErrorFunc;
}

#[no_mangle]
extern fn error_func_redirect(msg: *const c_char) {
    error!("libxml error output: {:?}", msg);
}

As soon as I've setup my context, I'm then trying to tell libxml about my Rust function (which I thought I had to pass the function signature in via):

let cvp_ptr: *mut XmlValidCtxtPtr = create_context_ptr()?;
unsafe {
    let error_function_name = std::ffi::CString::new("error_func_redirect(msg: *const c_char)").unwrap();
    let error_func = xmlGenericErrorFunc(cvp_ptr, error_function_name.as_ptr());
    initGenericErrorDefaultFunc(error_func);
}

But I get a linker error:

Undefined symbols for architecture x86_64:                                                                              
            "_xmlGenericErrorFunc", referenced from:                                                                              
                validator::XmlValidator::new::h3155275e1d619a12 in libvalidator-206fd62ca886041b.rlib(validator-206fd62ca886041b.4pmkvih2bq1byoha.rcgu.o)
          ld: symbol(s) not found for architecture x86_64                                                                         
          clang: error: linker command failed with exit code 1 (use -v to see invocation)                     

I'm now in a tangle trying to work out how I correctly pass a pointer to my Rust function through into libxml. Any tips most appreciated!

You need to define a function pointer type, not a function and an unrelated enum.

extern "C" {
    pub fn initGenericErrorDefaultFunc(handler: xmlGenericErrorFunc);
}
pub type xmlGenericErrorFunc = Option<extern "C" fn(ctx: *const libc::c_void, msg: *const libc::c_char, ...)>;

Also, your Rust-side function has to match that signature; you can't just ignore the ctx argument. I'm not sure you can implement such a function, though. Last I checked, Rust doesn't support defining C ABI functions with variadics, and I couldn't find anything to contradict that (heck, the new edition of the book doesn't mention them at all).

1 Like

There's an approved RFC to enable definition of variadic C ABI functions, but the implementation hasn't landed yet: https://github.com/rust-lang/rust/pull/49878

1 Like

Thank you @DanielKeep and @sfackler - I'm going to redirect the stderr output to file for now, and keep an eye on the RFC for future.

You can use the cc create to add your own varargs wrapper function in C.

You can use the cc create to add your own varargs wrapper function in C.