Rantanen / intercom

Object based cross-language FFI for Rust
MIT License
63 stars 7 forks source link

Can winapi structs be reused with intercom? #186

Closed saschanaz closed 3 years ago

saschanaz commented 3 years ago

For example, the thumbnail provider example redefines IStream without using the corresponding winapi definition. Implementing an actual provider requires better definition of IStream with ISequentialStream but redefining everything sounds not ideal.

https://github.com/Rantanen/intercom/issues/21#issuecomment-350478604 mentions [com_impl] but it's not available anymore. Is there still a way to do it?

Rantanen commented 3 years ago

The #[com_interface] and #[com_class] implementations were changed to avoid the need for #[com_impl] so it's not needed, that comment is slightly outdated.

There are few problems in supporting winapi directly.

If you want to call existing COM methods (ie, you receive ISequentialStream as a parameter), you can just accept the type as a winapi-rs pointer and then use that. However if you need to implement ISequentialStream, I'm not entirely sure how winapi-rs types would help, given they are structs instead of traits.

I guess one benefit would be to somehow derive the trait definition from the winapi-rs struct/vtbl, but at that point I feel like the goal should be to derive those definitions from the sources winapi-rs uses (original IDL files or such).

And the above might sound a bit aimless, which is probably because it is! I'd still love "winapi-rs compatibility" - but other than being able to reuse structs (without having to newtype them), I don't think I know what that compatibility would mean. :x

saschanaz commented 3 years ago

If you want to call existing COM methods (ie, you receive ISequentialStream as a parameter), you can just accept the type as a winapi-rs pointer and then use that.

Hmm, so the following code throws build errors, where it receives IStream as a parameter:

use winapi::um::objidlbase::IStream;

#[com_interface(com_iid = "b824b49d-22ac-4161-ac8a-9916e8fa3f7f")]
trait IInitializeWithStream {
    fn initialize(&self, stream: &ComItf<IStream>, mode: DWORD) -> ComResult<()>;
}
error[E0277]: the trait bound `IStream: ForeignType` is not satisfied
   --> src\lib.rs:197:26
    |
197 |     fn initialize(&self, stream: &ComItf<IStream>, mode: DWORD) -> ComResult<()>;
    |                          ^^^^^^ the trait `ForeignType` is not implemented for `IStream`
    | 
   ::: C:\Users\Kagami\.cargo\git\checkouts\intercom-a35634f63190d595\4633ab2\intercom\src\type_system.rs:108:1
    |
108 | pub unsafe trait ExternType<TS: TypeSystem>
    | ------------------------------------------- required by this bound in `ExternType`
    |
    = note: required because of the requirements on the impl of `ExternType<RawTypeSystem>` for `&intercom::ComItf<IStream>`

And of course the "basic Rust trait rules" prevents me to derive that. Based on the mentioned problems I guess there is no good way to do this yet.

Rantanen commented 3 years ago

By using the winapi pointer, you'll need to deal with the "raw" IStream struct. You should be able to newtype that similar to how the other winapi types are newtyped for various Intercom traits:

#[derive(intercom::ExternType, intercom::ForeignType, intercom::ExternOutput, intercom::ExternInput)]
#[repr(transparent)]
struct ComIStream(IStream)

The winapi IStream should already be FFI compatible and the newtype wrapping allows you to implement the traits necessary for Intercom to handle it - though ExternOutput is not strictly necessarily if the type never appears as a return value.

In any case, that should allow you to define the method as

fn initialize (&self, stream: ComIStream, ...) -> ComResult<()> {
    let istream = stream.0;
    unsafe {
        let hr = istream.foo(..);
    }
}

Intercom does implement From<intercom::error::raw::HRESULT> for ComResult<()> and intercom::raw::HRESULT can be constructed with ::new(i32), which winapi::...::HRESULT should be compatible with (being a type alias for i32, so along the lines of:

use intercom::error::raw::HRESULT as IntercomHR;

let winapi_hr = istream.foo(..);
let intercom_hr = IntercomHR::new(winapi_hr);
ComResult::from(intercom_hr)?;

The last line might need type parameters along the lines of ComResult::<()>::from(...), etc.

If you need the ISequentialStream, you'll need to go through query_interface and remember to handle reference counting manually since there's no ComRc/ComItf wrappers to handle that.

saschanaz commented 3 years ago

Thanks, I made it work with the Com* wrapper!

For anyone who read this later: It should be ComLPSTREAM instead of ComIStream because you'll receive a pointer. query_interface is not needed because an IStream is automatically ISequentialStream (because it's the ancestor interface).

Edit: https://github.com/saschanaz/jxl-winthumb