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

implement macro not implementing the Interface trait? #2882

Closed
daladim opened this issue Feb 26, 2024 · 7 comments
Closed

implement macro not implementing the Interface trait? #2882

daladim opened this issue Feb 26, 2024 · 7 comments
Labels
question Further information is requested

Comments

@daladim
Copy link
Contributor

daladim commented Feb 26, 2024

Summary

I'd like to use the RegisterForReport COM function, that requires to create a class that inherits ILocationEvents.

I could implement a suitable struct using #[implement(ILocationEvents)] (and a impl ILocationEvents_Impl for ... block), but the compiler refuses to use the Rust binding to this COM function because my custom Struct does not implement Interface. I pasted the actual error messages in the reproducer code below.

I am not sure what is wrong here.

  • Am I missing anything?
    But the examples I could read here and there did not specify anything about explicily implementing this Interface.
  • The generated doc of my custom struct indeed states that its does not implement Iterface. Given the doc of this trait, I'm not supposed to implement it myself. I could not successfully tell the #[implement] macro to implement it, as my attempts such as #[implement(ILocationEvents, Interface)] fail to compile.
  • Should #[implement(ILocationEvents)] know it is supposed to implement Interface as well? If so, is something missing from windows-rs/winmd side?
  • Instead of passing my custom struct to RegisterForReport, I tried passing my_struct.cast::<ILocationEvents>(), which compiles but (not so surprisingly) crashes at runtime.

Thanks a lot

Crate manifest

[package]
name = "repro"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
windows = { version = "0.53.0", features = [
    "implement",
    "Win32_System_Com",
    "Win32_Foundation",
    "Win32_Devices",
    "Win32_Devices_Geolocation",
] }

Crate code

use windows::Win32::Devices::Geolocation::{
    Location,
    ILocation,
    ILocationEvents,
    ILocationEvents_Impl,
    ILocationReport,
    ILatLongReport,
    LOCATION_REPORT_STATUS,
};
use windows::Win32::System::Com::{
    CoInitializeEx,
    CoCreateInstance,
    CLSCTX_ALL,
    COINIT_MULTITHREADED,
};
use windows::core::Interface;
use windows::core::implement;
use windows::core::GUID;


#[implement(ILocationEvents)]
struct LocationCallbacks;

impl ILocationEvents_Impl for LocationCallbacks {
    fn OnLocationChanged(
        &self,
        reporttype: *const GUID,
        plocationreport: Option<&ILocationReport>
    ) -> windows::core::Result<()> {
        println!("OnLocationChanged {plocationreport:?}");
        Ok(())
    }

    fn OnStatusChanged(
        &self,
        reporttype: *const GUID,
        newstatus: LOCATION_REPORT_STATUS
    ) -> windows::core::Result<()>
    {
        println!("OnStatusChanged {newstatus:?}");
        Ok(())
    }
}


// This function is inspired from the Microsoft (C++) code example at https://learn.microsoft.com/en-us/windows/win32/api/locationapi/nf-locationapi-ilocation-getreportstatus#workaround--subscribing-to-events
fn main() -> Result<(), Box<dyn std::error::Error>> {
    unsafe {
        CoInitializeEx(None, COINIT_MULTITHREADED)
    }.ok()?;

    let location_provider = unsafe {
        CoCreateInstance::<_, ILocation>(
            &Location as *const _,
            None,
            CLSCTX_ALL,
        )?
    };

    let callback_object = LocationCallbacks{};
    let res = unsafe{ 
        location_provider.RegisterForReport(
            &callback_object,
            &ILatLongReport::IID as *const _,
            100,
        )
    }?;
    // Note: passing `&callback_object` gives those errors:
    //       * the trait `Interface` is not implemented for `LocationCallbacks`
    //       * the trait `CanInto<ILocationEvents>` is not implemented for `LocationCallbacks`
    // Passing an (owned) `callback_object` instead would give:
    //       * the trait `IntoParam<ILocationEvents, ReferenceType>` is not implemented for `LocationCallbacks` 

    Ok(())
}
@daladim daladim added the bug Something isn't working label Feb 26, 2024
@daladim daladim changed the title implement macro not implmenting the Interface trait? implement macro not implmenting the Interface trait? Feb 26, 2024
@daladim daladim changed the title implement macro not implmenting the Interface trait? implement macro not implementing the Interface trait? Feb 26, 2024
@kennykerr kennykerr added question Further information is requested and removed bug Something isn't working labels Feb 26, 2024
@daladim
Copy link
Contributor Author

daladim commented Feb 27, 2024

Alternatively, should I implement CanInto<ILocationEvents> for LocationCallbacks myself (and return something like self.cast::<ILocationEvents>()?)
This trait not being documented suggest I am not supposed to 😅

@robmikh
Copy link
Member

robmikh commented Feb 27, 2024

I got this working by using .into() before the borrow:

let callback_object: ILocationEvents = LocationCallbacks{}.into();
unsafe{ 
	location_provider.RegisterForReport(
		&callback_object,
		&ILatLongReport::IID as *const _,
		100,
	)?
};

@daladim
Copy link
Contributor Author

daladim commented Feb 27, 2024

Awesome, thanks a lot :)
This fixes my problem, so this issue can be closed.

But how can we document this so that others could benefit from this?
Do you want me to add this code to the samples in this repo?
Do you know why we have to explicitly add this .into() in the first place? Should windows-rs make this implicit instead?

@kennykerr
Copy link
Collaborator

https://kennykerr.ca/rust-getting-started/how-to-implement-com-interface.html

@daladim
Copy link
Contributor Author

daladim commented Feb 27, 2024

All that remains is to move, or box, the implementation into the COM implementation provided by the implement macro through the Into trait:

That's the key here. It was indeed visible in the example you're linking (and which I had a look at), but it was easy to miss.
Would you accept an MR of mine if I add the link you just provided in the Rust documentation of #[implement]?

@daladim
Copy link
Contributor Author

daladim commented Feb 27, 2024

That's also irritating that documentation on COM api is ridiculously hard to find as Google mostly ignores the "COM" keyword (or rather, it matches most of the documentation that is hosted on a .com TLD, and is thus effectively a useless keyword)

@kennykerr
Copy link
Collaborator

Sure, that sounds great thanks.

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

No branches or pull requests

3 participants