WinUI 3 WinRT C++ 开发完整教程 - 第零部分:环境配置与基础概念
什么是 WinUI 3 和 WinRT?
WinUI 3 简介
WinUI 3 是微软为 Windows 平台开发的现代原生用户界面框架。让我们通过类比来理解:
- 如果说 Win32 API 是传统的"汇编语言级别"的 UI 开发方式
- 那么 WinUI 3 就是"高级语言级别"的现代化 UI 开发方式
WinUI 3 的优势:
- 现代化设计:内置 Fluent Design System,自动适配 Windows 11 风格
- 高性能:基于硬件加速的渲染引擎
- 跨版本兼容:支持 Windows 10 1809 及更高版本
- 丰富控件:提供 100+ 现代化 UI 控件
WinRT 详解
WinRT (Windows Runtime) 是微软设计的应用程序平台架构。为了理解 WinRT,我们需要了解几个关键概念:
1. 什么是 COM?
COM (Component Object Model) 是微软的组件对象模型:
cpp
// 传统的 COM 接口定义
interface IUnknown
{
virtual HRESULT QueryInterface(REFIID riid, void** ppv) = 0;
virtual ULONG AddRef() = 0;
virtual ULONG Release() = 0;
};
COM 的核心概念:
- 接口:定义对象的行为契约
- 引用计数:自动管理对象生命周期
- 语言无关:可以被不同编程语言使用
2. WinRT 如何改进 COM?
WinRT 基于 COM 但进行了现代化改进:
传统 COM 的问题:
cpp
// 传统 COM 代码很繁琐
IFileDialog* pFileDialog = nullptr;
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog,
nullptr,
CLSCTX_ALL,
IID_IFileDialog,
(void**)&pFileDialog);
if (SUCCEEDED(hr))
{
// 使用对象...
pFileDialog->Release(); // 手动管理内存
}
WinRT 的简化:
cpp
// WinRT 代码更简洁
auto fileDialog = winrt::Windows::Storage::Pickers::FileOpenPicker();
// 自动内存管理,无需手动 Release
3. 元数据驱动开发
WinRT 使用 .winmd 文件存储类型信息:
idl
// MainWindow.idl - 接口定义语言
namespace WinUI3App1C__
{
[default_interface]
runtimeclass MainWindow : Microsoft.UI.Xaml.Window
{
MainWindow();
Windows.Foundation.Collections.IObservableVector<String> collection{ get; };
}
}
这个 IDL 文件做了什么?
- 定义接口契约:告诉编译器这个类有什么方法和属性
- 生成元数据:编译成 .winmd 文件,包含类型信息
- 启用投影:其他语言可以使用这些类型
核心概念详解
1. 命名空间 (Namespace)
WinRT 使用分层命名空间组织 API:
cpp
// WinRT 命名空间层次结构
winrt::Windows::Foundation:: // 基础类型
winrt::Windows::Foundation::Collections:: // 集合类型
winrt::Microsoft::UI::Xaml:: // XAML UI 框架
winrt::Microsoft::UI::Xaml::Controls:: // UI 控件
实际使用示例:
cpp
// 完整命名空间
winrt::Windows::Foundation::Collections::IObservableVector<hstring> collection;
// 使用 using 简化
using namespace winrt::Windows::Foundation::Collections;
IObservableVector<hstring> collection; // 简化后的写法
2. hstring 类型详解
hstring 是 WinRT 的字符串类型:
cpp
// 创建 hstring 的几种方式
hstring str1{L"Hello World"}; // 从字面量创建
hstring str2 = winrt::to_hstring(42); // 从数字创建
hstring str3 = L"Direct assignment"; // 直接赋值
// hstring 的特点
std::wcout << str1.c_str() << std::endl; // 获取 C 风格字符串
size_t length = str1.size(); // 获取长度
bool isEmpty = str1.empty(); // 检查是否为空
为什么不直接用 std::string?
- WinRT 互操作性:hstring 是 WinRT ABI 的标准字符串类型
- 性能优化:内部优化了跨语言传递
- 自动编码处理:自动处理 UTF-16 编码
3. 装箱和拆箱 (Boxing/Unboxing)
WinRT 使用 IInspectable
作为基础对象类型,类似于 C# 的 object
:
cpp
// 装箱:将具体类型转换为 IInspectable
int32_t number = 42;
winrt::Windows::Foundation::IInspectable boxed = winrt::box_value(number);
// 拆箱:从 IInspectable 恢复具体类型
int32_t unboxed = winrt::unbox_value<int32_t>(boxed);
// 字符串装箱示例
hstring text = L"Hello";
auto boxedText = winrt::box_value(text);
装箱的应用场景:
cpp
// ListView 的 Items 集合只接受 IInspectable
manualList().Items().Append(winrt::box_value(hstring{L"Item 1"}));
4. 智能指针和 RAII
C++/WinRT 使用智能指针自动管理内存:
cpp
// 自动内存管理示例
{
auto button = winrt::Microsoft::UI::Xaml::Controls::Button();
button.Content(winrt::box_value(L"Click Me"));
// 当 button 超出作用域时,自动释放内存
} // button 在这里自动销毁
// 弱引用示例
winrt::weak_ref<winrt::Microsoft::UI::Xaml::Controls::Button> weakButton = button;
if (auto strongButton = weakButton.get())
{
// 安全使用按钮
strongButton.Content(winrt::box_value(L"Updated"));
}
开发环境搭建
环境
Visual Studio 2022
- 版本:使用最新版,社区版即可
- 必须工作负载:使用 C++ 的桌面开发
Windows SDK
- 版本:Windows 10 SDK (10.0.19041.0) 或更高
- 包含:WinRT 头文件和库
C++ 基础要求
必需的 C++ 知识
1. C++17 特性
项目使用 ISO C++17 标准,需要了解基础的,如果你不熟悉,我们在入门系列里也深入讲解了:
cpp
// auto 类型推导
auto number = 42; // int
auto text = L"Hello"; // const wchar_t*
auto collection = std::vector<int>{}; // std::vector<int>
// Lambda 表达式
auto clickHandler = [this](auto&& sender, auto&& args)
{
// 事件处理逻辑
};
// 范围-based for 循环
std::vector<hstring> items = {L"A", L"B", L"C"};
for (auto&& item : items)
{
std::wcout << item.c_str() << std::endl;
}
2. 智能指针
cpp
#include <memory>
// unique_ptr:独占所有权
std::unique_ptr<int> ptr = std::make_unique<int>(42);
// shared_ptr:共享所有权
std::shared_ptr<std::string> shared = std::make_shared<std::string>("Hello");
// weak_ptr:弱引用
std::weak_ptr<std::string> weak = shared;
3. 异常处理
cpp
try
{
// 可能抛出异常的代码
auto result = risky_operation();
}
catch (winrt::hresult_error const& ex)
{
// 处理 WinRT 异常
std::wcout << L"Error: " << ex.message().c_str() << std::endl;
}
catch (std::exception const& ex)
{
// 处理标准 C++ 异常
std::cout << "Error: " << ex.what() << std::endl;
}
推荐的 C++ 学习资源
书籍:
- 《C++ 20 高级编程》(第5版)
- 《Effective Modern C++》
- 《C++ Core Guidelines》
在线资源:
- cppreference.com
- Microsoft C++ 文档
第一个 WinUI 3 应用程序
创建项目
- 打开 Visual Studio 2022
- 选择"创建新项目"
- 搜索"WinUI"
- 选择"空白应用,打包的 (WinUI 3 in C++)"
- 设置项目名称:例如 "MyFirstWinUI3App"
项目结构解析
创建后的项目包含这些重要文件:
MyFirstWinUI3App/
├── App.xaml # 应用程序定义
├── App.xaml.h # 应用程序头文件
├── App.xaml.cpp # 应用程序实现
├── MainWindow.xaml # 主窗口界面定义
├── MainWindow.xaml.h # 主窗口头文件
├── MainWindow.xaml.cpp # 主窗口实现
├── MainWindow.idl # 主窗口接口定义
├── pch.h # 预编译头文件
└── Package.appxmanifest # 应用程序清单
理解关键文件
pch.h(预编译头文件)
cpp
#pragma once
#include <windows.h>
#include <unknwn.h>
#include <restrictederrorinfo.h>
#include <hstring.h>
// 解决宏名称冲突
#undef GetCurrentTime
// WinRT 基础头文件
#include <winrt/base.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
// WinUI 3 头文件
#include <winrt/Microsoft.UI.Xaml.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#include <winrt/Microsoft.UI.Xaml.Navigation.h>
pch.h 的作用:
- 加速编译:预编译常用头文件
- 统一包含:所有源文件都包含这些基础类型
- 解决冲突:处理宏名称冲突
MainWindow.idl
idl
namespace MyFirstWinUI3App
{
[default_interface]
runtimeclass MainWindow : Microsoft.UI.Xaml.Window
{
MainWindow();
Int32 MyProperty;
}
}
IDL 文件的作用:
- 定义公共接口:声明可以被外部访问的方法和属性
- 生成元数据:编译器生成 .winmd 文件
- 启用代码生成:自动生成投影代码
MainWindow.xaml.h
cpp
#pragma once
#include "MainWindow.g.h"
namespace winrt::MyFirstWinUI3App::implementation
{
struct MainWindow : MainWindowT<MainWindow>
{
MainWindow();
int32_t MyProperty();
void MyProperty(int32_t value);
private:
int32_t m_myProperty{0};
};
}
namespace winrt::MyFirstWinUI3App::factory_implementation
{
struct MainWindow : MainWindowT<MainWindow, winrt::MyFirstWinUI3App::implementation::MainWindow>
{
};
}
关键点解释:
#include "MainWindow.g.h"
:包含自动生成的基类MainWindowT<MainWindow>
:CRTP (Curiously Recurring Template Pattern)implementation
命名空间:包含具体实现factory_implementation
:对象工厂实现
编译流程详解
IDL 编译:
bashmidl.exe MainWindow.idl # 生成 MainWindow.winmd
代码生成:
bashcppwinrt.exe -input . -output Generated Files # 生成 MainWindow.g.h 和 MainWindow.g.cpp
XAML 编译:
bash# XAML 编译器处理 .xaml 文件 # 生成相应的 C++ 代码
C++ 编译:
bashcl.exe /std:c++14 *.cpp # 编译所有 C++ 源文件
运行第一个应用程序
- 按 F5 或点击"调试">"开始调试"
- 应用程序应该显示一个空白窗口
- 这表明环境配置正确!
添加第一个控件
让我们修改 MainWindow.xaml 添加一个按钮:
xml
<Window x:Class="MyFirstWinUI3App.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button x:Name="myButton"
Content="点击我!"
Click="myButton_Click"/>
</Grid>
</Window>
在 MainWindow.xaml.h 中添加事件处理器声明:
cpp
void myButton_Click(winrt::Windows::Foundation::IInspectable const& sender,
winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e);
在 MainWindow.xaml.cpp 中实现事件处理器:
cpp
void MainWindow::myButton_Click(winrt::Windows::Foundation::IInspectable const& sender,
winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e)
{
myButton().Content(winrt::box_value(L"已点击!"));
}
代码解释:
x:Name="myButton"
:为控件指定名称,可在 C++ 代码中访问Click="myButton_Click"
:指定点击事件处理器myButton()
:访问 XAML 中定义的控件winrt::box_value()
:将字符串装箱为 WinRT 对象
总结
这一部分我们学习了:
- WinUI 3 和 WinRT 的基本概念
- COM 和 WinRT 的关系
- 开发环境的搭建
- 必要的 C++ 知识
- 第一个 WinUI 3 应用程序的创建
下一部分我们将深入学习 WinRT 的类型系统和基础 API。
这是 WinUI 3 WinRT C++ 完整教程的第零部分。接下来我们将学习更深入的概念和实际开发技巧。