说明
C++/WinRT 最初是用来开发 UWP 应用程序的。WinUI3 继承了 UWP 部分API,重新设计了 UI 类型,并新增了 WindowsAppSDK API.
C++/WinRT 可用于 WinUI3 开发。
使用 C++ 的优点:
- 方便使用C++标准库。
- 方便使用第三方C++库。
- 方便直接使用 Windows API。
- 编译后的可执行文件目录中没有 .NET dll 库文件, 发布的程序体积比 C# 小很多.
也有这些缺点:
- 上手难度会大一点。
- 编译后生成的预编译头文件
ProjectName\x64\Debug\pch.pch
大小在 955MB 左右,整个项目容量大小在 1GB 以上。
- 自定义运行时类型,需要通过 idl 文件(使用的 MIDL 3.0 语言只是接口描述语言, 并不需要实现, 难度并不大)。编写 idl 文件时,不能智能提示,需要查看文档找到所需接口,或类型的完整名称。
- 文件比较繁琐,一个 xaml 页面会有四个文件(.xaml, .h, .cpp, .idl)。通过将项目的所有 idl 文件内容集中到一个文件中,可以减少到三个文件。
运行时类型,实现类型
当新建一个 C++/WinRT WinUI3 APP项目时候,假设名称为App1,那么将会看到在三个命名空间中都会出现 MainWindow
类型:
- winrt::App1::MainWindow (运行时类型, C++中的运行时类型只是WinRT中对应类型的投影)。
- winrt::App1::implementation::MainWindow (实现类型)。
- winrt::App1::factory_implementation::MainWindow (这个类型一般是空白的,可以不用特别在意)。
这个运行时类型,是通过 idl 文件定义的,这描述了其中公开的成员(属性,函数,事件),但并不能在 idl 文件中实现这些成员。如果需要实现,C++/WinRT 是通过 implementation:: 下同名类型实现的。
当我们调用一个运行时类型的方法(比如:void Hello()),会被映射到 implementation:: 下同名类型的 void Hello() 。
如果编译时在投影类型中找不到 idl 文件中定义的函数,就会报错,这也是常见的编译错误。
IDL 文件
参考文档。
IDL 文件可以定义以下类型:
- 委托 (关键字 delegate)
- 类似于函数指针,与 C# 委托类似。
- 结构 (关键字 struct)
- IDL 文件内的结构会自动生成, 在
Generated Files\winrt\impl\ProjectName.0.h
文件内. - 结构定义在idl 好处是可以在 idl文件和c++文件中都能使用。
- IDL 文件内的结构会自动生成, 在
- 枚举(关键字 enum)
- IDL 文件内的枚举会自动生成, 在
Generated Files\winrt\impl\ProjectName.0.h
文件内. - 枚举定义在 idl 的好处是可以在 idl 文件和c++文件中都能使用。
- IDL 文件内的枚举会自动生成, 在
- 接口 (关键字 interface)
- 约定了应该实现的属性,方法,事件。
- 运行时类型(关键字 runtimeclass)
- 运行时类型对应着一个头文件和一个源文件, 也会自动生成 (在
Generated Files\Source
目录中), 但必须手动复制到项目中.
- 运行时类型对应着一个头文件和一个源文件, 也会自动生成 (在
常见的问题解决方法
- FileOpenPicker, FileSavePicker, FolderPicker 在C#中并不需要使用窗口句柄初始化,在 C++/WinRT 中需要用窗口句柄初始化。
#include <microsoft.ui.xaml.window.h>
#include <Shobjidl.h>
IAsyncAction SelectFolderAsync()
{
FolderPicker picker{};
picker.FileTypeFilter().Append(L"*");
// 初始化 picker
auto initializeWithWindow {picker.as<::IInitializeWithWindow>()};
initializeWidthWindow->Initialize(hWnd);
if(StorageFolder folder = co_await folderPicker.PickSingleFolerAsync())
{
// doSomething()
}
}
- 现在 WinUI3 应用程序可以不用打包,编译后点击 .exe 文件就能运行。需要在 *.vcxproj 文件中进行以下修改:
<PropertyGroup Label="Globals">
<!-- 这行是已经存在的,需要将 true 修改为 false. -->
<AppxPackage>false</AppxPackage>
<!-- 这行是新增的。 -->
<WindowsPackageType>None</WindowsPackageType>
</PropertyGroup>
- 在 C++/WinRT 的 idl 文件中,并不能直接使用 int, float, double, 之类的数据类型,需要使用 WinRT 类型名称替代,可以参考以下表格:
- 如何在 C++/WinRt 中实现接口 INotifyPropertyChanged?
第一步:在 idl 文件中,让运行时类型继承Microsoft.UI.Xaml.Data.INotifyPropertyChanged
接口。
runtimeclass Person : Microsoft.UI.Xaml.Data.INotifyPropertyChanged
{
String Name{get;set;};
}
第二步: 在 implementation::Person 投影类型的 .h 文件中,编写以下内容:
struct Person
{
public:
hstring Name() const {return m_name;}
void Name(hstring value)
{
if(value != m_name){
m_name = value;
// 触发事件,Name 属性改变了。
m_propertyChanged(*this, PropertyChangedEventArgs(L"Name"));
}
}
winrt::event_token PropertyChanged(winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
{
return m_propertyChanged.add(handler);
}
void PropertyChanged(winrt::event_token const& token) noexcept
{
m_propertyChanged.remove(token);
}
private:
event<Microsoft::UI::Xaml::Data::PropertyChangedEventHandler> m_propertyChanged {};
hstring m_name;
}
如果属性发生改变,需要触发事件可以这样做:
m_propertyChanged(*this, PropertyChangedEventArgs(propertyName));