Loading wkhtmltopdf DLL (`wkhtmltox.dll`) on Windows - rust

I have installed wkhtmltopdf using the Windows installer and am using the wkhtmltopdf crate. wkhtmltopdf is installed at C:\Program Files\wkhtmltopdf, which contains (among other things) bin/wkhtmltox.dll and lib/wkhtmltox.lib.
I have successfully gotten Rust to link the .lib at build time with the following build.rs:
fn main() {
#[cfg(windows)]
{
println!("cargo:rustc-link-search=C:/Program Files/wkhtmltopdf/lib");
println!("cargo:rustc-link-lib=dylib=wkhtmltox");
}
}
That (I think) works fine for building; at least, it appears to build successfully (and without this build.rs, it fails to build). For running, I've tried using the windows crate to load the DLL at runtime as follows:
#[tokio::main]
async fn main() -> LSResult<()> {
println!("starting main");
#[cfg(windows)]
{
println!("cfg(windows)");
use windows::{
core::PCSTR,
Win32::{
Foundation::HANDLE,
System::LibraryLoader::{
AddDllDirectory, LoadLibraryExA, SetDefaultDllDirectories,
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR,
},
},
};
// Add the wkhtmltopdf DLL path to the DLL search path
let dll_path = r#"C:\Program Files\wkhtmltopdf\bin\wkhtmltox.dll"#;
let dll_path_pcstr_bytes = dll_path
.bytes()
.chain(std::iter::once(0))
.collect::<Vec<_>>();
unsafe {
LoadLibraryExA(
PCSTR(dll_path_pcstr_bytes.as_ptr() as *const u8),
HANDLE(0),
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR,
);
}
}
println!("about to get pdf");
wkhtmltopdf::PdfApplication::new().expect("Failed to init PDF application");
println!("got pdf");
}
But, try as I might, I cannot get the DLL to be loaded at runtime. I always get the following error at runtime:
error: process didn't exit successfully: target\\debug\\my_crate.exe (exit code: 0xc0000135, STATUS_DLL_NOT_FOUND)
C:/Users/me/.cargo/bin/cargo.exe: error while loading shared libraries: ?: cannot open shared object file: No such file or directory
None of the println! statements are executed; as soon as cargo run starts running the executable, the STATUS_DLL_NOT_FOUND is thrown. To me this indicates that Rust is trying to load the DLL before main is even called, and failing because it's not on the default DLL search path.
If I comment out the line wkhtmltopdf::PdfApplication::new().expect("Failed to init PDF application"); then it runs fine, everything is printed, etc. So it seems I have to find a way to point the executable to the DLL before even running it!
Also, after installing wkhtmltopdf on my Mac, things "just worked" without needing to do any explicit loading of the dylib. I assume this is because the Mac installation places the shared libraries in a standard location, whereas the Windows installation does not. But on Windows I cannot customize the installation of wkhtmltopdf; rather I must somehow point the executable to the DLL, and seemingly must do so before main is called.

Related

Statically linking screen_capture_lite in Rust

I am attempting to write a Rust wrapper for screen_capture_lite, my code for which is located here. I've gotten the cmake crate compiling screen_capture_lite from source, but I get 'unresolved external symbol' linker errors when trying to run my window_count example script. These errors appear to be for Windows API symbols or C++ standard library symbols. Here is a pastebin with the errors.
From what I've figured out about linking, I assume this is because I'm trying to statically link to screen_capture_lite, which gets rid of screen_capture_lite's dynamic dependencies. This seems true, because when I link to all of the system libraries manually, my window_count example script works and correctly reports the number of open windows. This is what my build script looks like when I do that:
extern crate cmake;
use cmake::Config;
fn main() {
let dest = Config::new(".").profile("Release").build();
println!("cargo:rustc-link-search={}", dest.join("lib").display());
println!("cargo:rustc-link-lib=static=screen_capture_lite");
println!("cargo:rustc-link-lib=kernel32");
println!("cargo:rustc-link-lib=user32");
println!("cargo:rustc-link-lib=gdi32");
println!("cargo:rustc-link-lib=winspool");
println!("cargo:rustc-link-lib=shell32");
println!("cargo:rustc-link-lib=ole32");
println!("cargo:rustc-link-lib=oleaut32");
println!("cargo:rustc-link-lib=uuid");
println!("cargo:rustc-link-lib=comdlg32");
println!("cargo:rustc-link-lib=advapi32");
println!("cargo:rustc-link-lib=dwmapi");
}
Manually linking the system libraries doesn't seem like a good solution to me, so is there a way to export a list of paths from cmake to a file for my build script to consume?

