前一节创建了一个 C++/WinUI 3 应用,接着分析默认生成的框架代码。框架代码中使用了大量 C++ 模板技术和新语言特性,发挥了 C++ 语言零开销抽象的优势,很值得学习。

App 应用入口

重新回到应用入口类 App。

struct App : AppT<App>
{
    App();

    void OnLaunched(Microsoft::UI::Xaml::LaunchActivatedEventArgs const&);

private:
    winrt::Microsoft::UI::Xaml::Window window{ nullptr };
};

应用的入口 App 类继承于一个代码生成的模板 AppT,这里用到的 C++ 奇异模板递归(CRTP)技术和 ATL 一脉相承。

template <typename D, typename ... Interfaces>
struct AppT: public ::winrt::Microsoft::UI::Xaml::ApplicationT<D, ::winrt::Microsoft::UI::Xaml::Markup::IXamlMetadataProvider, Interfaces...>
{
    using IXamlType = ::winrt::Microsoft::UI::Xaml::Markup::IXamlType;

    void InitializeComponent()
    {
        if (_contentLoaded)
            return;
        
        _contentLoaded = true;

        ::winrt::Windows::Foundation::Uri resourceLocator{ L"ms-appx:///App.xaml" };
        ::winrt::Microsoft::UI::Xaml::Application::LoadComponent(*this, resourceLocator);
    }


    IXamlType GetXamlType(::winrt::Windows::UI::Xaml::Interop::TypeName const& type)
    {
        return AppProvider()->GetXamlType(type);
    }

    IXamlType GetXamlType(::winrt::hstring const& fullName)
    {
        return AppProvider()->GetXamlType(fullName);
    }

    ::winrt::com_array<::winrt::Microsoft::UI::Xaml::Markup::XmlnsDefinition> GetXmlnsDefinitions()
    {
        return AppProvider()->GetXmlnsDefinitions();
    }

private:
    bool _contentLoaded{false};
    winrt::com_ptr<XamlMetaDataProvider> _appProvider;
    winrt::com_ptr<XamlMetaDataProvider> AppProvider()
    {
        if (!_appProvider)
        {
            _appProvider = winrt::make_self<XamlMetaDataProvider>();
        }
        return _appProvider;
    }
};

这里的 AppT 是由 C++/WinRT 代码生成器自动生成的,主要逻辑是为ApplicationT 模板类增加了 IXamlMetadataProvider 接口的实现。

这里和 ATL 常用的递归模板不一样的是,模板参数除了传入子类自身外,还可以传入不定长的接口列表。

struct ApplicationT :
    implements<D, winrt::Microsoft::UI::Xaml::IApplicationOverrides, composing, Interfaces...>,
    impl::require<D, winrt::Microsoft::UI::Xaml::IApplication>,
    impl::base<D, Application>,
    winrt::Microsoft::UI::Xaml::IApplicationOverridesT<D>
{
    using composable = Application;
protected:
    ApplicationT()
    {
        impl::call_factory<Application, IApplicationFactory>([&](IApplicationFactory const& f) { [[maybe_unused]] auto winrt_impl_discarded = f.CreateInstance(*this, this->m_inner); });
    }
};

这里 implements 模板约束了接口实现,用来代替 vtable 实现的虚函数重写。impl::requireimpl::base 提供了静态类型转换。

而构造函数委托给了 impl::call_factory, 最终将通过 COM 接口调用到 WinRT 运行时中。

程序入口

App 类实现了应用初始化,那 App 实例本身由谁来构造工程文件中并没有找到。这里利用 Visual Studio 的单步执行调试,直接跳转到了 Win32 C++ 程序的入口 wWinMain。

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    {
        void (WINAPI *pfnXamlCheckProcessRequirements)();
        auto module = ::LoadLibrary(L"Microsoft.ui.xaml.dll");
        if (module)
        {
            pfnXamlCheckProcessRequirements = reinterpret_cast<decltype(pfnXamlCheckProcessRequirements)>(GetProcAddress(module, "XamlCheckProcessRequirements"));
            if (pfnXamlCheckProcessRequirements)
            {
                (*pfnXamlCheckProcessRequirements)();
            }

            ::FreeLibrary(module);
        }
    }

    winrt::init_apartment(winrt::apartment_type::single_threaded);
    ::winrt::Microsoft::UI::Xaml::Application::Start(
        [](auto&&)
        {
            ::winrt::make<::winrt::winui3_test::implementation::App>();
        });

    return 0;
}

