Creating a static library from Rust code, windows x64

Hi,

I'm trying to create a library in Rust that I can link to from C. My code in Rust is simply:

#[no_mangle]
pub extern "win64" fn foo() -> i32{
    10
}

which I think is correct. I compile with "cargo build" and get a .lib file. However, when I link the library from my C code and try to compile I get 1. a warning saying that library machine type x86 conflicts with my machine's type x64 and 2. an error saying foo is an unresolved symbol. I am linking to the library (clearly) and I have int foo(); in my C header.

What am I doing wrong?

Can you show us the error message and where it's coming from? It sounds like you are trying to load a 32-bit library from a 64-bit application at runtime (e.g. via LoadLibrary() or dlopen()), which doesn't work.

How are you compiling your library? You need to set crate-type to ["cdylib"] or ["staticlib"] in Cargo.toml to make sure it is compiled to a form usable from other languages (see the docs for more).

If you didn't set crate-type, I'm guessing it's complaining about missing symbols from the standard library because of how Rust's rlibs work.

Sorry about being unclear. I have a bad habit of only asking questions when I'm tired. At this point I have found a solution, kinda.

I was trying to link to a .lib output from the rust compiler. Compiling with cargo build --release, crate-type set to ["staticlib"] withing cargo.toml.

I tried reducing my problem more and using both mingw compiler as well as msvs. So I reduced my Rust code to:

#[no_mangle]
pub extern "win64" fn foo() {
    let x = 5;
    //  print!("hi Rust"); 
}

To more closely follow the example here: https://rust-embedded.github.io/book/interoperability/rust-with-c.html . Commented out print because that wasn't working either.

A.lib file results from the compilation. I try to compile the .cpp program:

#include <stdio.h>
void foo();
int main(){
	printf("hello C");
	foo();
	return 0;
}

with MSVS command "cl /EHsc cppfoo.cpp /link export_rust.lib" and get the error:

cppfoo.obj : error LNK2019: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ) referenced in function main
cppfoo.exe : fatal error LNK1120: 1 unresolved externals

with mingw command "g++ cppfoo.cpp export_rust.lib" I get:

undefined reference to `foo()'
collect2.exe: error: ld returned 1 exit status

I have tried changing pub extern "win64" to pub extern "C", "system", "stdcall", and "sysv64". All result in the same error messages. Funnily, the only thing I got to work was changing the program from .cpp to .c. This compiled and ran when the print! macro was commented out, but returned a slew of errors about undefined references. Regardless, changing .cpp file to .c is not a long term solution.

I don't know if it makes a difference, but when I run rustup default it lists my default toolchain as "stable-x86_64-pc-windows-msvc". I have not tried other toolchains.

Hey, @gnodarse.

You should set your crate type to staticlib:

[staticlib] is recommended for use in situations such as linking Rust code into an existing non-Rust application because it will not have dynamic dependencies on other Rust code.

For your original example, you should declare the function in C++ as:

extern "C" int foo();

By default, C++ will mangle all function declarations on its side.

In your second example, C++ mangled the declared foo into ?foo@@YAXXZ.

The linker looked for that mangled symbol, but the Rust .lib only exports foo, hence the "unresolved external symbol" linker error.

The extern "C" disables this mangling for foo.

C doesn't mangle the function declarations, and the C++ compiler must have picked up this behavior when seeing the .c extension.

By the way, if you have a lot of functions that you're exporting from a Rust static library, then you should look into cbindgen to create the C/C++ bindings for you.

On Windows 64-bit, I believe there's only a single x64 calling convention. You can export your Rust functions as any of:

extern "C" fn foo() -> i32
extern "system" fn foo() -> i32 // Same as C calling convention on x64
extern fn foo() -> i32 // Uses C calling convention by default

The Rustonomicon has more information about calling conventions.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.