1

I can compile C-based dynamic library with Rust code, can I do the same with C++ -based dynamic library?

My actions 1 cargo new <project_name>

2 create foo.cpp

#include "stdint.h"

int32_t add(int32_t a, int32_t b) {
    return a + b;
}

3 Use c++ code in main.rs

extern "C++" {
    fn add(x: i32, y: i32) -> i32;
}

fn main() {
    let x = unsafe { add(62, 30)};
    println!("{}", x); // 92
}

4 Compile

g++ -fPIC -shared -o libfoo.so foo.cpp # create dynamic link library (DLL) with name libfoo.so 
rustc -l foo -L . main.rs # link DLL with name libfoo.so  with main.rs binary

Error

error[E0703]: invalid ABI: found `C++`
 --> tempCodeRunnerFile.rs:1:8
  |
1 | extern "C++" {
  |        ^^^^^ invalid ABI
  |
  = note: invoke `rustc --print=calling-conventions` for a full list of supported calling conventions.

Also I was trying to replace extern "C++" with "C"

error: linking with `cc` failed: exit status: 1
  |
  = note: LC_ALL="C" PATH="/Users/mascai/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/a ...  ld: symbol(s) not found for architecture arm64

P.S:

    g++ -v  Apple clang version 13.0.0 (clang-1300.0.27.3)
    rustc --version rustc 1.68.2 (9eb3afe9e 2023-03-27)
Botje
  • 26,269
  • 3
  • 31
  • 41
mascai
  • 1,373
  • 1
  • 9
  • 30
  • 2
    If you use `extern "C"` in Rust, you need to do that in the C++ code as well. – Some programmer dude May 08 '23 at 07:46
  • 1
    Note: the dynamic library extension on macOS is `.dylib`, not `.so` (or `.iso` as the pre-edit version of your post stated) – Botje May 08 '23 at 07:52
  • Does this answer your question? [How to call a C++ dynamic library from Rust?](https://stackoverflow.com/questions/52923460/how-to-call-a-c-dynamic-library-from-rust) – Chayim Friedman May 08 '23 at 09:13

1 Answers1

3

When you obtain a dynamic library from this code

#include "stdint.h"
int32_t add(int32_t a, int32_t b) { return a + b; }

the symbol of this function is not add but _Z3addii

$ nm -D libfoo.so | grep -F add
00000000000010e9 T _Z3addii

This happens because C++ allows function overloading (i.e. multiple functions with the same name but different parameters) then the name of the function itself is not sufficient to find it in the library (many functions could have the same name).

As an experiment, let's try to invoke the function with its mangled name (bad practice, we will come back to it later). In main.rs, we simply replace add by this mangled name.

extern "C" { fn _Z3addii(x: i32, y: i32) -> i32; }
...
let x = unsafe { _Z3addii(62, 30) };

and this time it works.

$ rustc -l foo -L . main.rs
$ LD_LIBRARY_PATH=. ./main
~~> 92

The problem is that the name-mangling is not standard in the language and may change from one compiler chain to another, so relying on this specific name is a bad practice, since it's not portable. Moreover, as stated in a comment below, nothing guaranties that the calling conventions used for this C++ function conforms to the C ABI expected from the Rust side (extern "C"); thus this experimentation just works « by chance ».

The reliable way to find this symbol, is to inform the C++ compiler that we don't want name-mangling for this specific function (as C does, since function overloading is not allowed). Of course, only one function with this specific name (add) can have this restriction, otherwise there would be a collision on the symbol name.

#include "stdint.h"
extern "C" int32_t add(int32_t a, int32_t b) { return a + b; }

This time the symbol is exactly the function name

$ nm -D libfoo.so | grep -F add
00000000000010e9 T add

and this functions conforms to the C ABI in order to be invoked. Then main.rs can be restored to

extern "C" { fn add(x: i32, y: i32) -> i32; }
...
let x = unsafe { add(62, 30) };

and everything works as expected

$ rustc -l foo -L . main.rs
$ LD_LIBRARY_PATH=. ./main
~~> 92

Note that our binary has to find at run-time the dynamic library it relies on. This is why the LD_LIBRARY_PATH environment variable is used. On other operating systems, this could change: DYLD_LIBRARY_PATH on MacOS, PATH on Windows (or implicitly found in the current directory). And the name of the library can also change depending on the system: libfoo.dylib on MacOS, foo.dll on Windows.

Note also that there exists some tools in order to ease such things.

prog-fh
  • 13,492
  • 1
  • 15
  • 30