目录
通过 C++/WinRT 将值装箱到 IInspectable
前言
本文是系列教程的第二篇,推荐新手从第一篇开始阅读体验更佳。本文是根据官方教程整理的更精简版本的教程,并优化了部分翻译问题。
字符串
利用 C++/WinRT,你可以使用 C++ 标准库宽字符串类型(如 std::wstring)调用 Windows 运行时 API(注:不要使用窄字符串类型,例如 std::string)。 C++/WinRT 使用 winrt::hstring 自定义字符串类型(在 C++/WinRT 基础库 %WindowsSdkDir%Include\<WindowsTargetPlatformVersion>\cppwinrt\winrt\base.h
中定义) 如果你要自己定义 API,则很可能需要了解 hstring。 winrt::hstring 利用 std::wstring_view 提供了可转换性,以实现 std::basic_string_view 应有的互操作性。
hstring 具有可用且无需关注的转换构造函数。 下面是一个代码示例,展示了如何从宽字符串参数、从宽字符串视图和从 std::wstring 创建 Uri。
#include <winrt/Windows.Foundation.h>
#include <string_view>
using namespace winrt;
using namespace Windows::Foundation;
int main()
{
using namespace std::literals;
winrt::init_apartment();
// You can make a Uri from a wide string literal.
Uri contosoUri{ L"http://www.contoso.com" };
// Or from a wide string view.
Uri contosoSVUri{ L"http://www.contoso.com"sv };
// Or from a std::wstring.
std::wstring wideString{ L"http://www.adventure-works.com" };
Uri awUri{ wideString };
}
常用的属性访问器 Uri::Domain 也返回hstring类型 。
public:
winrt::hstring Domain();
同时由于 hstring 的 std::wstring_view 的转换运算符,返回值可以自动转换。示例:
// Access a property of type hstring, via a conversion operator to a standard type.
std::wstring domainWstring{ contosoUri.Domain() }; // L"contoso.com"
domainWstring = awUri.Domain(); // L"adventure-works.com"
// Or, you can choose to keep the hstring unconverted.
hstring domainHstring{ contosoUri.Domain() }; // L"contoso.com"
domainHstring = awUri.Domain(); // L"adventure-works.com"
你可以使用 hstring::c_str function 函数从 hstring 获取标准宽字符串(正如你可以从 std::wstring 获取一样)。
#include <iostream>
std::wcout << tostringHstring.c_str() << std::endl;
hstring 是一个范围,因此你可以将其与基于范围的 for
或与 std::for_each
一起使用。 它还提供了一个比较运算符,用于自然、高效地与它在 C++ 标准库中的对应项进行比较。
很多 C++ 库使用了 std::string,并且仅与 UTF-8 文本配合。 winrt::to_string、winrt::to_hstring 等方法可以用于来回转换。
WINRT_ASSERT
是宏定义,扩展到 _ASSERTE。功能是调试模式进行值比较。
winrt::hstring w{ L"Hello, World!" };
std::string c = winrt::to_string(w);
WINRT_ASSERT(c == "Hello, World!");
w = winrt::to_hstring(c);
WINRT_ASSERT(w == L"Hello, World!");
你可能会注意到有些代码在逻辑上应该接受 winrt::hstring 的 C++/WinRT 输入参数,但是实际上需要 winrt::param::hstring。
param 命名空间包含一组类型,专用于优化输入参数以自然地绑定到 C++ 标准库类型,以及避免副本和其他低效率现象。 你不应直接使用这些类型。 如果你要对自己的函数使用优化,则应使用 std::wstring_view。 另请参阅将参数传递到 ABI 边界。
用于字符串格式化的一个选择是 std::wostringstream。
#include <sstream>
#include <winrt/Windows.UI.Input.h>
#include <winrt/Windows.UI.Xaml.Input.h>
...
void MainPage::OnPointerPressed(winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e)
{
winrt::Windows::Foundation::Point const point{ e.GetCurrentPoint(nullptr).Position() };
std::wostringstream wostringstream;
wostringstream << L"Pointer pressed at (" << point.X << L"," << point.Y << L")" << std::endl;
::OutputDebugString(wostringstream.str().c_str());
}
这个示例可以格式化的输出调试信息。
可以将值传递给设置函数,以这种方式设置属性。例如:
// 正确
myTextBlock.Text(L"Hello!");
// 错误
myTextBlock.Text() = L"Hello!";
标准 C++ 数据类型和 C++/WinRT
初始值列表 (std::initializer_list) 是 C++ 标准库构造。例如:可以使用初始值列表来调用 DataWriter::WriteBytes。
#include <winrt/Windows.Storage.Streams.h>
using namespace winrt::Windows::Storage::Streams;
int main()
{
winrt::init_apartment();
InMemoryRandomAccessStream stream;
DataWriter dataWriter{stream};
dataWriter.WriteBytes({ 99, 98, 97 }); // the initializer list is converted to a winrt::array_view before being passed to WriteBytes.
}
这个过程中,DataWriter::WriteBytes 方法先选取一个 winrt::array_view 类型的参数。然后使用winrt::array_view 初始值列表构造函数。
void WriteBytes(winrt::array_view<uint8_t const> value) const;
template <typename T> winrt::array_view(std::initializer_list<T> value) noexcept;
winrt::array_view 还有来自 std::vector 和 std::array 的转换构造函数。
template <typename C, size_type N> winrt::array_view(std::array<C, N>& value) noexcept
template <typename C> winrt::array_view(std::vector<C>& vectorValue) noexcept
因此,可以改为使用 std::vector 调用 DataWriter::WriteBytes。
std::vector<byte> theVector{ 99, 98, 97 };
dataWriter.WriteBytes(theVector); // theVector is converted to a winrt::array_view before being passed to WriteBytes.
通过 C++/WinRT 将值装箱到 IInspectable
WinRT 不仅可对标量值进行装箱和取消装箱,而可使用 winrt::box_value 和 winrt::unbox_value 函数对大多数类型的数组进行这类操作(枚举数组除外) 。 但是只能使用 winrt::unbox_value_or 函数对标量值取消装箱。
向任何运行时类的实例传递需要 IInspectable 的函数。 但是你无法将标量值(如数值或文本值)直接传递到此类函数,也不能直接传递数组。 相反,标量或数组值需要封装到引用类对象内。 该封装过程称为对值进行装箱。
不管将什么类型传递给 Windows 运行时 API,都可以对该类型进行装箱和取消装箱操作。
C++/WinRT 提供了 winrt::box_value 函数,该函数采用标量或数组值,并将装箱的值返回到 IInspectable中 。 对于取消 IInspectable 装箱并返回到标量或数组值,提供 winrt::unbox_value 函数 。 对于取消 IInspectable 装箱并返回到标量值,还提供 winrt::unbox_value_or 函数 。
取消值装箱的示例
LaunchActivatedEventArgs::Arguments 访问器函数返回 winrt::hstring,这是一个标量值。 我们可以将该 hstring 值进行装箱并将其传递到需要 IInspectable 的函数,如下所示。
void App::OnLaunched(LaunchActivatedEventArgs const& e)
{
...
rootFrame.Navigate(winrt::xaml_typename<BlankApp1::MainPage>(), winrt::box_value(e.Arguments()));
...
}
取消 IInspectable 装箱的示例
在自己的需要 IInspectable 的函数中,可以使用 winrt::unbox_value 取消装箱,也可以使用 winrt::unbox_value_or 通过默认值取消装箱。 还可以使用 try_as 取消装箱到 std::optional。
void Unbox(winrt::Windows::Foundation::IInspectable const& object)
{
hstring hstringValue = unbox_value<hstring>(object); // Throws if object is not a boxed string.
hstringValue = unbox_value_or<hstring>(object, L"Default"); // Returns L"Default" if object is not a boxed string.
float floatValue = unbox_value_or<float>(object, 0.f); // Returns 0.0 if object is not a boxed float.
std::optional<int> optionalInt = object.try_as<int>(); // Returns std::nullopt if object is not a boxed int.
}
确定装箱值的类型
如果收到装箱值但不确定它所包含的类型(需要知道类型以便取消装箱),可以查询装箱值的 IPropertyValue 接口,然后对其调用 Type。 下面是代码示例。
float pi = 3.14f;
auto piInspectable = winrt::box_value(pi);
auto piPropertyValue = piInspectable.as<winrt::Windows::Foundation::IPropertyValue>();
WINRT_ASSERT(piPropertyValue.Type() == winrt::Windows::Foundation::PropertyType::Single);