libvlc-sys: import libvlc via raw-dylib on Windows

Windows has no import library to link against: the VLC installer and
.zip ship libvlc.dll but no .lib. Previous workaround was to use vswhom
+ dumpbin + lib.exe to synthesise one at build time from the DLL's
exports.

rustc's `raw-dylib` linkage removes the need entirely, it generates the
imports straight from the DLL name.
master
Alaric Senat 2026-07-21 22:19:19 +02:00
parent 832a715d79
commit f508cf7daf
5 changed files with 19 additions and 160 deletions

43
Cargo.lock generated
View File

@ -2,22 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "cc"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.189" version = "0.2.189"
@ -30,7 +14,6 @@ version = "0.2.0"
dependencies = [ dependencies = [
"libc", "libc",
"pkg-config", "pkg-config",
"vswhom",
] ]
[[package]] [[package]]
@ -39,12 +22,6 @@ version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]] [[package]]
name = "vlc-rs" name = "vlc-rs"
version = "0.3.0" version = "0.3.0"
@ -52,23 +29,3 @@ dependencies = [
"libc", "libc",
"libvlc-sys", "libvlc-sys",
] ]
[[package]]
name = "vswhom"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b"
dependencies = [
"libc",
"vswhom-sys",
]
[[package]]
name = "vswhom-sys"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150"
dependencies = [
"cc",
"libc",
]

View File

@ -23,6 +23,3 @@ libc = "0.2"
[build-dependencies] [build-dependencies]
pkg-config = "0.3" pkg-config = "0.3"
[target.'cfg(target_os = "windows")'.build-dependencies]
vswhom = "0.1.0"

View File