I can't run the executable of a game made with Bevy

I was following a bevy tutorial and I thought:
What if I run the executable? After all, I will want to share my games in the future.
When i ran the executable, it said that cannot find 2 dlls, bevy_dylib-5d51f44a630848aa.dll and std-1cd530251ef8500f.dll.
It is interesting because has a file called bevy_dylib.dll in the folder and should searching this.
But I found bevy_dylib-5d51f44a630848aa.dll inside the deps, while std-1cd530251ef8500f.dll seems just don't exist.
Same thing happens with this code:
use bevy::prelude::*;
fn main() {
App::new().add_system(hello).run();
}
fn hello() {
println!("Hello");
}
but it doesn't happend when I use cargo run.
Other times I've noticed that apparently has some difference between using cargo run and running the executable.
Anyone know how compile correctly a game made with bevy? What I am doing wrong?
First, there is a big difference between development and release. Your development configuration should be focused on low build times, and your release configuration should be focused on portability.
Bevy recommends using the "dynamic" feature for low build times. If you want to compile for release, though, you should remove that flag. This is most likely where most of your problems come from.
Another problem that arises is that by default, Rust on Windows depends on the MSVC Runtime. You might want to enable static runtime linking to compile the runtime into the executable. This increases its size a little, but that should be fine.
The best way to enable static runtime linking on windows is to create the file .cargo/config.toml in your project directory, and then paste into it:
[target.x86_64-pc-windows-msvc]
rustflags = ["-Ctarget-feature=+crt-static"]
So to sum up:
Remove the "dynamic" flag from bevy
Build with the crt-static flag
Then, you should be able to copy-paste your binary to arbitrary windows systems.
Note, however, that further dependencies of your project could introduce new runtime dependencies. You might have to deliver the respective .dll files with your executable, then; this is common practice for games.
Further remarks
You can check for runtime dependencies with the ldd tool in git-bash.
Using it on my executable without +crt-static shows:
> ldd target/release/<my cratename>.exe
ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7ffc8c410000)
KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7ffc8afc0000)
KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7ffc89d80000)
bcrypt.dll => /c/WINDOWS/System32/bcrypt.dll (0x7ffc89d00000)
ucrtbase.dll => /c/WINDOWS/System32/ucrtbase.dll (0x7ffc8a370000)
VCRUNTIME140.dll => /c/WINDOWS/SYSTEM32/VCRUNTIME140.dll (0x7ffc6cba0000)
After linking with +crt-static, it shows:
> ldd target/release/<my cratename>.exe
ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7ffc8c410000)
KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7ffc8afc0000)
KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7ffc89d80000)
bcrypt.dll => /c/WINDOWS/System32/bcrypt.dll (0x7ffc89d00000)

Why Rust build.rs is unable to find C headers?

