microsoft / windows-rs

Rust for Windows
https://kennykerr.ca/rust-getting-started/
Apache License 2.0
10.45k stars 493 forks source link

need improve document or clarification on type interops between `windows-sys` and `windows` crates for mixed use cases #2629

Closed nerditation closed 1 year ago

nerditation commented 1 year ago

Suggestion

when writing libraries, I use types from windows-sys for public APIs, since I usually just make simple wrappers around the native API so no need for the convenient features of the windows crate, and as bonus it compiles faster, and is a lot easier to migrate from the winapi crate

however, when writing a complicated application using native Windows API (or wrapper crates), I would prefer the windows crate for the added features. so I often ended up have both windows-sys and windows at the same time, this raises the situation with many duplication of data types and I need to do a lot of conversions.

here's some examples:

this is both tedious and very error prone, and sometimes frustrating. I can't seem to find any documentation about what is the "proper" (or recommended) way to mix windows-sys and windows? I found #1330, #1393, #1643, etc, but they are out of concern of the interoperability between windows-sys and windows.

I see there's :core::Type::abi() and core::Type::from_abi(), can we offer something similar, like Interop::into_sys() and Interop::from_sys()? it can be provided in a separate crate (e.g. named windows_helper, windows_util, windows_interop etc) on top of both windows-sys and windows, or maybe (for better or worse) we can add such functionality directly to windows, which means windows now depends on windows-sys. this approach would make the pointer type and structs conversion safer, although the conversion of handle types are still unchecked because they use type aliases , at least code feels more documented with the intentions.

currently, both windows-sys and windows are used by hundreds of crates on crates.io, I'm afraid it would eventually hinder the adoption of rust native windows programming if the ecosystem is segregated without good support for interoperability in the long run.

opnions?

kennykerr commented 1 year ago

Just getting over a nasty cold. I'll read this more carefully but briefly, all of the types in windows and windows-sys are size and layout compatible with each other as well as their C counterparts. That means you can always transmute and end up with the same memory layout.

riverar commented 1 year ago

Hi @nerditation, is this an issue that's popping up because you're re-exporting windows/windows-sys types? No judgement, just trying to build up context.

nerditation commented 1 year ago

Just getting over a nasty cold. I'll read this more carefully but briefly, all of the types in windows and windows-sys are size and layout compatible with each other as well as their C counterparts

sorry to hear that, hope you're well now.

yes, I understand that the types are all binary compatible to the C counterparts, but they are different as for the rust type checker. in other words we have two different set of rust types with parallel name spaces for the same undelrying ffi types.

for example, this is what the dependency graph could be like for the ffi library openssl:

+-----------+     +---------+          rust  --->  ffi boundary  --->  C
|           | --> | openssl |                           |
|           |     +---------+\
|           |          ^      \                         |
|           |          |       \
|           |          |        \                       |
|           |     +---------+    \     +-------------+     +-------------+
|application| --> |   foo   |     +--> | openssl-sys | --> | openssl-dev |
|           |     +---------+    /     +-------------+     +-------------+
|           |          |        /                       |
|           |          |       /
|           |          V      /                         |
|           |     +---------+/
|           | --> |   bar   |                           |
+-----------+     +---------+

and here's ffi for the Windows SDK:

+-----------+                            rust  --->  ffi boundary  --->  C
|           | -----------------+                         |
|           |                   \
|           |                    \     +-------------+   |
|           |                     +--> |   windows   | ----+
|           |     +---------+    /     +-------------+   |  \
|application| --> |   foo   | --+             |              \
|           |     +---------+                 |          |    \    +-------------+
|           |          |                     ???               +-> | Windows SDK |
|           |          |                      |          |    /    +-------------+
|           |          V                      |              /
|           |     +---------+          +-------------+   |  /
|           | --> |   bar   | -------> | windows-sys | ----+
+-----------+     +---------+          +-------------+   |

my point is, currently, when interfacing the Windows API, you have to make a choice between windows and windows-sys (and don't forget winapi too), and your choice impacts the users of your crate. to my understanding, windows-sys exists because the compile time of windows is way too long to be accepted as THE crate for the Windows API, so a "slim" version with reduced feature set is provided as a middle ground. that's all well and good, if it's structured like a "full-featured" addon crate layered atop the "slim" core crate, and users have a smooth transition path between the two. but the reality is, the two crates are more like alternatives (as oppose to supplementaries) to each other.

of course you can always cast (transmute) between the two, they are just transparent ffi wrappers, but it just doesn't feel right to me.

anyway, maybe it's just me being extra frustrated by the Windows API and somehow imagining rust would magically fix all the mess. sorry about the rant.

nerditation commented 1 year ago

is this an issue that's popping up because you're re-exporting windows/windows-sys types?

yes, kind of. I'm not re-exporting types from windows-sys directly, but some type "leaked" into function signatures.

here's an example, I have a library (implemented using windows-sys) to manage embedded resources in the EXE file, and the API requires an argument of type HMODULE, I didn't call internally GetModuleHandleA(NULL) (which return a HMODULE to the running EXE) because occasionally I want to load resources from DLLs too. another example is an API that returns a HWND to a managed window.

since these are all opaque handles, I can use any opaque types with same C representation to "hide" the dependency on windows-sys, but what's that good for? if the caller is getting the handles from different crates (say windows or winapi), you still need a cast anyway. I know it's an API design issue and I'm going to change it anyway. for now, I'm probably gonna use the raw-window-handle crate for the public API.

but still, I just want to make a point, because it doesn't feel quite right to me.

btw, I also checked RawHandle (and HandleOrNull etc) from std, then I found out raw-window-handle just uses opaque pointer *mut c_void for both HWND and HINSTANCE:

https://github.com/rust-windowing/raw-window-handle/blob/4611ee8644d72c7b36fa47960d1b18c0c9648e08/src/windows.rs#L32-L39

kennykerr commented 1 year ago

Thanks for the feedback!

I don't recommend exporting types from windows or windows-sys mainly because those crates are not yet 1.0 as I work on stabilizing the underlying metadata for the rather vast Windows API surface. So, prefer to export something like *mut c_void or isize or some wrapper of your own if you must export something.

Regarding windows depending on windows-sys, the trouble there is that this would make use of the windows crate even more "expensive". I experimented with this, but found that it ended up causing more code to be effectively compiled. The windows crate can define a very streamlined ABI for its internal use that is even more concise than that of the windows-sys crate. This is something that can be revisited if an effective solution is discovered in future.