windows-rs icon indicating copy to clipboard operation
windows-rs copied to clipboard

How to cast C++ IUnknown pointer to rust IUnknown struct?

Open seungha-yang opened this issue 3 years ago • 3 comments

I'm currently trying to cast C++ IUnknown pointer (*mut c_void) to rust IUnknown via std::mem::transmute_copy but not successful so far.

Is it supposed to be working or is there any guide for the scenario?

seungha-yang avatar Aug 22 '22 16:08 seungha-yang

It's not particularly easy because it's not particularly safe. There's also the question of whether the C++ pointer is owned or dangling. Do you want the Rust IUnknown to assume ownership - and thus call Release when it is dropped - or only reference the C++ pointer that is potentially owned by someone else?

If we assume you're only borrowing someone else's pointer, then it would look something like this:

let raw: *mut std::ffi::c_void = ptr; // someone else will call `ptr->Release()`
let unknown: &windows::core::IUnknown = std::mem::transmute(&raw);

You can now use unknown for as long as raw is valid...

If we assume you're taking ownership of the pointer, then it would look something like this:

let raw: *mut std::ffi::c_void = ptr; // nobody else will touch `ptr` again
let unknown: windows::core::IUnknown = std::mem::transmute(raw);

You can now use unknown for as long as it is valid as it owns a reference and will call Release when it is dropped.

kennykerr avatar Aug 22 '22 18:08 kennykerr

Thanks for the feedback. My case is borrowing C++ pointer and it was indeed ownership issue. I worked around it via core::mem::forget or something else so that rust IUnknown can be stay in the scope without IUnknown::Release.

I understood that this kind of stuff is hard (or almost impossible) to be safe but I'm wondering if there can be a higher level and a bit safer helper method. Particularly I'm working on rust binding for GStreamer D3D11 library and plugin

seungha-yang avatar Aug 22 '22 19:08 seungha-yang

Such helper API would certainly be useful, even if it has to be unsafe. It's not obvious which memory representations are equivalent and having to call transmute() with all it's pre-conditions is a lot less safe than having e.g.

unsafe fn from_raw_owned(ptr: *mut c_void) -> IUnknown;
unsafe fn from_raw_borrowed<'a>(ptr: &'a *mut c_void) -> &'a IUnknown;

Both could document the pre-conditions for calling these safely much clearer than transmute() could, i.e. the first only needs a valid pointer to an IUnknown that is owned by the code in question, while the second requires a valid pointer and that the pointer is valid at least for the lifetime 'a.

sdroege avatar Aug 23 '22 06:08 sdroege

Thanks for the suggestion! I'm just not convinced this is any safer or even any clearer.

These would just wrap transmute calls. Yes, there's value in having the functions be somewhat self-documenting but even then the names can be misleading. Does from_raw_owned mean the raw pointer is owned or the resulting IUnknown is owned. There's the difference between ownership and transferring ownership. Either way you need a good understanding of how COM interface pointers are represented in both C++ and Rust to use it safely.

I'm interested in what @rylev has to say.

kennykerr avatar Aug 25 '22 15:08 kennykerr

Either way you need a good understanding of how COM interface pointers are represented in both C++ and Rust to use it safely.

You need that anyway if you want to deal with raw pointers :) The main advantage of those functions over just transmuting is that they make it clearer what is allowed to do and what not, and how memory layouts are matching. And more importantly, they're much more discoverable than knowing that you can transmute things under certain conditions.

For example you could create an &IUnknown from a *mut c_void via transmute if you're not careful, but such functions would avoid such mistakes.

Similarly, the documentation would make it clear that in the *mut c_void -> IUnknown case the object must be owned by the code and it would make the mistake much less likely that people do a transmute on an unowned/borrowed pointer and then accidentally Drop it.

In any case, these functions would still be unsafe so would require an unsafe block.

(Note that the &'a *mut c_void -> &'a IUnknown function doesn't even need a transmute but just a raw pointer cast and dereference)


FWIW, in the GObject (GTK/GStreamer/etc) Rust bindings we provide such functions and people generally seem to use them correctly while that's much less often the case with transmute.

sdroege avatar Aug 25 '22 15:08 sdroege

I am absolutely in favor of these functions existing for many of the same reasons as @sdroege listed. I think any time we're requiring a transmute, we have a failure in the API design.

rylev avatar Aug 26 '22 13:08 rylev

Sounds good - I'll try to get to it as I have time.

kennykerr avatar Aug 29 '22 19:08 kennykerr

Sorry for the delay - here you go: #2010

kennykerr avatar Sep 07 '22 14:09 kennykerr