Basically I am trying to cargo build a crate which has a build.rs file.
This crate is located inside a bigger project and it's supposed to be a lib crate.
And inside this build.rs file, I am trying to compile a .C file which includes a couple of headers.
Fun fact: I got this build.rs file and crate structure from another little demo crate, and in that demo crate I had no problem to compile this exact C file with these headers.
FULL ERROR HERE:
Here is a link to github: https://github.com/mihaidogaru2537/FirecrackerPlayground/tree/dpdk_component/firecracker/src/dpdk_component
There is the crate I am talking about and you can also see the bigger project in which it resides. In the README file you can see the full error.
Either I do cargo build from the root of the big project or from the root of this problematic crate, the error is the same.
"cargo:warning=/usr/include/asm-generic/errno.h:5:10: fatal error: asm-generic/errno-base.h: No such file or directory
cargo:warning= 5 | #include <asm-generic/errno-base.h>"
The missing file might change depending on the .flag("-I/path/..") calls I am doing inside the build.rs
As you can see, right now it's unable to find errno-base.h, but I am including the path to asm-generic.
Here is the code of the build.rs file from the crate where the compilation of this C file works, as you can see, I did not have to add any include flags before calling compile.
fn main() {
// Tell cargo to tell rustc to link the system bzip2
// shared library.
// println!("cargo:rustc-link-lib=rte_ring");
// println!("cargo:rustc-link-lib=rte_mempool");
// Tell cargo to invalidate the built crate whenever the wrapper changes
// println!("cargo:rerun-if-changed=wrapper.h");
let _src = ["src/static-functions.c"];
println!("cargo:rerun-if-changed=build.rs");
let mut builder = cc::Build::new();
let build = builder
.file("src/static-functions.c")
.flag("-Wno-unused-parameter");
build.compile("foo");
}
Additional info:
The problematic crate is pretty small, see the link above. There is the build.rs file, C file and header file is inside the include directory.
One thing that I suspect, is that the target of the bigger project:
TARGET = Some("x86_64-unknown-linux-musl")
might affect the way the C file is compiled.
In the project where the compilation is working, I am not using that linux-musl stuff.
I am a total noob when it comes to Rust, but I do have a decent understanding of how C/C++ works.
I am running the project on Ubuntu 20.04
Those missing headers are a result of importing DPDK headers, I have DPDK libraries installed on the machine in question.
Let me know if you have any questions, sorry for the long read and thank you.
I somehow managed to fix it by adjusting the cargo build command to use x86_64-unknown-linux-gnu as target instead of x86_64-unknown-linux-musl (By default cargo build was doing the musl target somehow)
So if you are trying to build a rust app which is using DPDK libraries and you are getting missing headers, make sure to try the following:
cargo build --target=x86_64-unknown-linux-gnu
Well if you have to use musl and there is no alternative, I don't have an answer. But to me this was enough.
If someone has an explanation why musl is not working in this scenario, please let us know.
Reddit Rust community helped me as well, check this link out if you are interested:
https://www.reddit.com/r/rust/comments/mo3i08/unable_to_compile_c_file_inside_buildrs_headers/
So Why build.rs was unable to find .C headers?
ANSWER
Because I was using x86_64-unknown-linux-musl as target.

fatal error LNK1181: cannot open input file 'gtk-3.lib'

