Skip to content

Support composable classes with overridable methods #3404

@Alovchin91

Description

@Alovchin91

Suggestion

Hi,

Let me start with the disclaimer: I do understand that this might be an uncommon scenario, but I hope that you'll be able to find some time to look into it nonetheless, and maybe share your experience.

Besides, I believe that generally, this should be a supported scenario.

Anyway, I've been toying with Windows App SDK and Rust, and I'm happy to say that I've managed to create a very simple app! I have essentially reimplemented this app: https://github.com/sotanakamura/winui3-without-xaml

My original research was into WinUI Islands. I must note that I believe that hosting WinUI components, even if from a C++ DLL, within a Win32 app written in Rust could be a valid scenario. I understand that XAML technology itself is very specific to C#, but some things (like the Islands host) are, from my knowledge, "usual" (non-XAML) components.

Anyway :) Here are a few things that I've noticed.

Composable classes with overridable methods

Probably the most important bit for this specific issue and for the future of windows-rs in general, currently windows-bindgen seems unable to generate bindings for unsealed classes with overridable methods.

To make the app run, I need to inherit from the Application class and override the OnLaunched method. For this, I need to call the factory's CreateInstance method with a pointer to my IApplicationOverrides implementation. However, windows-bindgen generates a simple new() function that doesn't accept any parameters. In the end, I had to write the constructor myself like this:

fn create_app() -> Result<Application> {
    // App implements IApplicationOverrides
    let app = App::new()?.into_object();
    let app_unknown = app.as_interface::<IUnknown>();
    ApplicationFactory(|this| unsafe {
        let mut result__ = core::mem::zeroed();
        (Interface::vtable(this).CreateInstance)(
            Interface::as_raw(this),
            Interface::as_raw(&*app_unknown),
            &mut core::ptr::null_mut(),
            &mut result__,
        )
        .and_then(|| Type::from_abi(result__))
    })

Interesting behaviour of Drop

I'm not sure that this is a correct heading, but I experience a bit of an interesting behaviour with this constructor.

In reality, it does a bit more (see the linked project): it actually saves the resulting Application into a variable and then "inits" the App class with it as follows:

#[implement(IApplicationOverrides)]
struct App {
    application: RefCell<Option<Application>>,
}

impl App {
    fn init(&self, app: Application) {
        self.application.borrow_mut().replace(app);
    }
}

impl Drop for App {
    fn drop(&mut self) {
        log::debug!("App::drop");
    }
}

The interesting thing is that, if I do pass the Application to my App class, the latter doesn't get dropped anymore, and the call to RoUninitialize at process exit causes a violating access error. And if I do manage to manually release it (by casting it to IUnknown and then using the vtable), the Drop gets called but I get an Object has been over-released panic at process exit.

I'm sure I'm doing something wrong here, but I can't quite figure it out. Application is a strange object that cannot be put into a ComObject, and cannot even be cast to IApplication. But I need to save it so I could later call its methods.

Usability of this repo as a dependency

I have to note that using this repo as a git dependency is quite sub-optimal. This is because I would like to use only the newer version of windows-bindgen that is capable of generating composable class constructors, but I end up checking out the whole multi-dozen-megabyte repository. I wish there was a new windows-bindgen release on crates.io.

The sample app

You can find the sample app I've built here: https://github.com/Alovchin91/rust-winui

Note that it uses Git LFS because WinAppSDK's bindings that do compile are ginormous 😅

There are also a couple of patches that I had to apply to the bindings: I couldn't find a good way to make WinAppSDK bindings use the Windows::UI::Xaml::Interop bindings so I do search and replace, and WebView2.Core's bindings generate two overloaded methods with the same name so I have to rename the last one manually.

You'll also have to put all the WinAppSDK's WinMDs into the winappsdk/winmd directory manually as I'm not sure if I can legally "redistribute" them.

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions