Skip to content
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

winapi interfaces interoperability #180

Closed
DataTriny opened this issue Nov 7, 2020 · 8 comments
Closed

winapi interfaces interoperability #180

DataTriny opened this issue Nov 7, 2020 · 8 comments

Comments

@DataTriny
Copy link

Hello,

I need to define COM objects that implement COM interfaces that I can pass to functions declared by the winapi crate.

Let's pretend that winapi defines a function and an interface like:

fn DoStuff(pRetVal: *mut *mut ISomeInterface) -> HRESULT;

RIDL!{#[uuid(...)]
interface ISomeInterface(ISomeInterfaceVtbl): IUnknown(IUnknownVtbl) {
	fn GetValue(
		pRetVal: *mut u8,
	) -> HRESULT,
}}

Because winapi doesn't provide an easy way to implement interfaces for objects, I would like to use this crate to help me deal with virtual table management.

So I tried re-defining the interface above using the com::interface! macro:

com::interfaces! {
	#[uuid("...")]
	pub unsafe interface ISomeInterface: IUnknown {
		pub fn get_value(
			&self,
			p_ret_val: *mut u8
		) -> HRESULT;
	}
}

com::class! {
	pub class MyObject: ISomeInterface {
		value: u8
	}
	
	impl ISomeInterface for MyObject {
		fn get_value(&self, p_ret_val: *mut u8) -> HRESULT {
			unsafe { *p_ret_val = self.value; };
			NOERROR
		}
	}
}

And convert it to its winapi counterpart:

let o = MyObject::allocate(10);
let mut i = o.query_interface::<ISomeInterface>().unwrap();
let p_i: *mut ISomeInterface = &mut i;
let p = p_i as *mut c_void;
let p_wi = p as *mut crate::winapi_interfaces::ISomeInterface;
let v: *mut u8 = null_mut();
(*p_wi).GetValue(v);

But it always fail at runtime:

error: process didn't exit successfully: `target\debug\comtest.exe` (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)

I have checked that all three pointers point to the same memory address, I have used the -Zprint-type-sizes flag to make sure that the memory layout of both versions of the interface are the same.

The only differences I can see are the name of both virtual table field (which I don't think matter), and the calling convention of the virtual table functions (stdcall for com, system for winapi, so it could differ).

What am I doing wrong? Since I am a beginner with COM, does my conversion code even make sence?

Many thanks.

Best regards.

@MarijnS95
Copy link
Contributor

MarijnS95 commented Nov 7, 2020

Hey!

Two small things:

  • ISomeInterface from com-rs contains an inner: field which is a pointer to ISomeInterfaceVPtr (the object in memory), which is again a pointer to the actual vtable (ISomeInterfaceVTable). You need to interpret i as *mut ISomeInterface instead of taking the address to it. (in other words, ISomeInterface is not the interface by-value on the stack, instead it contains an internal pointer to the object).
  • You pass a null_mut() into GetValue, which will in turn get dereferenced at *p_ret_val. Instead consider passing:
    let mut v = 0u8;
    (*p_wi).GetValue(&mut v as *mut _)

That should get the segfaults solved. Consider using cargo-expand (or browse the com-rs source code!) to see the layout of generated structures and/or a powerful debugger like gdb that can deal with Rust struct formats and inspect their layout (and pointer values!) accordingly.

Oh, and a sidenote for @rylev: with winapi being relatively stale, here's hoping it's going to be revived at some point with all interfaces consistently converted to com-rs, which I assume is part of Microsofts effort to provide good Windows support in Rust?

@DataTriny
Copy link
Author

Hi, here is an updated version of my code that seems to work:

let o = MyObject::allocate(10);
let mut i = o.query_interface::<ISomeInterface>().unwrap();
let p_i: *mut ISomeInterface = &mut i;
let p = p_i as *mut c_void;
let p_wi = p as *mut *mut crate::winapi_interfaces::ISomeInterface;
let mut v = 0u8;
(**p_wi).GetValue(&mut v as *mut _);
println!("{}", v);

Thank you @MarijnS95 for your help, the null_mut pointer was of course stupid, but I somewhat missed the double indirection for the virtual table.

I also agree with you on the winapi part and I expressed my concerns on retep998/winapi-rs#867.

Regards.

@MarijnS95
Copy link
Contributor

@DataTriny Awesome, glad I could help!

Note that these casts/transmutes are undefined behaviour unless the interface struct is marked as repr(C) or repr(transparent). Perhaps com-rs should attain an .as_raw() function to take the inner object pointer out for exactly these usecases, until the whole com-rs/winapi "thingy" is unified?

@rylev
Copy link
Contributor

rylev commented Nov 9, 2020

@MarijnS95 all the wrapper structs in com-rs are #[repr(transparent)] so they can be treated just like the underlying pointers. Or did I miss a case?

@MarijnS95
Copy link
Contributor

@rylev Ah never mind I was looking at the wrong line, the interface is indeed repr(transparent):

#[repr(transparent)]
#[derive(Debug)]
#vis struct #name {
inner: ::std::ptr::NonNull<#vptr>,

Sorry for that. Still, would it make sense to have a way to pull this pointer out without casts/transmutes?

@DataTriny In that case it's probably more natural to write let p_wi: *mut ISomeInterface = unsafe { std::mem::transmute(i) };.

@DataTriny
Copy link
Author

@MarijnS95 I initially used std::mem::transmute but as I understand, it copies the data. Given the fact that com-rs interfaces are pinned, and so should not be moved, wouldn't it cause me trouble?

@rylev
Copy link
Contributor

rylev commented Nov 9, 2020

@DataTriny. std::mem::transmute copies the ptr itself (not the data it points to) and forgets the previous value. This is effectively a move of the pointer and is totally fine to do.

@DataTriny
Copy link
Author

Makes sence. Thanks @rylev for clarifying.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants