Open thomasantony opened 4 years ago
I've started looking into this. There are some surprises for me (having never programmed for Windows)!
It looks like in my Linux environment both gcc and clang consider unsigned long
to be the exact same type as the equivalently sized type from cstdint:
#include <cstdint>
static_assert(sizeof(unsigned long) == sizeof(uint64_t), ""); // passes
void f(unsigned long) {}
//void f(uint64_t) {} // not allowed
int main() {
void (*f$)(uint64_t) = f; // okay
(void)f$;
}
Whereas from toying around in https://msvc.godbolt.org it looks like MSVC considers them distinct types. static_assert(sizeof(unsigned long) == sizeof(uint32_t))
passes but we still get:
error C2440: 'initializing': cannot convert from 'void (__cdecl *)(unsigned long)' to 'void (__cdecl *)(uint32_t)'
Confusingly, it does appear to consider unsigned int
to be the same type as uint32_t
.
That means we're likely to have to introduce some Rust type that the code generator recognizes as equivalent to unsigned long (c_ulong in Rust, going by the terminology in std::os::raw and the libc crate).
For the minwindef.h types in particular, ideally cxx::ExternType
would support this use case.
unsafe impl ExternType for DWORD {
type Id = type_id!("DWORD");
}
but it doesn't quite yet, because we don't support passing them by value yet even with an ExternType impl present. I expect that restriction will be lifted with some more design work.
error[cxxbridge]: passing C++ type by value is not supported
┌─ src/main.rs:19:17
│
19 │ fn doit(d: DWORD);
│ ^^^^^^^^ passing C++ type by value is not supported
For now I guess the only immediate way to unblock signatures that involve DWORD would be for you to write some wrappers expressed in terms of uint32_t which perform (probably implicit) casts to call the original function, and only bind the wrappers from cxx, but this is obviously not great.
Since the comment above, cxx has gained the ability to do this:
unsafe impl ExternType for DWORD {
type Id = cxx::type_id!("DWORD");
type Kind = cxx::kind::Trivial; // the new bit!
}
This isn't quite perfect though.
type DWORD = std::os::raw::c_ulong;
, in which case the orphan rule prevents us from implementing ExternType
. Or,#[repr(transparent)] struct DWORD(std::os::raw::c_ulong);
which should work, but will be a bit awkward to use(I didn't know about #[repr(transparent)]
until I started writing this comment... it sounds like what we need... but to keep cxx
happy we might need to do some different repr
things).
I'm going to try the latter approach until a better plan occurs to me!
The newtype wrapper approach works (i.e. the second bullet above plus an implementation of ExternType
for that type, with cxx::kind::Trivial
). It's not especially nice due to the need to convert types to and from that newtype wrapper, but as a workaround until/unless these types come to b natively supported in cxx.
Specifically here's what I did, for others who end up needing to do this before/unless support lands natively in cxx.
typedef unsigned long c_ulong; // etc.
because cxx
can't cope with C++ type names containing a space. (And who can blame it).
macro_rules! ctype_wrapper {
($r:ident, $c:expr) => {
/// Newtype wrapper for a `$c`
#[derive(Debug,Eq,Clone,PartialEq,Hash)]
#[allow(non_camel_case_types)]
#[repr(transparent)]
pub struct $r(pub ::std::os::raw::$r);
unsafe impl autocxx_engine::cxx::ExternType for $r {
type Id = autocxx_engine::cxx::type_id!($c);
type Kind = autocxx_engine::cxx::kind::Trivial;
}
};
}
ctype_wrapper!(c_ulong, "c_ulong");
ctype_wrapper!(c_long, "c_long");
ctype_wrapper!(c_ushort, "c_ushort");
ctype_wrapper!(c_short, "c_short");
ctype_wrapper!(c_uint, "c_uint");
ctype_wrapper!(c_int, "c_int");
ctype_wrapper!(c_uchar, "c_uchar");
ctype_wrapper!(c_char, "c_char");
There may be a way to get rid of the second parameter using stringify!
but the deep magic done by cxx::type_id
seems to conflict. I think. This is my first ever macro_rules!
use...
Then in the cxx::bridge
mod simply type c_ulong = my_crate::c_ulong;
. And then use my_crate::c_ulong(3)
instead of 3
when calling C++ APIs that take an unsigned long
. Not quite ideal but it works for now.
Related to supporting those without a wrapper: https://github.com/dtolnay/cxx/issues/529.
I'm not sure that #529 really helps here: it would allow the cxx
crate itself to define these aliases but wouldn't allow dependents to define them due to the orphan rule.
I'm assuming that checking the Id
s in verify_extern_type
is designed to ensure that all uses of the same Rust type generate the same C++ name to avoid authoring mistakes?
If so, then perhaps we could add an attribute to indicate to the cxx
crate that the renaming was intentional:
#[cxx::bridge]
mod ffi {
extern "C++" {
#[renamed]
type DWORD = u32;
fn UseDWord(value: DWORD);
}
}
Applying that attribute would cause cxxbridge-macro
to use a different function that didn't check the Id
:
pub fn verify_extern_type_renamed<T: ExternType>() {}
I have been trying to access a 3rd-party win32 API using
cxx
. One of the first things I am coming across is that the library contains extensive use of windows-specific type aliases likeDWORD
,WPARAM
,LPARAM
,HINSTANCE
etc. most of which are equivalent to an unsigned integer of some size (u32 in case of 32-bit windows). Now obviously, sincecxx
is unaware of this, I am unable to pass or return these by value.If I try to use
u32
inside of the extern "C" block, I get errors like:Is there any workaround to this to make
cxx
"aware" of that these types are interchangeable with unsigned integers? I believe most of these are defined inminwindef.h
in the windows SDK.