@ -1610,6 +1610,7 @@ const _: () = {
["Offset of field: libvlc_log_message_t::psz_message"] ["Offset of field: libvlc_log_message_t::psz_message"]
[::core::mem::offset_of!(libvlc_log_message_t, psz_message) - 32usize]; [::core::mem::offset_of!(libvlc_log_message_t, psz_message) - 32usize];
}; };
#[cfg_attr(windows, link(name = "libvlc", kind = "raw-dylib"))]
unsafe extern "C" { unsafe extern "C" {
pub fn libvlc_errmsg() -> *const libc::c_char; pub fn libvlc_errmsg() -> *const libc::c_char;
pub fn libvlc_clearerr(); pub fn libvlc_clearerr();

View File

@ -1,5 +1,7 @@
//! Build script for `libvlc-sys`, locates and links libvlc. //! Build script for `libvlc-sys`, locates and links libvlc.
use std::env;
/// Minimum supported libvlc version. /// Minimum supported libvlc version.
const MIN_LIBVLC_VERSION: &str = "3.0.0"; const MIN_LIBVLC_VERSION: &str = "3.0.0";
@ -10,126 +12,21 @@ fn probe_libvlc() -> Result<pkg_config::Library, pkg_config::Error> {
.probe("libvlc") .probe("libvlc")
} }
#[cfg(target_os = "windows")] fn main() {
mod windows { // vsnprintf is inlined by the UCRT headers, so MSVC needs this to resolve
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] // the symbol src/lib.rs declares. https://stackoverflow.com/a/34230122
compile_error!("Only x86 and x86_64 are supported at the moment. Adding support for other architectures should be trivial."); if env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default() == "msvc" {
use std::env;
use std::ffi::OsString;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use vswhom::VsFindResult;
pub fn link_vlc() {
let vlc_path = vlc_path();
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let vs = VsFindResult::search().expect("Could not locate Visual Studio");
let vs_exe_path = PathBuf::from(
vs.vs_exe_path
.expect("Could not retrieve executable path for Visual Studio"),
);
generate_lib_from_dll(&out_dir, &vs_exe_path, &vlc_path);
println!("cargo:rustc-link-search=native={}", out_dir.display());
// NOTE: Without this directive, linking fails with:
// ```
// error LNK2019: unresolved external symbol vsnprintf referenced in function _{MangledSymbolName}
// msvcrt.lib(vsnprintf.obj) : error LNK2001: unresolved external symbol vsnprintf
// msvcrt.lib(vsnprintf.obj) : error LNK2001: unresolved external symbol _vsnprintf
// ```
// https://stackoverflow.com/a/34230122
println!("cargo:rustc-link-lib=dylib=legacy_stdio_definitions"); println!("cargo:rustc-link-lib=dylib=legacy_stdio_definitions");
} }
fn generate_lib_from_dll(out_dir: &Path, vs_exe_path: &Path, vlc_path: &Path) { // The bindings import from libvlc.dll directly via `raw-dylib`, so
// https://wiki.videolan.org/GenerateLibFromDll/ // Windows needs neither an import library nor a search path,
// only the DLL at runtime.
let vs_dumpbin = vs_exe_path.join("dumpbin.exe"); if env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
let vs_lib = vs_exe_path.join("lib.exe"); return;
let vlc_def_path = out_dir.join("libvlc.def");
let vlc_import_lib = out_dir.join("vlc.lib");
let libvlc = vlc_path.join("libvlc.dll");
let exports = Command::new(vs_dumpbin)
.current_dir(out_dir)
.arg("/EXPORTS")
.arg(libvlc.display().to_string().trim_end_matches(r"\"))
.output()
.unwrap();
let exports = String::from_utf8(exports.stdout).unwrap();
let mut vlc_def = String::from("EXPORTS\n");
for line in exports.lines() {
if let Some(line) = line.get(26..) {
if line.starts_with("libvlc_") {
vlc_def.push_str(line);
vlc_def.push_str("\r\n");
}
}
}
fs::write(&vlc_def_path, vlc_def.into_bytes()).unwrap();
// FIXME: Handle paths with spaces in them.
Command::new(vs_lib)
.current_dir(out_dir)
.arg("/NOLOGO")
.args(&[
format!(
r#"/DEF:{}"#,
vlc_def_path.display().to_string().trim_end_matches(r"\")
),
format!(
r#"/OUT:{}"#,
vlc_import_lib.display().to_string().trim_end_matches(r"\")
),
format!(
"/MACHINE:{}",
match target_arch().as_str() {
"x86" => "x86",
"x86_64" => "x64",
_ => unreachable!(),
}
),
])
.status()
.expect("Failed to run lib.exe")
.success()
.then_some(())
.expect("lib.exe failed to generate the vlc import library");
} }
fn vlc_path() -> PathBuf {
#[allow(unused_assignments)]
let arch_path: Option<OsString> = match target_arch().as_str() {
"x86" => env::var_os("VLC_LIB_DIR_X86"),
"x86_64" => env::var_os("VLC_LIB_DIR_X86_64"),
_ => unreachable!(),
};
arch_path
.or_else(|| env::var_os("VLC_LIB_DIR"))
.map(PathBuf::from)
.expect("VLC_LIB_DIR not set")
}
fn target_arch() -> String {
env::var("CARGO_CFG_TARGET_ARCH").unwrap()
}
}
fn main() {
// On success pkg-config has already emitted the link directives; only the
// failure path needs handling.
if let Err(err) = probe_libvlc() { if let Err(err) = probe_libvlc() {
#[cfg(target_os = "windows")]
windows::link_vlc();
#[cfg(not(target_os = "windows"))]
panic!("libvlc (>= {}) not found: {:?}", MIN_LIBVLC_VERSION, err); panic!("libvlc (>= {}) not found: {:?}", MIN_LIBVLC_VERSION, err);
} }
} }

View File

@ -73,6 +73,13 @@ fn generate_bindings() {
"a va_list spelling we do not know about survived" "a va_list spelling we do not know about survived"
); );
// On Windows rustc can synthesise the imports straight from the DLL, so no
// import library is needed. bindgen cannot emit the attribute itself.
let generated = generated.replace(
"unsafe extern \"C\" {",
"#[cfg_attr(windows, link(name = \"libvlc\", kind = \"raw-dylib\"))]\nunsafe extern \"C\" {",
);
std::fs::write(&output, generated).expect("couldn't write bindings"); std::fs::write(&output, generated).expect("couldn't write bindings");
println!("wrote {}", output.display()); println!("wrote {}", output.display());
} }