Is there an easy way to use a macro in an ffi library?


#1

Hi all,
Imagine I wanted to make a simple shared library in rust, that will be called from C, and will use a macro. Is there an easy way to do this? Everything I try hits a wall and I feel I must be missing something.

Let’s start simple; cargo new --lib my-lib
Then replace the test in lib.rs with:

fn my_function(in: u32) -> u32 {
return in * 2;
}

Now, to make this exportable, we first have to jump through a few hoops:

  • It needs to be public, etc, so add "#[no_mangle] pub extern " on the front of that definition.
  • But now it can’t be public in lib.rs (why?!?), so rename lib.rs to thing.rs, and put “mod thing;” in lib.rs.

This works, so now let’s try to introduce some basic logging. Add the log crate to Cargo.toml, add a “trace!(“test”);” to the top of my_function, and hit a wall…

If you put “#[macro_use] extern crate log;” at the top of thing.rs then you get an error saying:
error[E0468]: an extern crate loading macros must be at the crate root

and if you search you find this stack-overflow question: https://stackoverflow.com/questions/39175953/how-do-you-import-macros-in-submodules-in-rust but nothing there works.

If I move the “#[macro_use] extern crate log;” to the top of lib.rs, then I get
error: cannot find macro trace! in this scope
when it hits the use of ‘trace!’.

If I add an additional “#[macro_use] extern crate log;”, so it is at the top of both thing.rs and lib.rs, then I get the original error - it doesn’t want the macros_use annotation at the top of the sub-module. If I remove that annotation and just have “extern crate log”, that doesn’t work. Some searching reveals that maybe I need to “use” something instead of “extern crate” in thing.rs, but at this stage I’m just frustrated - it’s time to ask for help.

I have no good mental model for how crates/modules work in rust. They cause me WAY more pain than borrowing (which I really don’t find hard at all). In fact, I don’t want to use modules here at all - I only have one function - but I’m being forced to use a module to make that function public.

Help. What is the easy way to make a one-function example ffi library that uses a macro?

Thanks.


#2

Why, indeed? What error are you getting, it seems I can do this just fine.

Your difficulties with using the macro also sound unusual. Having the #[macro_use] extern crate log; at the top of lib.rs (crucially, before mod thing;) should be enough to use its macros in thing.rs.


#3

Here’s a minimal sample that compiles:

File structure

.
├── Cargo.toml
├── src
│   └── lib.rs
└── test.c

Cargo.toml:

[package]
authors = ["Michael Lamparski <diagonaldevice@gmail.com>"]
name = "externit"
version = "0.1.0"

[lib]
# Here I chose to use dynamic linking.
# Another option for FFI is 'staticlib'.
crate-type = ['cdylib']

[dependencies]
log = "0.4.1"

src/lib.rs

#[macro_use]
extern crate log;

use ::std::os::raw::c_int;

#[no_mangle]
pub extern fn foo(i: c_int) -> c_int {
    trace!("input was: {}", i);
    i * 2
}

test.c:

#include <stdio.h>

extern int foo(int x);

void main(int argc, char **argv) {
    int x = foo(2);
    printf("Twice two is: %d\n", x);
}

Building/running on unix:

$ cargo build --release  # creates target/release/libexternit.so
$ gcc test.c -Ltarget/release -lexternit -otest  # creates executable ./test
$ LD_LIBRARY_PATH=target/release ./test
Twice two is: 4

Notice however that the trace! macro didn’t print anything because no log handler was set up. (the crate would also need to depend on something like env_logger, and you should probably expose a separate extern fn for initializing the logger.)


#4

@ExpHP dylib is for Rust, cdylib is C-compatible.


#5

Yikes!! Thanks for the correction!


#6

I can’t replicate the error now. I don’t know what I was doing that caused the problem - it was a few days ago. I was getting dead code warnings when I didn’t have ‘pub’, so then I added that but it didn’t solve the problem.

First - thank you!

Second. Out of interest, how do you you make this work with a module? (While I don’t need it yet, I assume I’ll get there at some point.)

Thanks again.


#7

If you’re wanting to use macros defined in a sub-module, you can add #[macro_use] above the mod foo; statement.

A good example of this is the standard library. They’ve defined a bunch of macros in the macros.rs file, and are then adding a #[macro_use] to the mod macros statement in lib.rs.

If you’re wanting to use macros declared in another crate you need to add #[macro_use] over the extern crate foo line in your main lib.rs file though.


#8

I’ll be honest, the module system was one of the things I had the most frustration with when I was learning rust as well, so I can feel where you’re coming from. Part of the trouble is that paths like a::b::c have a different meaning in use statements and outside of use statements (and worse, they are equivalent in the root module, and so code can sometimes appear to mysteriously break when moved into a module!).

I’m going to try to make things blindingly explicit:

Example 1: An inline module

You can take my example from the other post and replace lib.rs with the following:

// 1. `extern crate` should always be in the root module.
//    Exceptions to this are extremely rare and really only ever
//    show up in generated code.
// 2. Things with `#[macro_use]` must always come before code
//    that uses the macro. (this is one of the few places where
//    order matters in rust)
#[macro_use]
extern crate log;

// I'm using an inline module for clarity, because ultimately this is what
// all mod statements desugar into. (and they're easier to experiment with)
//
// This is identical to writing `mod thing;` and moving the contents of
// the braces into either `thing.rs` or `thing/mod.rs`.
mod thing {
    // note: Even though we are still in lib.rs, this code is no longer
    //       considered to be in the "root module".  It is in "::thing".

    // This must be here instead of the root module.
    //
    // You can think of `use` as similar to defining an item at that
    // location in the code. (and inner modules cannot automatically
    // see stuff defined in outer modules)
    use ::std::os::raw::c_int;

    #[no_mangle]
    pub extern fn foo(i: c_int) -> c_int {
        trace!("input was: {}", i);
        i * 2
    }
}

// Things with #[no_mangle] must be re-exported at the root level
// for it to properly take effect.
//
// Re-exporting is done with 'pub use.'
//
// There are three equivalent ways you could write this:
//
//    pub use thing::foo; // an absolute path from the root module
//                        // (however, this syntax confusingly means something
//                        //  else outside of `use`, so I recommend that learners
//                        //  avoid using this form initially)
//
//    pub use ::thing::foo; // also an absolute path from the root module.
//                          // (this one does not have the inconsistency I
//                          //  mentioned earlier)
//
//    pub use self::thing::foo; // relative to the current module. (`self`)
//                              // (which in this case IS the root module,
//                              //  so it doesn't matter TOO much...)
//
pub use ::thing::foo;

Example 2: Separate files

As stated in the comments, Example 1 is identical to having these two files:

lib.rs

#[macro_use]
extern crate log;

mod thing;

pub use ::thing::foo;

thing.rs (or thing/mod.rs)

use ::std::os::raw::c_int;

#[no_mangle]
pub extern fn foo(i: c_int) -> c_int {
    trace!("input was: {}", i);
    i * 2
}

#9

Thank you!