Description
Summary
TL;DR, generated bindings that used to compile now fail to compile with "mismatched types". Mismatch is between the type expected by the interface wrapper and what the map
over the vtable invocation result actually returns.
Using the current master branch instead of the latest crates.io release results in the same behavior.
I can manually comment out the offending items from the generated bindings (and make notes of which they are, why it's necessary, etc.), so this is not currently a high-priority blocking item on my end. The priority comes from long-term, forward-looking correctness, as well as potentially revealing a blind-spot in the windows-rs
COM support.
This is kind of a tricky issue, because there are so many unknowns on my end. I'm not certain what the "real problem" is, because I'm not sure what the "correct behavior" actually looks like, even in a non-Rust, non-windows-rs
context. So, I'm just going to start at the beginning.
SolidWorks COM API wrapper
I am working from the type libraries redistributed with a SolidWorks installation. The API is so old that not only do I not have .winmd to work from, I have to "decompile" the .tlb files into .idl files that the WinmdGenerator
SDK even knows what to do with, kind of. This has (seemed to have) worked so far, after a lot of finagling.
I mention that here just to underscore that, because of the multi-step translation process, I actually don't know if this is even a faithful translation of the original SolidWorks interface definition, even without considering whether the original SolidWorks devs wrote that definition correctly.
Problem Bindings
All the problematic methods are
- deprecated (except one)
- only to be used in an in-process context
- ones that I've never actually used
Some of the successor methods are using VARIANT
arrays instead (SolidWorks is a heavy user of IDispatch
"OLE Automation"), but some of them are still pointer-and-length based (just with one less pointer indirection, for some reason, which seems to work correctly).
Return Result<*mut Option<IFoo>>
vs Result<IFoo>
impl IBody {
// ...
pub unsafe fn IGetIntersectionEdges<P0>(
&self,
toolbodyin: P0,
) -> windows_core::Result<*mut Option<IEdge>>
where
P0: windows_core::Param<IBody>,
{
unsafe {
let mut result__ = core::mem::zeroed();
(windows_core::Interface::vtable(self).IGetIntersectionEdges)(
windows_core::Interface::as_raw(self),
toolbodyin.param().abi(),
&mut result__,
)
.map(|| result__)
}
}
// ...
}
#[repr(C)]
pub struct IBody_Vtbl {
pub base__: windows::Win32::System::Com::IDispatch_Vtbl,
// ...
pub IGetIntersectionEdges: unsafe extern "system" fn(
*mut core::ffi::c_void,
*mut core::ffi::c_void,
*mut *mut *mut core::ffi::c_void,
) -> windows_core::HRESULT,
// ...
}
error[E0308]: mismatched types
--> crates\solidworks-sys\src\gen\sldworks.rs:32014:13
|
32008 | ) -> windows_core::Result<*mut Option<IEdge>>
| ---------------------------------------- expected `Result<*mut Option<IEdge>, windows_core::Error>` because of return type
...
32014 | / (windows_core::Interface::vtable(self).IGetIntersectionEdges)(
32015 | | windows_core::Interface::as_raw(self),
32016 | | toolbodyin.param().abi(),
32017 | | &mut result__,
32018 | | )
32019 | | .map(|| result__)
| |_____________________________^ expected `Result<*mut Option<IEdge>, Error>`, found `Result<*mut *mut c_void, Error>`
|
= note: expected enum `Result<*mut Option<IEdge>, _>`
found enum `Result<*mut *mut c_void, _>`
IBody.IGetIntersectionEdges
is deprecated in favor of IBody2.IGetIntersectionEdges
(which is itself deprecated in favor of IBody2.GetIntersectionEdges
)
impl IBody2 {
// ...
pub unsafe fn GetIntersectionEdges<P0>(
&self,
toolbodyin: P0,
) -> windows_core::Result<windows::Win32::System::Variant::VARIANT>
where
P0: windows_core::Param<windows::Win32::System::Com::IDispatch>,
{
unsafe {
let mut result__ = core::mem::zeroed();
(windows_core::Interface::vtable(self).GetIntersectionEdges)(
windows_core::Interface::as_raw(self),
toolbodyin.param().abi(),
&mut result__,
)
.map(|| core::mem::transmute(result__))
}
}
pub unsafe fn IGetIntersectionEdges<P0>(&self, toolbodyin: P0) -> windows_core::Result<IEdge>
where
P0: windows_core::Param<IBody2>,
{
unsafe {
let mut result__ = core::mem::zeroed();
(windows_core::Interface::vtable(self).IGetIntersectionEdges)(
windows_core::Interface::as_raw(self),
toolbodyin.param().abi(),
&mut result__,
)
.and_then(|| windows_core::Type::from_abi(result__))
}
}
// ...
}
#[repr(C)]
pub struct IBody2_Vtbl {
pub base__: windows::Win32::System::Com::IDispatch_Vtbl,
// ...
pub GetIntersectionEdges: unsafe extern "system" fn(
*mut core::ffi::c_void,
*mut core::ffi::c_void,
*mut windows::Win32::System::Variant::VARIANT,
) -> windows_core::HRESULT,
pub IGetIntersectionEdges: unsafe extern "system" fn(
*mut core::ffi::c_void,
*mut core::ffi::c_void,
*mut *mut core::ffi::c_void,
) -> windows_core::HRESULT,
// ...
}
The IBody2
interface compiles successfully in its entirety.
The only difference in the generated wrappers between v0.58 and v0.59 is the unsafe
block enclosing the method body.
pub unsafe fn IGetIntersectionEdges<P0>(
&self,
toolbodyin: P0,
) -> windows_core::Result<*mut Option<IEdge>>
where
P0: windows_core::Param<IBody>,
{
let mut result__ = core::mem::zeroed();
(windows_core::Interface::vtable(self).IGetIntersectionEdges)(
windows_core::Interface::as_raw(self),
toolbodyin.param().abi(),
&mut result__,
)
.map(|| result__)
}
Parameter of *const *const Option<IFoo>
vs *const Option<IFoo>
Our lovely friend, IBody
, is at it again
impl IBody {
// ...
pub unsafe fn IRemoveFacesFromSheet(
&self,
numoffaces: i32,
facestoremove: *const *const Option<IFace>,
) -> windows_core::Result<()> {
unsafe {
(windows_core::Interface::vtable(self).IRemoveFacesFromSheet)(
windows_core::Interface::as_raw(self),
numoffaces,
facestoremove,
)
.ok()
}
}
// ...
}
#[repr(C)]
pub struct IBody_Vtbl {
pub base__: windows::Win32::System::Com::IDispatch_Vtbl,
// ...
pub IRemoveFacesFromSheet: unsafe extern "system" fn(
*mut core::ffi::c_void,
i32,
*const *const *mut core::ffi::c_void,
) -> windows_core::HRESULT,
// ...
}
error[E0308]: mismatched types
--> crates\solidworks-sys\src\gen\sldworks.rs:32059:17
|
32056 | (windows_core::Interface::vtable(self).IRemoveFacesFromSheet)(
| ------------------------------------------------------------- arguments to this function are incorrect
...
32059 | facestoremove,
| ^^^^^^^^^^^^^ expected `*const *const *mut c_void`, found `*const *const Option<IFace>`
|
= note: expected raw pointer `*const *const *mut c_void`
found raw pointer `*const *const Option<IFace>`
As before, IBody.IRemoveFacesFromSheet
is deprecated in favor of IBody2.IRemoveFacesFromSheet
. However, IBody2.RemoveFacesFromSheet
exists, but is not specified as superseding IBody2.IRemoveFacesFromSheet
.
impl IBody2 {
// ...
pub unsafe fn RemoveFacesFromSheet(
&self,
numoffaces: i32,
facestoremove: &windows::Win32::System::Variant::VARIANT,
) -> windows_core::Result<()> {
unsafe {
(windows_core::Interface::vtable(self).RemoveFacesFromSheet)(
windows_core::Interface::as_raw(self),
numoffaces,
core::mem::transmute_copy(facestoremove),
)
.ok()
}
}
pub unsafe fn IRemoveFacesFromSheet(
&self,
numoffaces: i32,
facestoremove: *const Option<IFace2>,
) -> windows_core::Result<()> {
unsafe {
(windows_core::Interface::vtable(self).IRemoveFacesFromSheet)(
windows_core::Interface::as_raw(self),
numoffaces,
core::mem::transmute(facestoremove),
)
.ok()
}
}
// ...
}
#[repr(C)]
pub struct IBody2_Vtbl {
pub base__: windows::Win32::System::Com::IDispatch_Vtbl,
// ...
pub RemoveFacesFromSheet: unsafe extern "system" fn(
*mut core::ffi::c_void,
i32,
windows::Win32::System::Variant::VARIANT,
) -> windows_core::HRESULT,
pub IRemoveFacesFromSheet: unsafe extern "system" fn(
*mut core::ffi::c_void,
i32,
*const *mut core::ffi::c_void,
// ...
}
Again, the only difference in the generated wrappers between v0.58 and v0.59 is the enclosing unsafe
block.
The other problematic methods are just variations on this common theme.
Questions
Is this a simple matter of "getting confused by double indirection"?
As far as I can tell, windows-bindgen
appears to be going "well, it's a pointer type, and pointer types are copy-able, so do the copy-able thing, which is the identity map
". I think. Maybe. It's very complicated, and I'm very new to that codebase.
How is this "supposed" to work?
If I replace .map(|| result__)
with .map(|| core::mem::transmute(result__))
, it compiles. Given that transmute
is black freakin magic "just_ pretend that the bits mean something different", I personally have no idea if this means it's "correct". It makes sense in my head, where *mut c_void
turns into *mut IFoo
("cast void
to the type you're actually pointing to"), which turns into Option<IFoo>
("nullptr
is the niche for Option
discriminant"), without any binary/layout changes along the way.
And then, if that's valid, shouldn't it be a simple matter of (if Some
) using Interface::as_raw
/Interface::into_raw
, <pointer>::offset
, and Interface::from_raw
to grab the following elements of the array? Those interface pointers have already been "given" to me by returning from the method, and it's my responsibility to release
them (by wrapping them in a type that will do that on drop
), right? Something like,
let len = body.IGetIntersectionEdgeCount(&other)? as isize * 2; // [sic]. Documentation: "The total number of edges in this array is twice the value returned from IBody2::IGetIntersectionEdgeCount)."
let first = body
.IGetIntersectionEdges(&other)
.and_then(|ptr| ptr.read().ok_or_else(windows_core::Error::empty))?;
let ptr = first.into_raw();
let edges: Vec<IEdge> = (0..len)
.map(|offset| IEdge::from_raw(ptr.offset(offset)))
.collect();
Is that right? I've been programming in the lower-level Microsoft realm for just long enough to know that "if you've done it 'the wrong way', it still might 'work', and if you've done it 'the right way', it still might not work."
Should I be seeing some explicit Ref
/OutRef
?
Because even though I haven't fully grok'd how they work, in a post-#3386 world, not seeing them is slightly concerning to me. :)
Disclaimer
I know that "supporting 3rd-party metadata" is not a top priority for this project. The solidworks-sys
crate I'm working on is not public (yet) and quite chonky (~650k lines of bindings for the main sldworks
namespace alone). My apologies if this is out of scope or if providing the relevant code-snippets inline is less helpful than a live repo.
The windows-rs
project is seriously magical. Thank you guys for the hard work you've put into it. I feel like I've learned a ton so far, and I'd love to see it go as far as it can go. Maybe help out if/where I can. Cheers!