第一次接触C++/WinRT时,我花了整整两天时间在环境配置上。现在回想起来,其实只需要三个关键步骤就能搞定。首先确保你的Windows系统版本不低于1803(10.0.17134.0),这个版本号可以在"设置→系统→关于"里查到。我建议直接用最新版Windows 11,能避免90%的兼容性问题。
Visual Studio的选择有讲究。2022社区版完全够用,安装时记得勾选"使用C++的桌面开发"工作负载。有个容易忽略的细节:在"单个组件"选项卡里要额外勾选"Windows 10 SDK (10.0.17134.0)"或更高版本。我遇到过有人装了VS却跑不了项目,就是因为漏了这个SDK。
验证环境是否就绪有个小技巧:新建一个空白C++控制台项目,在项目属性里检查两项——"C++语言标准"要设为ISO C++17,Windows SDK版本要显示10.0.17134.0以上。如果看到灰色不可选的状态,说明SDK没装对,需要重新运行VS安装器。
打开VS2022,选择"创建新项目",这次别选常规的C++控制台项目,我们要用专门的项目模板。在搜索框输入"WinRT",会看到"C++/WinRT控制台应用"模板。如果找不到这个选项,可能需要先安装C++/WinRT VSIX扩展,微软商店里搜"Windows C++/WinRT"就能找到。
新建项目后,观察解决方案资源管理器,会发现比普通C++项目多了几个特殊文件:
Package.appxmanifest:应用清单文件pch.h:预编译头文件<projectname>.winmd文件我建议立即做个小改动:右键项目→属性→C/C++→预编译头,把"预编译头文件"从"pch.h"改成"stdafx.h"。这不是必须的,但能避免后续引用第三方库时的兼容性问题。记得把源文件里的include语句也同步修改。
第一次看到WinRT代码时,那些winrt::开头的类型让我很困惑。其实这就是微软设计的现代C++包装器,把传统的COM接口转换成了符合C++17标准的语法。举个例子,传统的BSTR字符串现在变成了winrt::hstring,智能指针变成了winrt::com_ptr。
最核心的初始化代码长这样:
cpp复制#include <winrt/Windows.Foundation.h>
int main()
{
winrt::init_apartment(); // 初始化COM和WinRT运行时
// 你的代码...
winrt::uninit_apartment(); // 清理资源
}
这个init_apartment()特别重要,它做了三件事:
忘记调用它会导致各种神秘崩溃。我有次调试两小时才发现是因为漏了这行代码。
让我们写个真正有用的功能——获取系统版本信息。在pch.h中添加这些头文件:
cpp复制#include <winrt/Windows.System.Profile.h>
#include <winrt/Windows.Foundation.Collections.h>
然后在main.cpp写实现:
cpp复制using namespace winrt;
using namespace Windows::System::Profile;
void PrintSystemInfo()
{
auto analytics = AnalyticsInfo::VersionInfo();
auto deviceFamily = analytics.DeviceFamily();
auto version = analytics.DeviceFamilyVersion();
uint64_t versionInt = std::stoull(version.c_str());
uint16_t major = (versionInt >> 48) & 0xFFFF;
uint16_t minor = (versionInt >> 32) & 0xFFFF;
std::wcout << L"Device: " << deviceFamily.c_str() << std::endl;
std::wcout << L"Version: " << major << L"." << minor << std::endl;
}
这段代码有几个关键点:
运行后会输出类似"Device: Windows.Desktop Version: 10.0"的信息。我在实际项目中发现,这个API比传统的GetVersionEx更可靠,特别是在Windows 11上。
WinRT的文件API比传统Win32 API更安全。让我们实现一个文件复制功能,首先添加头文件:
cpp复制#include <winrt/Windows.Storage.h>
#include <winrt/Windows.Foundation.h>
实现函数如下:
cpp复制using namespace Windows::Storage;
IAsyncAction CopyFileAsync(winrt::hstring source, winrt::hstring dest)
{
auto sourceFile = co_await StorageFile::GetFileFromPathAsync(source);
auto destFolder = co_await StorageFolder::GetFolderFromPathAsync(
std::wstring(dest).substr(0, dest.rfind('\\')));
co_await sourceFile.CopyAsync(destFolder,
std::wstring(dest).substr(dest.rfind('\\') + 1),
NameCollisionOption::ReplaceExisting);
}
注意几个细节:
调用这个函数需要改造main函数:
cpp复制int main()
{
winrt::init_apartment();
// 同步等待异步操作完成
CopyFileAsync(L"C:\\test.txt", L"D:\\backup\\test.txt").get();
winrt::uninit_apartment();
}
这种异步编程模式刚开始会不习惯,但确实是现代Windows开发的趋势。我在处理大文件时,发现WinRT的异步API比同步API性能好很多。
第一次使用WinRT难免会遇到各种问题,这里分享几个典型错误:
错误1:LNK2019 无法解析的外部符号
code复制error LNK2019: 无法解析的外部符号 WINRT_CanUnloadNow
解决方法是在pch.h最前面添加:
cpp复制#pragma comment(lib, "windowsapp")
错误2:C++17特性不支持
code复制error C2760: 语法错误: 意外的令牌
检查项目属性→C/C++→语言→C++语言标准,必须选择"ISO C++17标准"
错误3:异步操作崩溃
异步调用前必须确保调用了init_apartment(),且要用co_await或.get()等待操作完成。
我遇到过最棘手的问题是版本冲突。如果同时引用了传统COM组件和WinRT组件,可能会因为线程模型不匹配导致死锁。这时候需要在init_apartment()里明确指定线程模型:
cpp复制winrt::init_apartment(winrt::apartment_type::single_threaded);
当我们需要在C++/WinRT项目中添加自定义运行时类时,步骤比较特殊。首先添加一个Midl文件(.idl):
idl复制namespace MyComponent
{
runtimeclass MyClass
{
MyClass();
Int32 MyProperty;
void MyMethod(String value);
}
}
编译后会生成对应的.h和.cpp文件。这里有个坑:生成的源文件会放在Generated Files\winrt目录下,默认不在项目里显示。需要在解决方案资源管理器顶部点击"显示所有文件"按钮才能看到。
实现类时需要继承生成的类模板:
cpp复制struct MyClass : MyClassT<MyClass>
{
MyClass() = default;
int32_t MyProperty();
void MyProperty(int32_t value);
void MyMethod(winrt::hstring const& value);
};
注册组件需要在pch.h添加:
cpp复制#include <winrt/MyComponent.h>
这种开发模式刚开始会觉得绕,但习惯后会发现它比传统COM开发简洁很多。我在一个硬件驱动项目中使用这种模式,代码量比之前的ATL实现减少了40%。