-
Notifications
You must be signed in to change notification settings - Fork 512
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
MFTEnumEx memory management confusion #1685
Comments
I need to update to the newest version of the windows crate, but here's how I call it in displayrecorder: |
This is helpful, thank you! I didn't think about using transmute because I've mostly done safe Rust so far (and because of the big scary warnings in the doc); is this the recommended way of dealing with out pointers to COM interfaces? |
No problem! In this case, where you're getting a list back that's allocated by the function, transmute would be what I recommend. There shouldn't be null entries in that list and you know how long it's supposed to be, which makes it great for a slice. Generally, sticking with the Option and unwrapping it is what I go with for COM out pointers (e.g. ID3D11Device::GetImmediateContext). |
So I have copied the code from your repo (without lines 47-48), but something seems off still; when debugging I never end up in I've tried with VSCode and WinDbg, and with the latter I expected to end up in some Windows DLL where the COM object's Code looks like this now: let mut mfactivate_list = std::ptr::null_mut();
let mut num_mfactivate = 0u32;
let res = MFTEnumEx(
MFT_CATEGORY_VIDEO_ENCODER,
MFT_ENUM_FLAG_HARDWARE.0.try_into().unwrap(),
std::ptr::null(),
std::ptr::null(),
&mut mfactivate_list,
&mut num_mfactivate,
);
if res.is_ok() {
println!("MFTEnumEx succeeded, {} MFTs returned!", num_mfactivate);
//call release on every IMFActivate pointer
let mfactivate_list: *mut IMFActivate = std::mem::transmute(mfactivate_list);
let mfactivate_slice =
std::slice::from_raw_parts(mfactivate_list, num_mfactivate as usize);
for mfactivate in mfactivate_slice {
// We need to release each item
std::mem::drop(mfactivate);
}
CoTaskMemFree(mfactivate_list as *const std::ffi::c_void);
println!("Freed activate ptrs");
} Is this a bug or am I misunderstanding how |
No, it looks like you've got it right. Thanks for catching this. This is the only way I could think of to fix it: unsafe {
// If we have more than one IMFActivate in the list,
// we can transmute it out of the Option<_>
let mfactivate_list: *mut IMFActivate = std::mem::transmute(mfactivate_list);
let mfactivate_slice =
std::slice::from_raw_parts(mfactivate_list, num_mfactivate as usize);
for mfactivate in mfactivate_slice {
let transform_source = mfactivate.clone();
// This is a dirty trick we play so that we can
// release the underlying IMFActivate despite having
// a shared reference.
let temp: windows::core::IUnknown = std::mem::transmute_copy(&transform_source.0);
transform_sources.push(transform_source);
// We need to release each item
std::mem::drop(temp)
}
// Free the memory that was allocated for the list
CoTaskMemFree(mfactivate_list as *const _);
} If you create a |
Yeah, this is a difficult API to call properly. It probably needs a little help. use windows::{core::*, Win32::Media::MediaFoundation::*, Win32::System::Com::*};
fn main() -> Result<()> {
unsafe {
let mut data = std::ptr::null_mut();
let mut len = 0;
MFTEnumEx(
MFT_CATEGORY_VIDEO_ENCODER,
MFT_ENUM_FLAG_HARDWARE.0 as _,
std::ptr::null(),
std::ptr::null(),
&mut data,
&mut len,
)?;
let array = ComArray::<IMFActivate>::from_raw(data as _, len);
for i in array.as_slice() {
println!("{}", i.GetCount()?);
}
Ok(())
}
}
struct ComArray<T: Interface> {
data: *mut T,
len: u32,
}
impl<T: Interface> ComArray<T> {
pub unsafe fn from_raw(data: *mut T, len: u32) -> Self {
Self { data, len }
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn as_slice(&self) -> &[T] {
if self.is_empty() {
return &[];
}
unsafe { std::slice::from_raw_parts(self.data, self.len as usize) }
}
}
impl<T: Interface> Drop for ComArray<T> {
fn drop(&mut self) {
if self.is_empty() {
return;
}
unsafe {
std::ptr::drop_in_place(std::slice::from_raw_parts_mut(self.data, self.len as usize));
CoTaskMemFree(self.data as _);
}
}
} A bit more work up front but then you know it's taken care of. |
There's also windows::core::Array which is also backed by the same allocator. We could potentially generalize |
Thanks a lot for the help. :) |
Just a quick update. With #2362 you can use the use windows::{core::*, Win32::Media::MediaFoundation::*};
fn main() -> Result<()> {
unsafe {
let mut data = std::ptr::null_mut();
let mut len = 0;
MFTEnumEx(
MFT_CATEGORY_VIDEO_ENCODER,
MFT_ENUM_FLAG_HARDWARE,
None,
None,
&mut data,
&mut len,
)?;
let array = Array::<IMFActivate>::from_raw_parts(data as _, len);
for i in array.as_slice() {
println!("{}", i.as_ref().unwrap().GetCount()?);
}
Ok(())
}
} |
I'm having trouble with the
MFTEnumEx
function. It's documentation says that I'm supposed to callRelease
on each of the returnedIMFActivate
pointers, which as far as I could tell is aDrop
impl on::windows::core::IUnknown
(there are no methods to call explicitly like in C++).I was a bit confused getting this to compile at all, in particular the
maybe_activate
line; there's some stuff going on with references that I don't fully understand, but just dereferencing the pointer without borowing resulted in a move which didn't compile becauseOption<IMFActivate>
does not implementCopy
.As you can see I'm calling drop on whatever I get out of the array, though I suspect this is incorrect. If I place a breakpoint on
IUnknown
'sDrop
function, it is not hit. So I guess I'm freeing the reference (a no-op) instead of theIMFActivate
object that I'm supposed to free.Can anyone explain how to use this function correctly?
The text was updated successfully, but these errors were encountered: