microsoft / microsoft-ui-xaml

Windows UI Library: the latest Windows 10 native controls and Fluent styles for your applications
MIT License
6.3k stars 676 forks source link

Unable to implement classic COM interface on XAML component with C++/WinRT #3331

Open sylveon opened 4 years ago

sylveon commented 4 years ago

Describe the bug When creating a XAML control/page, it's not possible to make it implement a classic COM interface (as C++/WinRT does it, by appending it to the base template arguments: Something : SomethingT<Something, IMyClassicInterface>) because the XAML compiler codegen does not account for this case.

Steps to reproduce the bug

  1. Create a new blank C++/WinRT UWP app in VS
  2. Open MainPage.h, and add the following code:
    • Include <windows.ui.xaml.media.dxinterop.h>.
    • Change the struct declaration to struct MainPage : MainPageT<MainPage, ISwapChainPanelNative>.
    • Add IFACEMETHOD(SetSwapChain)(IDXGISwapChain*) noexcept; to the struct members.
  3. In MainPage.cpp, add the implementation:
    IFACEMETHODIMP MainPage::SetSwapChain(IDXGISwapChain*) noexcept
    {
       // dummy implementation
       return S_OK;
    }
  4. Try building the app.
  5. Observe compiler errors.

Expected behavior The app builds fine and a consumer is able to cast the XAML component to ISwapChainPanelNative and call the method provided by that interface.

Screenshots image

Version Info

NuGet package version: [Microsoft.Windows.CppWinRT 2.0.200921.6]

Windows app type: UWP Win32
Yes Yes
Windows 10 version Saw the problem?
Insider Build (xxxxx)
May 2020 Update (19041) Yes
November 2019 Update (18363)
May 2019 Update (18362)
October 2018 Update (17763)
April 2018 Update (17134)
Fall Creators Update (16299)
Creators Update (15063)
Device form factor Saw the problem?
Desktop Yes
Xbox
Surface Hub
IoT

Additional context I replied Yes to the app type being Win32 because this issue showed up in system XAML islands while implementing IInitializeWithWindow to pass on a window handle to MessageDialog from my hosting code to the XAML code. I'm using MessageDialog because of ContentDialog's limitation of one dialog open at a time per thread. Implementing threading proved to be a huge pain, so I've decided on using MessageDialog for now.

The bug is caused by this line in MainPage.xaml.g.hpp:

template struct MainPageT<struct MainPage>;

It tries to specialize MainPageT using different parameters than what I used in my declaration, and since the class uses CRTP, it tries to cast this back to MainPage, but because MainPage actually inherits from a different type (different template instantiations are effectively different types), the cast fails.

StephenLPeters commented 4 years ago

@kennykerr or @jevansaks I feel like we do this already, I think one of you two probably knows the answer. If not this sounds like a xaml compiler bug? @fabiant3

StephenLPeters commented 4 years ago

going with markup team for now.

sylveon commented 4 years ago

Yeah, it is a XAML compiler bug. Without XAML (a normal WinRT runtime component) it does work, but the XAML compiler's explicit specialization in .xaml.g.hpp is what breaks it.

fabiant3 commented 4 years ago

@RealTommyKlein - can you take a look?

sylveon commented 3 years ago

My workaround was implementing the functionality in a subclass that doesn't use the XAML compiler: https://github.com/TranslucentTB/TranslucentTB/commit/8f68f3c0b1b1900b76c3900c98b8270da1e0c535

DHowett commented 3 years ago

Guessing that there's no workaround for this one? Just bit Terminal :/

sylveon commented 3 years ago

There's 2 options:

kennykerr commented 3 years ago

@Scottj1s any chance someone from the Xaml compiler can take a look at this seemingly trivial bug?

DHowett commented 3 years ago

@sylveon There was a brief but real moment when I realized I was here because I was also implementing IInitializeWithWindow in a XAML islands application that I was quite crestfallen because these are the depths to which we must sink.

Why does the XAML generated header need to forcibly instantiate this template anyway? Does it not suffice to let the consumer do it?

sylveon commented 3 years ago

The XAML codegen does this because some members are forward declared in the .g.h file, which you include, and then implemented + instantiated in .g.hpp, which you don't include (it's built as part of XamlTypeInfo.g.cpp).

sylveon commented 3 years ago

Without this explicit instantiation, you get linker errors.

sylveon commented 1 year ago

Bump, still an issue