Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
nerditation opened this issue Aug 25, 2023 · 5 comments
Labels
question Further information is requested

Comments

@nerditation
Copy link

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:

  • for pointers, just cast the pointer using ptr::cast() or as _, but it's unchecked whether the pointee is actually the same type, for example, some structs have both A and W variants, it's prone to accidental typos when casting a pointer.
  • for structs, I can use mem::transmute(), but for very simple structs like POINT and RECT, I usually just destructure the fields of the struct (bind to individual variables such as x, y, width, height), but some API require LPRECT, in which case I have to reconstruct a new struct.
  • for handle types, windows uses newtype wrapper, windows-sys uses type aliases, the conversion is done by manually wrap/unwrap-ing the primitive types.
  • for enums and constants, it's similar to handle types.

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?

@nerditation nerditation added the enhancement New feature or request label Aug 25, 2023
@kennykerr
Copy link
Collaborator

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.

@kennykerr kennykerr added question Further information is requested and removed enhancement New feature or request labels Aug 25, 2023
@riverar
Copy link
Collaborator

riverar commented Aug 25, 2023

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
Copy link
Author

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
Copy link
Author

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
Copy link
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants