C to Rust FFI library linking in Windows

Rust Once, Run Everywhere post from Rust official blog, led me to the author’s repository with FFI examples. I was able to run “Rust to C” example, but “C to Rust” gives me a little headache.

Here’s what I did:

  1. I compiled the Rust code to library using:
cargo build
  1. There’s a Makefile in the repository, but since I’m on Windows, and using mscv version of the rustc, I couldn’t use it. I tried to make executable, by linking C source with the library:
cl src\main.c target\debug\double_input.lib
  1. Compiler (or rather linker) started to yell at me:
Microsoft (R) C/C++ Optimizing Compiler Version 19.20.27508.1 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

main.c
Microsoft (R) Incremental Linker Version 14.20.27508.1
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:main.exe
main.obj
target\debug\double_input.lib
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol freeaddrinfo referenced in function _ZN4core3ptr18real_drop_in_place17h8f7164cb4c08a80cE
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol closesocket referenced in function _ZN4core3ptr18real_drop_in_place17hd12d400f3f6dc52fE
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol WSACleanup referenced in function _ZN50_$LT$F$u20$as$u20$alloc..boxed..FnBox$LT$A$GT$$GT$8call_box17hda1367731cbd7b29E
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol OpenProcessToken referenced in function _ZN3std3env8home_dir17hcfcc8baa85f07218E
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol GetUserProfileDirectoryW referenced in function _ZN3std3env8home_dir17hcfcc8baa85f07218E
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol getpeername referenced in function _ZN3std3net3tcp9TcpStream9peer_addr17h4fda3ba35bb8aa36E
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol WSAGetLastError referenced in function _ZN3std3net3tcp9TcpStream9peer_addr17h4fda3ba35bb8aa36E
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol shutdown referenced in function _ZN3std3net3tcp9TcpStream8shutdown17h85198a6ac8f9970aE
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol recv referenced in function _ZN3std3net3tcp9TcpStream4peek17hb15011b3e7917eb7E
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol setsockopt referenced in function _ZN3std3net3tcp9TcpStream11set_nodelay17hf7caa446ab5ce1d7E
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol WSARecv referenced in function _ZN58_$LT$std..net..tcp..TcpStream$u20$as$u20$std..io..Read$GT$13read_vectored17hafff0ac29a6c2f3cE
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol send referenced in function _ZN59_$LT$std..net..tcp..TcpStream$u20$as$u20$std..io..Write$GT$5write17hab6a293bc8927e9aE
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol WSASend referenced in function _ZN59_$LT$std..net..tcp..TcpStream$u20$as$u20$std..io..Write$GT$14write_vectored17he9669e435a174b0cE
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol getsockname referenced in function _ZN3std3net3tcp11TcpListener10local_addr17h2e8ac0f973ead7b7E
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol ioctlsocket referenced in function _ZN3std3net3tcp11TcpListener15set_nonblocking17heca205f14b171f87E
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol WSAStartup referenced in function _ZN3std4sync4once4Once9call_once28_$u7b$$u7b$closure$u7d$$u7d$17h151cd75414ec4185E
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol getsockopt referenced in function _ZN3std10sys_common3net10getsockopt17heed543cec873071bE
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol getaddrinfo referenced in function _ZN120_$LT$std..sys_common..net..LookupHost$u20$as$u20$core..convert..TryFrom$LT$$LP$$RF$$u27$a$u20$str$C$$u20$u16$RP$$GT$$GT$8try_from17h86ace16b016ac604E
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol connect referenced in function _ZN3std10sys_common3net9TcpStream7connect17h223cef679bf78102E
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol bind referenced in function _ZN3std10sys_common3net11TcpListener4bind17he4c8532dd61a943aE
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol listen referenced in function _ZN3std10sys_common3net11TcpListener4bind17he4c8532dd61a943aE
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol sendto referenced in function _ZN3std10sys_common3net9UdpSocket7send_to17h37852fb24b8a7411E
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol WSASocketW referenced in function _ZN3std3sys7windows3net6Socket3new17hbc4ce7cb35631eb9E
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol select referenced in function _ZN3std3sys7windows3net6Socket15connect_timeout17h02959efe5714bddfE
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol accept referenced in function _ZN3std3sys7windows3net6Socket6accept17h057430023cc7e91cE
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol WSADuplicateSocketW referenced in function _ZN3std3sys7windows3net6Socket9duplicate17h5325bb6f6710a926E
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol recvfrom referenced in function _ZN3std3sys7windows3net6Socket20recv_from_with_flags17h8e22e1848c76061dE
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol SystemFunction036 referenced in function _ZN3std3sys7windows4rand19hashmap_random_keys17hd50cf806ba3f66a5E
main.exe : fatal error LNK1120: 28 unresolved externals
  1. I tried the same with clang compiler, with similar result.
  2. I changed target library type in Cargo.toml to ‘dylib’ and repeated all previous steps.This time it worked:
cl  src\main.c target\debug\double_input.dll.lib
  1. I changed library target back to ‘staticlib’ and build a release version:
cargo build --release
  1. I repeated step 2, this time using release version of the library:
cl src\main.c target\release\double_input.lib
  1. It worked. OK, now I’m confused.

Is this a standard behavior, or is there a way to also link the ‘debug’ variant of a Rust library? Maybe I’m missing something really obvious and my approach may seem naive, but I’m used to build things ‘by hand’ and I would love to know, how cargo is running the show behind the scenes.

If I’m doing something stupid, please let me know. I’m here to learn.

My environment:
rustc 1.34.0 (91856ed52 2019-04-10),
Windows 10 Pro,
Visual Studio Community 2019,
LLVM 8.0

example, but “C to Rust” gives me a little headache.
double_input.lib(std-c1e537280a7eb2d9.std.3xiyrb3s-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol getpeername referenced in function _ZN3std3net3tcp9TcpStream9peer_addr17h4fda3ba35bb8aa36E

If you do not change c-to-rust example (original one does not contain TcpStream), I beg on linker.
During optimization it removes not used code, so dependicies like TcpStream from stdlib were removed, while in debug build no “remove unused stuff” step.

In fact you need run rustc --crate-name static_lib src/lib.rs --crate-type staticlib --print=native-static-libs during build and link with them.

Look for example how I do it in one of my projects https://github.com/Dushistov/rust_swig/blob/master/c%2B%2B_tests/c%2B%2B/cmake/rust_cargo.cmake ,
you can generate VS project from cmake file , or open it directly if you use last version of VS.

Thank you for a great tip, Dushistov. I was able to generate a list of missing dependencies and compile working executable.

If you do not change c-to-rust example (original one does not contain TcpStream ), I beg on linker.
During optimization it removes not used code, so dependicies like TcpStream from stdlib were removed, while in debug build no “remove unused stuff” step.

Could you clarify this? Do you mean Rust or C linker/compiler? Why optimize code out from the library in the first place, and even if it was not a library, why optimize in Debug mode, anyway?

There is no “Rust linker” for final executable like: exe or OS’s shared library,
by default on Windows/Linux/MacOS/… rust toolchains uses external linker.
For msvc it uses obviously Microsoft linker.
But this is not important, because of in case of C's main function and Rust library case msvc linker is used any way.

The linker can remove functions and data if it can prove that it is not used in output library/executable (
https://devblogs.microsoft.com/cppblog/introducing-gw-compiler-switch/ ).

May be it is not clear, but my theory that in debug case optimization this optimization turn off,
so linker deals with “your C code + your Rust code + Rust’s stdlib”,
TcpStream depends winsocks library so your compilation failed,

but in release build optimization turn on, so linker find out that there is no need for TcpStream, so it removes it completely, and so there is no need for winsocks library and all works fine.
But this is just my theory.

I think it clicked in my head now.

Here’s my current mental model: do development and internal testing (cargo test) in debug mode, but if you want to test your library with C code, you should always use release build. How’s that for a summary?

Looks wrong, for example if you add into your Rust program work with network build failed in release and in debug mode.

From my point of view:

  1. If you link statically with Rust code, you should have Pre-Link script that call rustc print=native-static-libs and link with these libraries. With new Rust’s toolchain release list of dependencies can change. So you should have automatization of step.

  2. If can link dynamicaly (use crate-type = ["cdylib"]), then you no need any Pre-Link automazation,
    but you should distribute two files instead of one: C exe and Rust dll.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.