So I set up GTK-rs for rust and I must have done something wrong because when I try to run my code it returns this error and I have no idea how to fix it:
fatal error LNK1181: cannot open input file 'gtk-3.lib'
I use Eclipse IDE if that will help.
Some more data that might help:
My environment variables are:
GTK_LIB_DIR=C:\msys64\mingw64\lib
PATH:
C:\msys64\mingw64\bin
C:\msys64\mingw64\include
My Cargo.toml file:
[package]
name = "myapp"
version = "0.1.0"
authors = ["author"]
edition = "2018"
[dependencies.gtk]
version = "0.9.2"
features = ["v3_16"]
[dependencies]
glib = "0.10.2"
gio = "0.9.1"
I used some modified sample code for testing:
#![allow(non_snake_case)]
extern crate gtk;
extern crate glib;
extern crate gio;
use gio::prelude::*;
use glib::clone;
use gtk::prelude::*;
// When the application is launched…
fn on_activate(application: &gtk::Application) {
// … create a new window …
let window = gtk::ApplicationWindow::new(application);
// … with a button in it …
let button = gtk::Button::with_label("Hello World!");
// … which closes the window when clicked
button.connect_clicked(clone!(#weak window => move |_| window.close()));
window.add(&button);
window.show_all();
}
fn main() {
// Create a new application
let app = gtk::Application::new(Some("com.github.gtk-rs.examples.basic"), Default::default())
.expect("Initialization failed...");
app.connect_activate(|app| on_activate(app));
// Run the application
app.run(&std::env::args().collect::<Vec<_>>());
}
I was having the same problem with "link.exe" when using gstreamer and gtk Rust bindings. Here's what I did to make my program compile.
Download Microsoft Build Tools and install the following in addition to the 'default' tools that are installed when checking the "C++ build tools".
MS Build Tools Options - credit https://github.com/rust-lang/rust/issues/44787
After the installation, make sure that "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC<VC Version>\bin\Hostx64\x64" is in your environment path.
Restart your pc and try compiling again. Hopefully it is going to work. However, if it still doesn't, I recommend uninstalling Rust from powershell
rustup self uninstall
And then reinstalling Rust from the rustup.exe in this way.
Run rustup.exe
When prompted with the install option, select "customize installation"
For default host triples option type
stable-x86_64-pc-windows-gnu
Press enter for 'default' options for the rest and continue installation.
Restart your pc and the program is definitely going to compile.

How do I build a project that uses the device_query crate on WSL?

I’m trying to track which keys are typed in WSL using the device_query crate. I’ve read the crate’s documentation, added device_query = "0.2.4" to my Cargo.toml file and installed the X11 dependency (sudo apt install libx11-dev).
In my src/main.rs file, I use the crate as intended:
use device_query::{DeviceQuery, DeviceState, MouseState, Keycode};
fn main() {
let device_state = DeviceState::new();
let mouse: MouseState = device_state.get_mouse();
println!("Current Mouse Coordinates: {:?}", mouse.coords);
let keys: Vec<Keycode> = device_state.get_keys();
println!("Is A pressed? {}", keys.contains(&Keycode::A));
}
However, when I run cargo build, I get a 101 exit error:
Updating crates.io index
Compiling x11 v2.18.2
error: failed to run custom build command for `x11 v2.18.2`
Caused by:
process didn't exit successfully: `/home/egerou/Coding/Rust/Wow/target/debug/build/x11-5b031a8b4760d83b/build-script-build` (exit code: 101)
--- stderr
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Command { command: "\"pkg-config\" \"--libs\" \"--cflags\" \"x11\" \"x11 >= 1.4.99.1\"", cause: Os { code: 2, kind: NotFound, message: "No such file or directory" } }', /home/egerou/.cargo/registry/src/github.com-1ecc6299db9ec823/x11-2.18.2/build.rs:36:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
When I read the error, I feel like X11 might not be installed correctly, but if I compile without the device_query = "0.2.4" crate but still the X11 crate (x11 = "2.18.2"), cargo build works.
The error also says a file is missing. Maybe since I’m on WSL, the file isn’t at the correct/expected location.
I’m also using the indexmap = "1.3.2" and rand = "0.5.5" crates. I don’t think they would interfere with the device_query = "0.2.4" crate.
How to build a project that uses the device_query = "0.2.4" crate?
Your error message states (reformatted to make it more readable):
Command {
command: "pkg-config" "--libs" "--cflags" "x11" "x11 >= 1.4.99.1",
cause: Os {
code: 2,
kind: NotFound,
message: "No such file or directory",
}
}
This means that it tried to run the command "pkg-config" "--libs" "--cflags" "x11" "x11 >= 1.4.99.1" but it failed. It failed because the binary pkg-config could not be found.
If you were to run this command in the terminal, presumably you'd see the same error. If so, you need to install pkg-config. If not, then pkg-config might not be in your PATH or is otherwise unavailable to your Rust program. You'll need to investigate where it's installed and why that installation path isn't available to the program.

Resources