Description
The "problem"
Currently the windows
crate is allowed to compile on non-windows
systems thanks to a rather large block of code, replicated across every function:
pub unsafe fn DxcCreateInstance2(...) -> Result {
#[cfg(windows)]
{
#[link(name = "windows")]
extern "system" {
fn DxcCreateInstance2(...) -> ::windows::core::HRESULT;
}
let mut result__ = ::core::option::Option::None;
DxcCreateInstance2(...).and_some(result__)
}
#[cfg(not(windows))]
unimplemented!("Unsupported target OS");
}
I remember discussing this long ago: it was needed for CI but seems to have never been removed, as there wasn't much reason to run on non-windows
anyway.
Rust is supposed to omit linkage when functions aren't used: any extern "system" { pub fn blah(); }
doesn't show up in the symbol table if it isn't called into. However, this isn't the case for #[link]
: -lwindows
is added to the linker invocation even if no functions from the extern "system"
block it is annotating are called.
That is evident in the windows-sys
crate which doesn't carry any cfg(windows)
around #[link]
attributes: it is inconveniently used in the metadata
crate that's subsequently used by all the tools, not allowing any of them to be ran on non-Windows (= note: mold: fatal: library not found: windows
) even though they don't reference a single function, just some typedefs.
Proposed solution
Now, combine those two points and we can turn the above into:
pub unsafe fn DxcCreateInstance2(...) -> Result {
#[cfg_attr(windows, link(name = "windows"))]
extern "system" {
fn DxcCreateInstance2(...) -> ::windows::core::HRESULT;
}
let mut result__ = ::core::option::Option::None;
DxcCreateInstance2(...).and_some(result__)
}
(Dis)advantages
Disclaimer: most of the below applies to non-windows
only, which perhaps still isn't "in scope" of the project - but I do hope we can discuss and consider this, especially in light of com-rs
being officially deprecated! 1
This is much shorter and simpler (multiplied by thousands of functions), but there's an obvious catch: whenever this function is used, even in conditional code (consider for example error.rs
using various functions for retrieving and stringifying/prettifying Error
), linking fails.
Perhaps that's (much) better than an unsuspecting unimplemented!()
being reached in an optional codepath (e.g. Error
handling).
However, there's an immediate solution too. And I've alluded to / requested this in earlier issues: define your own fallback.
(This goes anywhere in a user's codebase, could even be a dependent crate)
#[no_mangle]
pub extern "system" DxcCreateInstance2(...) -> ::windows::core::HRESULT {
// My custom fallback implementation
}
(Badly chosen example, this'd be better on e.g. SysStringLen
.)
Then the linker error is gone again, and the user has the ability to provide the necessary functions themselves at no additional design cost to windows-rs
🎉.
Proof of concept
You can find the end result here: https://github.com/MarijnS95/windows-rs/compare/simplify-cfg-windows (Showing 429 changed files with 71,379 additions and 157,268 deletions
). As bonus this includes the same cfg_attr
in windows-sys
to allow running the tools on non-Windows.
Alternative (even smaller) approach
Perhaps this isn't necessary (read: remove the #[cfg(windows)]
entirely) at all if a blanket libwindows.so
is provided by the user, possibly built straight from a Rust crate with aforementioned fallback implementations. This can be done directly through cargo
by providing a:
[package]
name = "windows-fallback"
[lib]
name = "windows"
crate-type = ["cdylib"]
Such a crate can be specified directly as a dependency in another crate even if no Rust symbols are referenced and it's not explicitly linked against: "windows-fallback"
here still builds and its libwindows.so
result ends up in a path that's already part of the search path to cover the automatically-inserted -lwindows
🎉 (may be a bit fragile to depend on though?).
That however doesn't provide a solution to the metadata/tools currently being only compilable on Windows, and I'm not suggesting to provide this crate within windows-rs
!
Footnotes
-
I am of course again trying to find a proper, maintained, clean Rust alternative for
DirectXShaderCompiler
COM bindings. This is perhaps one of the few - or only? - binaries that has a functioning COM API outside of Windows. The proposed Quality-of-Life improvements will shorten generated code and allow me to usewindows-rs
outside of Windows at no additional maintenance cost here. ↩