windows-rs: Remove `unimplemented!("Unsupported target OS");` to allow _running_ on Linux and WSL
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]
[^1]: 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 use windows-rs
outside of Windows at no additional maintenance cost here.
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
!
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 43 (21 by maintainers)
Hm we don’t want any new features at this time, those are hard to change/use. Let’s stick to what’s in the linked comment and we can re-evaluate afterwards.
I was confused as well, so I whipped up a dxcore sample to test the end-to-end dev experience here and identified several blockers, most of which Marijn mentioned:
Unconditional OS string usage in core.
Blocker guards in generated APIs.
#[cfg(not(windows))]
Forced umbrella lib linkage via
#[link(name = "windows")]
.#[cfg(all(windows, link(name = "windows")))]
), developer then emitsrustc-link-*
flags as needed.Did I miss anything @MarijnS95? Do you agree with these proposed fixes?
@kennykerr DirectX has a few cross-platform APIs and very minimal changes are being proposed to make
windows-rs
just work for those targets (https://github.com/microsoft/windows-rs/issues/1842#issuecomment-1164681869). The win for us is that we enable a solid DirectX Rust development experience, get to delete thousands of lines of code from the crate, and best of all keep @MarijnS95 quiet/happy. 😂 (I kid, I kid.)@MarijnS95 Can you submit a PR? Let’s move forward with the changes listed in https://github.com/microsoft/windows-rs/issues/1842#issuecomment-1164681869. (Whether you want to use
cfg_attr
orcfg(all(...)))
is up to you.)So to sum it up succinctly, the end goal is to provide Rust bindings to DirectXShaderCompiler on Linux systems.
Windows-rs can provide the needed COM auto-generation (vtables, AddRef/Release) and associated types (e.g. BSTR).
Though there are some differences between platforms (e.g. Linux BSTR uses 32-bit code units whereas Windows uses 16-bit).
The problem this specific issue aims to address is that the current
cfg
’s outright prevents using any generated code on non-Windows systems, even if you otherwise provide the necessary functions that Windows-rs imports.@MarijnS95 Got it, thanks. I learned something new today.
@ChrisDenton Good point re: the less efficient indirection. I would say that disqualifies the dropping-cfg option until we crunch some numbers and see if there’s meaningful impact there. (LTO may narrow any gap?)
So I think we can move forward for now with the changes listed above. If you agree we’ve settled on something usable, I can pester Kenny for sign-off. 🙃
Just here to confirm that the proposed simplification would work for my (cross-compilation) use-case.
The cross-compilation MR added this
#[cfg]
so we could keep callingunreachable!()
on non-windows targets, like it did before the MR. Cross-compilation use-cases don’t need thecfg
at all - we could just keep the windows block and it would work.It was to allow the Rust compiler devs (who didn’t use Windows at the time) to build and profile the generated code. Specifically, profile the compiler while building and generating the code.
@MarijnS95 Oh those tools, got it. Thanks!