在Windows平台上进行现代应用开发时,WinRT(Windows Runtime)是一个绕不开的核心技术框架。作为微软推出的新一代API集合,它提供了对操作系统功能的统一访问方式。与传统的Win32 API相比,WinRT采用了更现代的COM实现方式,通过C++/WinRT这种标准C++语言投影,开发者可以更高效地调用系统功能。
要开始WinRT开发,首先需要确保开发环境正确配置。以下是关键步骤:
Visual Studio版本选择:必须使用Visual Studio 2019或更高版本,并确保安装"使用C++的桌面开发"和"通用Windows平台开发"工作负载。特别要注意勾选"C++/WinRT"可选组件。
Windows SDK版本管理:项目属性中需要设置正确的目标平台版本(建议使用Windows 10, version 2004或更高版本),同时确保平台工具集设置为"Visual Studio 2019 (v142)"或更新版本。
项目属性配置:
$(WindowsSDK_IncludePath)windowsapp.lib注意:在混合使用WinRT和传统Win32/DirectX代码时,头文件包含顺序非常重要。必须按照先Windows头文件,再C++/WinRT头文件,最后Interop头文件的顺序包含,否则可能导致编译错误。
WinRT编程有几个关键概念需要理解:
COM的现代实现:WinRT基于COM但简化了引用计数管理,C++/WinRT通过智能指针自动处理资源释放。
异步编程模型:WinRT广泛使用协程和IAsyncAction等异步接口,与传统的回调方式相比代码更清晰。
元数据驱动:通过.winmd文件提供类型信息,支持跨语言调用。
命名空间组织:功能按Windows.[Area].[SubArea]方式组织,如Windows.Graphics.Capture。
在示例代码中,我们特别需要注意winrt::init_apartment()的调用。这个函数初始化COM运行时环境,参数apartment_type::multi_threaded表示使用多线程单元(MTA),这是与DirectX交互时的推荐设置。
Windows.Graphics.Capture命名空间提供了一套强大的屏幕捕获API,可以高效地捕获应用窗口或屏幕区域的内容。与传统的BitBlt或DXGI方式相比,这套API更高效且支持现代图形功能。
在调用任何Windows.Graphics.Capture API前,必须先创建DispatcherQueue。这是因为这些API需要在线程上运行消息循环来处理图形事件。示例中的CreateDispatcherQueue函数实现了这一关键步骤:
cpp复制void CreateDispatcherQueue() {
DispatcherQueueOptions options{
sizeof(DispatcherQueueOptions),
DQTYPE_THREAD_CURRENT, // 在当前线程创建
DQTAT_COM_NONE // 不使用COM STA,避免死锁
};
ABI::Windows::System::IDispatcherQueueController* controller;
HRESULT hr = CreateDispatcherQueueController(options, &controller);
if (FAILED(hr)) {
throw hresult_error(hr, L"Failed to create DispatcherQueue");
}
}
这里有几个关键点:
DQTYPE_THREAD_CURRENT表示在当前线程创建DispatcherQueue,而不是新建线程。DQTAT_COM_NONE避免了COM单线程单元(STA)可能导致的死锁问题,这在图形密集型应用中尤为重要。图形捕获需要与Direct3D设备配合工作。示例中展示了如何创建D3D11设备并与WinRT互操作:
cpp复制// 创建标准D3D11设备
com_ptr<ID3D11Device> d3d_device;
D3D_FEATURE_LEVEL feature_level;
check_hresult(D3D11CreateDevice(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
D3D11_CREATE_DEVICE_BGRA_SUPPORT, // 必须支持BGRA格式
nullptr, 0,
D3D11_SDK_VERSION,
d3d_device.put(),
&feature_level,
nullptr));
// 转换为DXGI设备接口
com_ptr<IDXGIDevice> dxgi_device = d3d_device.as<IDXGIDevice>();
// 创建WinRT可识别的Direct3D设备
com_ptr<IInspectable> device_inspectable;
check_hresult(CreateDirect3D11DeviceFromDXGIDevice(dxgi_device.get(),
device_inspectable.put()));
// 转换为WinRT接口
auto winrt_device = device_inspectable.as<winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice>();
这段代码有几个技术要点:
D3D11_CREATE_DEVICE_BGRA_SUPPORT标志是必须的,因为Windows图形子系统使用BGRA格式。check_hresult是C++/WinRT提供的辅助函数,用于检查HRESULT并在失败时抛出异常。CreateDirect3D11DeviceFromDXGIDevice是关键的互操作函数,它将DXGI设备转换为WinRT可识别的形式。要捕获特定窗口内容,首先需要获取窗口句柄(HWND)。示例中使用FindWindow查找记事本窗口:
cpp复制HWND hwnd = FindWindow(L"Notepad", nullptr);
if (!hwnd) {
std::wcout << L"请先打开记事本!" << std::endl;
return 1;
}
找到窗口后,通过IGraphicsCaptureItemInterop接口创建GraphicsCaptureItem:
cpp复制auto activation_factory = get_activation_factory<winrt::Windows::Graphics::Capture::GraphicsCaptureItem>();
auto interop_factory = activation_factory.as<IGraphicsCaptureItemInterop>();
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = nullptr;
check_hresult(interop_factory->CreateForWindow(
hwnd,
guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
put_abi(item)));
这里使用了C++/WinRT的工厂获取模式:
get_activation_factory获取WinRT类的激活工厂as<>转换为所需的Interop接口CreateForWindow最终创建捕获项创建捕获项后,需要设置帧池(FramePool)来接收捕获的帧:
cpp复制auto frame_pool = winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::Create(
winrt_device, // 之前创建的D3D设备
winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized, // BGRA格式
2, // 帧缓冲数量
item.Size()); // 初始大小
帧池相当于一个缓冲队列,参数2表示双缓冲。最后创建捕获会话并开始捕获:
cpp复制auto session = frame_pool.CreateCaptureSession(item);
session.StartCapture(); // 开始捕获
在实际应用中,还需要注册FrameArrived事件来处理捕获到的帧,示例中省略了这部分以保持简洁。
示例代码中包含了一个有趣的附加功能——虚拟机检测。这在图形应用中有时很有用,因为虚拟机的图形性能通常有限。
代码使用CPUID指令来检测虚拟机环境:
cpp复制bool isVMware() {
std::vector<int> cpuinfo(4);
__cpuid(cpuinfo.data(), 0x40000000); // 虚拟机特定CPUID叶
std::string vendor((char*)&cpuinfo[1], (char*)&cpuinfo[3]);
vendor += std::string((char*)&cpuinfo[2], 12);
return vendor.find("VMwareVMware") != std::string::npos ||
vendor.find("KVMKVMKVM") != std::string::npos ||
vendor.find("Microsoft Hv") != std::string::npos ||
vendor.find("XenVMMXenVMM") != std::string::npos ||
vendor.find("prl hyperv") != std::string::npos ||
vendor.find("VBoxVBoxVBox") != std::string::npos;
}
这段代码的工作原理是:
更通用的虚拟机检测方法是检查CPUID的Hypervisor位:
cpp复制int cpuInfo[4] = {};
__cpuid(cpuInfo, 1);
bool is_vm = (cpuInfo[2] & (1 << 31)) != 0; // 检查第31位
这个位由CPU硬件设置,表示代码运行在虚拟化环境中。不过它不能区分具体的虚拟化技术。
实际开发中需要注意:虚拟机检测技术并非100%可靠,且随着虚拟化技术的发展,有些环境可能隐藏这些特征。这类功能通常用于优化或调试,不应作为安全机制依赖。
未定义符号错误:
头文件顺序问题:
DispatcherQueue创建失败:
捕获会话启动失败:
常见的HRESULT错误码:
帧处理延迟:
内存管理:
虚拟环境适配: