原文地址:modernwindows.wordpress.com/2017/11/15/…
原文作者:modernwindows.wordpress.com/
发布时间:2017年11月15日
11月初,C++/WinRT首次在Windows SDK(17025)中发布。该SDK包含了C++/WinRT头文件,所以你不必先从GitHub上获取它们。取而代之的是,你可以简单地在任何C++项目中包含相应的头文件,并开始使用Windows API编写现代C++代码。
我很高兴地宣布,今天更新的Windows SDK (17035)现在也包含了C++/WinRT编译器(cppwinrt.exe)。虽然在大多数情况下,你不需要自己运行编译器,但如果你需要使用第三方WinRT组件,它可以派上用场,并且需要用C++/WinRT编写自己的WinRT组件。你也可以用它为自己的一组.winmd文件制作自定义投影。
Scott Jones在今年早些时候的CppCon上首次演示了这一点。你可以在这里观看我们的整个CppCon 2017演讲。
最终,我们将把C++/WinRT完全集成到Visual Studio中,但在那之前,你必须做一点工作来让它顺利集成。上次我向你展示了如何从与Visual C++ 2017项目开始使用C++/WinRT头文件。我还准备了一套示例项目,你可以将其作为一个起点。你可以在这里找到它们。
这些示例与今天更新的Windows SDK一起工作,并且有各种示例可以让你开始使用桌面以及商店应用程序。有的样本说明了各种互操作的场景,甚至还有一个完整的编写WinRT组件的例子。
不过,我知道很多开发者还是急于尝试C++/WinRT编译器本身。让我告诉你如何开始,但请记住,这仍然被认为是实验性的,可能会发生变化。不过,我们非常乐意在GitHub问题页面上回答你的任何问题。如果你想跟随我们,请打开一个开发者命令提示符。我通常使用VS 2017的x86 Native Tools命令提示符。假设你已经安装了Windows SDK,你应该在路径中找到cppwinrt.exe。你可以通过使用where命令进行双重检查。它应该看起来像这样。
> where cppwinrt
C:\Program Files (x86)\Windows Kits\10\bin\10.0.17035.0\x86\cppwinrt.exe
你也可以运行cppwinrt命令本身来探索一些可用的功能和选项。
> cppwinrt
C++/WinRT v1.0.170906.1
Copyright (c) 2017 Microsoft Corporation. All rights reserved.
cppwinrt.exe [options...]
Options:
-in <spec> Winmd files to include in projection
-ref <spec> Winmd files to reference from projection
-out <path> Location of generated projection files
-component Generate component implementation source files
-filter <prefix> One or more prefixes to include in component
-name <name> Assume this component name to shorten file names
-natvis Generate a natvis script for debug visualization
-verbose Show detailed progress information
-overwrite Overwrite existing component projection files
-help Show detailed help with examples
@<path> Response file containing command line options
Where <spec> is one or more of:
path Path to winmd file or recursively scanned folder
local Local %WinDir%\System32\WinMetadata folder
10.0.15063.0 Specific version of Windows SDK
正如你从看选项中可以想象的那样,我经常把cppwinrt称为编译器,因为它读取一些输入文件集,并写入相应的输出文件集。让我们从一个为使用Windows Insider构建的开发者量身定做的具体例子开始。让我们想象一下,你得到了一个新的构建,并立即想尝试一些新的功能,但与之匹配的Windows SDK构建却莫名其妙地延迟了。我知道这种情况从来没有发生过,但是玩玩看。没问题,你可以使用本地选项,并为你碰巧运行的Windows版本制作一个投影。
> cppwinrt -in local
你应该会发现刚刚创建了一个名为winrt的本地文件夹,其中包括不少头文件。你可以通过使用 verbose 选项来获得更多的可见性。
> cppwinrt -in local -verbose
in: C:\WINDOWS\System32\WinMetadata\Windows.ApplicationModel.winmd
in: C:\WINDOWS\System32\WinMetadata\Windows.Data.winmd
in: C:\WINDOWS\System32\WinMetadata\Windows.Devices.winmd
in: C:\WINDOWS\System32\WinMetadata\Windows.Foundation.winmd
in: C:\WINDOWS\System32\WinMetadata\Windows.Gaming.winmd
in: C:\WINDOWS\System32\WinMetadata\Windows.Globalization.winmd
in: C:\WINDOWS\System32\WinMetadata\Windows.Graphics.winmd
in: C:\WINDOWS\System32\WinMetadata\Windows.Management.winmd
in: C:\WINDOWS\System32\WinMetadata\Windows.Media.winmd
in: C:\WINDOWS\System32\WinMetadata\Windows.Networking.winmd
in: C:\WINDOWS\System32\WinMetadata\Windows.Perception.winmd
in: C:\WINDOWS\System32\WinMetadata\Windows.Security.winmd
in: C:\WINDOWS\System32\WinMetadata\Windows.Services.winmd
in: C:\WINDOWS\System32\WinMetadata\Windows.Storage.winmd
in: C:\WINDOWS\System32\WinMetadata\Windows.System.winmd
in: C:\WINDOWS\System32\WinMetadata\Windows.UI.Xaml.winmd
in: C:\WINDOWS\System32\WinMetadata\Windows.UI.winmd
in: C:\WINDOWS\System32\WinMetadata\Windows.Web.winmd
out: C:\sample
time: 2890ms
现在你可以清楚地看到本地输入代表什么,以及输出在哪里被写入,在本例中是写入本地文件夹。不幸的是,SDK中的cppwinrt.exe版本比我们的内部构建晚了几个星期。更多的编译器的最新版本在三分之一的时间内就完成了编译(由于一个coroutine优化器的bug)。这也意味着在这个版本的编译器中有一些已知的bug,但如果你遇到任何困难,请告诉我们。
现在你已经创建了一个投影,你可以像使用Windows SDK中的投影一样使用它。当然,请记住,你现在可以访问各种API,这些API可能会发生变化。不过,你还是可以用一个简单的控制台应用程序快速测试它的工作情况。
> type sample.cpp
#pragma comment(lib, "windowsapp")
#include "winrt/Windows.Foundation.Metadata.h"
using namespace winrt;
using namespace Windows::Foundation::Metadata;
int main()
{
init_apartment();
bool const rs4 = ApiInformation::IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 6);
printf("Am I running on Redstone 4? %s\n", rs4 ? "Yes!" : "No. :(");
}
然后简单地使用支持异常处理和C++17的C++编译器。
C:\sdk>cl /EHsc /std:c++17 sample.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.11.25547 for x86
Copyright (C) Microsoft Corporation. All rights reserved.
sample.cpp
Microsoft (R) Incremental Linker Version 14.11.25547.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:sample.exe
sample.obj
然后,示例应用程序将告诉您是否运行在Windows的Redstone 4版本上。
> sample.exe
Am I running on Redstone 4? Yes!
当然,您可以使用 cppwinrt 来瞄准更多的本地机器。更典型的情况是,您需要为 Windows SDK 的特定版本创建投影。例如,以下是您如何为Fall Creators Update (Redstone 3)创建投影。
> cppwinrt -in 10.0.16299.0 -verbose
当然,前提是你已经安装了16299版本的Windows SDK。我把输出结果给你留着,但从啰嗦的输出中应该可以看出它是用哪一组.winmd文件来创建投影的。
那第三方组件呢?想象一下,某个开发者给你发了一个DLL以及一个.winmd文件,描述了一个你想利用的组件。我知道你会问,答案是否定的,这在今天的Win2D中是行不通的,因为Windows SDK中附带的cppwinrt.exe版本有一个bug,妨碍了你的使用。我们的内部构建已经支持Win2D有一段时间了,我被告知下一个Windows SDK会有修复,希望在本月晚些时候。现在考虑一个不同的组件。
> dir /b Component.*
Component.dll
Component.winmd
这就是该组件的作者所需要的全部内容。现在你可以为这个组件创建一个投影,如下所示。
> cppwinrt -in Component.winmd -ref local -out Component
在前面的例子中,输入的是Windows SDK的某个版本,现在输入的是代表WinRT组件的特定.winmd。现在输入的是代表WinRT组件的特定.winmd。ref选项告诉编译器,哪一组底层的.winmds代表了这个组件所指的平台。例如,它可能有一个Windows自己定义的某种类型的参数,或者可能实现了一个像Windows.Foundation.IStringable这样的通用接口。cppwinrt编译器需要知道如何解析这些类型,这就是ref选项的作用。最后,组件的头文件应该驻留在自己的文件夹中,C++项目可以根据需要简单地包含各种文件夹。
现在我们换个角度,在Visual Studio里面看一个更具体的例子。你可以先在Visual Studio中打开Component解决方案。它包括两个项目,一个是用C++/WinRT编写的商店应用,以及一个同样用C++/WinRT编写的WinRT组件。当然,如果你愿意,你也可以从C#应用程序中消耗组件。如果需要的话,你也可以在Desktop Bridge的帮助下使用一个桌面应用程序。
如果你看看组件的IDL文件,你会注意到一个简单的WinRT运行时类的定义。C++项目调用MIDL编译器来产生上述的.winmd文件。然后,它还调用cppwinrt来创建在C++/WinRT中代表组件的头文件,但它更进一步,使用组件选项也产生任何需要的脚手架,以实现现代C++的运行时类。让我们改变一下IDL来看看这个操作。我将在IButton2接口中添加一个名为Hide的方法。
[version(2.0), uuid(d3235252-4081-4cc8-b0e0-8c7691813845), exclusiveto(Button)]
interface IButton2 : IInspectable
{
HRESULT Show();
HRESULT Hide(); // <-- add this!
};
构建项目会使MIDL生成一个新的.winmd文件,这将使cppwinrt更新头文件和实现脚手架。你所要做的就是在Button类中添加Hide方法。在Button.h文件中,你可以添加隐藏方法的声明。
struct Button : ButtonT<Button>
{
Button() = default;
hstring Text();
void Show();
hstring ToString();
void Hide(); // <-- add this!
};
就是那个ButtonT类模板,cppwinrt更新了Hide方法的期望。然后在Button.cpp文件里面可以添加定义。
void Button::Hide()
{
}
而这就是全部。在幕后,C++/WinRT做了大量的工作,以确保这种现代的C++转化为Windows Runtime所需的ABI的有效实现,但确保你可以坚持使用现代或习惯的C++作为你实现的一部分。
C++/WinRT对你的代码和生成的代码进行了明确的区分。例如,Button.h文件可能最初是由cppwinrt生成的,但现在是你要维护和扩展的代码。另一方面,它所包含的Button.g.h文件总是由cppwinrt再生,并且在文件的顶部有警告提醒你。当然,如果你在IDL中定义了一个全新的运行时类,而这个类一开始并不存在.h和.cpp文件,cppwinrt会继续创建这些文件来帮助你入门。
正如我所提到的,我们正忙于改进C++/WinRT,你应该期待在接下来的几个月里定期更新和改进。直到下一次,我希望你喜欢使用C++/WinRT和cppwinrt编译器。