这个函数位于 App.xaml.g.hpp 中,由 XamlTypeInfo.g.cpp 引入,这个文件由工程自动生成,但未显示在 VS 的资源管理器中,可能是被 WinUI 3 工程默认引入的。

这里逻辑比较简单,Check 函数应该是用来检查运行环境的,winrt::init_apartment 初始化了 COM 环境,随后调用 Application.Start() 并构造了 App 类。

MainWindow 主窗口

梳理了 WinUI 3 的初始化流程后,接着分析 MainWindow 的功能。

struct MainWindow : MainWindowT<MainWindow>
{
    MainWindow();

    int32_t MyProperty();
    void MyProperty(int32_t value);

    void myButton_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& args);
};

MainWindow 继承自 MainWindowT 并实现了 myButton 的 click 方法。

template <typename D, typename ... I>
struct MainWindowT : public ::winrt::winui3_test::implementation::MainWindow_base<D,
    ::winrt::Microsoft::UI::Xaml::Markup::IComponentConnector,
    I...>
{
    using base_type = typename MainWindowT::base_type;
    using base_type::base_type;
    using class_type = typename MainWindowT::class_type;

    void InitializeComponent();
    virtual void Connect(int32_t connectionId, IInspectable const& target);
    virtual ::winrt::Microsoft::UI::Xaml::Markup::IComponentConnector GetBindingConnector(int32_t connectionId, IInspectable const& target);
    void UnloadObject(::winrt::Microsoft::UI::Xaml::DependencyObject const& dependencyObject);
    void DisconnectUnloadedObject(int32_t connectionId);

    ::winrt::Microsoft::UI::Xaml::Controls::Button myButton()
    {
        return _myButton;
    }
    void myButton(::winrt::Microsoft::UI::Xaml::Controls::Button value)
    {
        _myButton = value;
    }
    
protected:
    bool _contentLoaded{false};

private:
    struct MainWindow_obj1_Bindings;

    ::winrt::Microsoft::UI::Xaml::Controls::Button _myButton{nullptr};
};

MainWindowT 模板向 MainWindow_base 添加了 Button 控件,并实现了控件连接接口 IComponectConnector,上一篇中提到的控件点击事件 myButton_Click 的响应也就是通过该接口实现的。

template <typename D, typename... I>
struct __declspec(empty_bases) MainWindow_base : implements<D, winui3_test::MainWindow, composing, I...>,
    impl::require<D, winrt::Microsoft::UI::Xaml::IWindow>,
    impl::base<D, winrt::Microsoft::UI::Xaml::Window>
{
    using base_type = MainWindow_base;
    using class_type = winui3_test::MainWindow;
    using implements_type = typename MainWindow_base::implements_type;
    using implements_type::implements_type;
    using composable_base = winrt::Microsoft::UI::Xaml::Window;
    hstring GetRuntimeClassName() const
    {
        return L"winui3_test.MainWindow";
    }
    MainWindow_base()
    {
        impl::call_factory<winrt::Microsoft::UI::Xaml::Window, winrt::Microsoft::UI::Xaml::IWindowFactory>([&](winrt::Microsoft::UI::Xaml::IWindowFactory const& f) { [[maybe_unused]] auto winrt_impl_discarded = f.CreateInstance(*this, this->m_inner); });
    }
};

MainWindow_base 仅负责了 Window 对象的构造,和 IWindow 接口约束。

winrt::implements 和 impl::call_factory

到这里基本熟悉了 C++/WinRT 对 WinUI 3 对象的包装方式,也就是通过继承 winrt::implements 来实现 IUnknownIInspectable 这样的基础设施。和传统 COM 对象不同的是放弃了使用虚函数来定义接口,而是通过模板约束接口,减少对象方法调用开销。

而 C++/WinRT 这些的对象实例都只是一个智能指针,真正的实例应该位于 WinRT 内部,这里智能指针只是代理了实际实例的所有方法。代理的方式则是通过 impl::call_factory 实现,内部还是通过 COM 接口来完成调用。

uri.png

参考

作者:lyincc

版权:本文采用 「CC BY 4.0」 知识共享许可协议进行许可。

地址:https://lyincc.com/tech/cpp-winui-3-note-01/