Edit: After some research, I have found a partial solution. The link_name
attribute can be used to change the name that is linked for an external variable. My question is now if this attribute can automatically be applied by bindgen
to resolve linking errors. Context:
I am trying to get my Rust project to work with Julia's C library. I trimmed down my original error to this code:
// main.rs
extern "C" {
pub static mut jl_method_type: *mut ();
}
fn main() {
println!("{:?}", unsafe { jl_method_type });
}
// build.rs
fn main() {
println!("cargo:rustc-link-search=C:/path/to/julia/lib/");
println!("cargo:rustc-link-lib=julia");
}
(I also had to rename the file libjulia.dll.a
to julia.lib
so that the linker could find it.)
The error produced is as follows:
note: reprod.jth9vslxkrhc6ez.rcgu.o : error LNK2019:
unresolved external symbol jl_method_type referenced
in function _ZN1reprod4main17hf5ae7fcf7e25b2b0E
To try and trim it down further, I replicated it in C++:
// Compile with clang -ljulia -LC:/path/to/julia/lib/ test.cpp -IC:/path/to/julia/include/julia/
extern "C" void* jl_method_type;
int main() {
auto local = jl_method_type;
return 0;
}
This produces the same error as before. I then found this SO question and realized I needed to modify the definition like so:
extern "C" __declspec(dllimport) void* jl_method_type;
The C++ program then compiled without any errors. However, I was unable to find an equivalent for Rust.
Some additional notes, the actual code I am using to interface with Julia uses bindgen
to generate the Rust bindings for the DLL. As far as I can tell from the linker errors, they work fine with the functions defined by the DLL but fail when trying to access global variables. This lines up with the problem in the linked question where adding __declspec() was optional on functions but required on variables.
Edit: I have found that using the name __impl_jl_method_type
without the __declspec(dllimport)
annotation results in a successful link. This also works in the Rust code. However, since the actual code I am using is generated by bindgen, it is missing this prefix. The code is also used by a crate that provides a safe wrapper around the bindings, so it seems like there should be a way to get everything to link correctly without having to change the variable names depending on the platform.
The ultimate solution turned out to be that Rust's #[link(name="libname", kind="dylib")]
annotation functions like the __declspec(dllimport)
annotation in C++. I added some code to my build script to automatically insert this annotation above every part where bindgen created bindings for a global variable:
let mut code = bindings.to_string();
if cfg!(target_os = "windows") {
code = code.replace(
"extern \"C\" {",
"#[link(name = \"julia\", kind = \"dylib\")]\r\nextern \"C\" {",
);
}
// Write the bindings to the $OUT_DIR/bindings.rs file.
let mut file = std::fs::File::create(&out_path).unwrap();
use std::io::Write;
file.write_all(code.as_bytes()).unwrap();
The result:
#[link(name="julia", kind="dylib")]
extern "C" {
pub static mut global_var: *mut some_bound_type_t;
}
Previous hacky solution in case any future people find it useful:
For now I have found a workaround by adding this to the build.rs
file which generates the bindings:
let mut code = bindings.to_string();
if (cfg!(target_os = "windows")) {
const BEFORE_GLOBAL: &'static str = "extern \"C\" {\r\n pub static mut ";
let mut prev_index = 0;
while let Some(index) = code[prev_index..].find(BEFORE_GLOBAL) {
let index = index + prev_index;
let name_start = BEFORE_GLOBAL.len();
let colon = code[index..].find(":").expect("Invalid syntax.");
let name = &code[index..][name_start..colon];
let annotation = format!("#[link_name = \"__imp_{}\"]\r\n ", name);
code.insert_str(index + "extern \"C\" {\r\n ".len(), &annotation[..]);
prev_index = index;
}
}
It modifies the generated code so that global variables look like this:
extern "C" {
#[link_name = "__imp_jl_vararg_type"]
pub static mut jl_vararg_type: *mut jl_unionall_t;
}