Closed nerditation closed 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.
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.
Just getting over a nasty cold. I'll read this more carefully but briefly, all of the types in
windows
andwindows-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.
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
:
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.
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 thewindows
crate, and as bonus it compiles faster, and is a lot easier to migrate from thewinapi
cratehowever, 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 bothwindows-sys
andwindows
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:
ptr::cast()
oras _
, but it's unchecked whether the pointee is actually the same type, for example, some structs have bothA
andW
variants, it's prone to accidental typos when casting a pointer.mem::transmute()
, but for very simple structs likePOINT
andRECT
, I usually just destructure the fields of the struct (bind to individual variables such as x, y, width, height), but some API requireLPRECT
, in which case I have to reconstruct a new struct.windows
uses newtype wrapper,windows-sys
uses type aliases, the conversion is done by manually wrap/unwrap-ing the primitive 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
andwindows
? I found #1330, #1393, #1643, etc, but they are out of concern of the interoperability betweenwindows-sys
andwindows
.I see there's
:core::Type::abi()
andcore::Type::from_abi()
, can we offer something similar, likeInterop::into_sys()
andInterop::from_sys()
? it can be provided in a separate crate (e.g. named windows_helper, windows_util, windows_interop etc) on top of bothwindows-sys
andwindows
, or maybe (for better or worse) we can add such functionality directly towindows
, which meanswindows
now depends onwindows-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
andwindows
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?