Skip to content

WinUI 3 WinRT C++ 开发完整教程 - 第四部分:基础API与类型系统深度解析

WinRT 类型系统详解

基础类型映射

WinRT 定义了一套跨语言的类型系统。理解这些类型对于有效使用 C++/WinRT 至关重要:

WinRT 类型C++/WinRT 类型C++ 原生类型说明
Booleanboolbool布尔值
Int8int8_tsigned char8位有符号整数
UInt8uint8_tunsigned char8位无符号整数
Int16int16_tshort16位有符号整数
UInt16uint16_tunsigned short16位无符号整数
Int32int32_tint32位有符号整数
UInt32uint32_tunsigned int32位无符号整数
Int64int64_tlong long64位有符号整数
UInt64uint64_tunsigned long long64位无符号整数
Singlefloatfloat32位浮点数
Doubledoubledouble64位浮点数
Char16char16_twchar_tUTF-16字符
Stringwinrt::hstring-不可变字符串
Objectwinrt::Windows::Foundation::IInspectable-基础对象类型

深入理解 hstring

hstring 是 WinRT 字符串的核心类型,让我们深入了解它:

cpp
// hstring 的内部结构(简化版)
class hstring
{
private:
    HSTRING m_handle;  // 内部句柄

public:
    // 构造函数详解
    hstring() noexcept;                           // 默认构造:空字符串
    hstring(std::wstring_view value);             // 从 wstring_view 构造
    hstring(wchar_t const* value);                // 从 C 风格字符串构造
    hstring(wchar_t const* first, wchar_t const* last); // 从范围构造
    
    // 核心方法
    wchar_t const* c_str() const noexcept;       // 获取 C 风格字符串
    uint32_t size() const noexcept;              // 获取字符数
    bool empty() const noexcept;                 // 检查是否为空
    
    // 操作符重载
    hstring& operator=(hstring&& other) noexcept;
    bool operator==(hstring const& other) const noexcept;
    bool operator<(hstring const& other) const noexcept;
};

hstring 的高级用法

cpp
void DemonstrateHString()
{
    // 1. 创建 hstring 的各种方式
    hstring empty{};                              // 空字符串
    hstring fromLiteral{L"Hello World"};         // 从字面量
    hstring fromStdString{std::wstring{L"Test"}}; // 从 std::wstring
    
    // 2. 性能优化的创建方式
    hstring efficient = winrt::to_hstring(42);   // 数字转字符串
    hstring formatted = std::format(L"Number: {}", 42); // C++20 格式化
    
    // 3. hstring 与 std::wstring 的转换
    std::wstring stdStr = std::wstring{fromLiteral};
    hstring backToHString{stdStr};
    
    // 4. 字符串操作
    bool isEmpty = fromLiteral.empty();
    size_t length = fromLiteral.size();
    wchar_t const* cStr = fromLiteral.c_str();
    
    // 5. 比较操作
    bool equal = (fromLiteral == hstring{L"Hello World"});
    bool less = (hstring{L"A"} < hstring{L"B"});
    
    OutputDebugStringW((L"hstring length: " + winrt::to_hstring(length)).c_str());
}

hstring 的内存管理原理

cpp
// hstring 使用 Windows String API (HSTRING)
// 内部实现参考伪代码:
class hstring_implementation
{
private:
    HSTRING m_string;
    
public:
    hstring_implementation(wchar_t const* value)
    {
        // 使用 Windows Runtime String API
        HRESULT hr = WindowsCreateString(value, 
                                        static_cast<UINT32>(wcslen(value)), 
                                        &m_string);
        winrt::check_hresult(hr);
    }
    
    ~hstring_implementation()
    {
        if (m_string)
        {
            WindowsDeleteString(m_string);  // 自动释放
        }
    }
    
    // 写时复制 (Copy-on-Write) 优化
    // 多个 hstring 可以共享同一块内存
};

IInspectable 接口详解

IInspectable 是 WinRT 对象的基础接口,相当于 .NET 中的 Object

cpp
// IInspectable 接口定义(简化版)
interface IInspectable : IUnknown
{
    // 从 IUnknown 继承的方法
    virtual HRESULT QueryInterface(REFIID riid, void** ppv) = 0;
    virtual ULONG AddRef() = 0;
    virtual ULONG Release() = 0;
    
    // IInspectable 特有的方法
    virtual HRESULT GetIids(ULONG* iidCount, IID** iids) = 0;        // 获取支持的接口
    virtual HRESULT GetRuntimeClassName(HSTRING* className) = 0;      // 获取运行时类名
    virtual HRESULT GetTrustLevel(TrustLevel* trustLevel) = 0;        // 获取信任级别
};

实际使用中的 IInspectable

cpp
void DemonstrateIInspectable()
{
    // 1. 创建一个 WinRT 对象
    auto button = winrt::Microsoft::UI::Xaml::Controls::Button();
    
    // 2. 获取 IInspectable 接口
    winrt::Windows::Foundation::IInspectable inspectable = button;
    
    // 3. 查询接口(安全转换)
    auto control = inspectable.try_as<winrt::Microsoft::UI::Xaml::Controls::Control>();
    if (control)
    {
        // 安全使用 Control 接口
        control.FontSize(16.0);
    }
    
    // 4. 强制转换(如果失败会抛出异常)
    auto frameworkElement = inspectable.as<winrt::Microsoft::UI::Xaml::FrameworkElement>();
    
    // 5. 检查对象类型
    auto typeName = winrt::get_class_name(inspectable);
    OutputDebugStringW((L"对象类型: " + typeName).c_str());
}

基础API深入讲解

Windows.Foundation 命名空间

这是 WinRT 的核心命名空间,包含最基础的类型和接口:

1. Uri 类详解

cpp
// Uri 类的核心用法
void DemonstrateUri()
{
    // 1. 创建 Uri 对象
    auto uri = winrt::Windows::Foundation::Uri{L"https://www.example.com/path?query=value#fragment"};
    
    // 2. 访问 Uri 组件
    hstring scheme = uri.SchemeName();        // "https"
    hstring host = uri.Host();                // "www.example.com"
    hstring path = uri.Path();                // "/path"
    hstring query = uri.Query();              // "?query=value"
    hstring fragment = uri.Fragment();        // "#fragment"
    int32_t port = uri.Port();                // 443 (HTTPS 默认端口)
    
    // 3. 完整 Uri 字符串
    hstring absoluteUri = uri.AbsoluteUri();  // 完整 URL
    
    // 4. Uri 验证
    bool isAbsolute = uri.Absolute();         // true
    
    // 5. 相对 Uri 处理
    auto baseUri = winrt::Windows::Foundation::Uri{L"https://www.example.com/"};
    auto relativeUri = winrt::Windows::Foundation::Uri{baseUri, L"subpath/file.html"};
    
    OutputDebugStringW((L"绝对 Uri: " + absoluteUri).c_str());
}

2. DateTime 和 TimeSpan

cpp
// 时间处理详解
void DemonstrateDateTime()
{
    // 1. DateTime 创建
    auto now = winrt::clock::now();                    // 当前时间
    auto epoch = winrt::Windows::Foundation::DateTime{}; // Unix 纪元时间
    
    // 2. TimeSpan 创建
    using namespace std::chrono;
    auto oneSecond = winrt::Windows::Foundation::TimeSpan{seconds(1)};
    auto oneMinute = winrt::Windows::Foundation::TimeSpan{minutes(1)};
    auto oneHour = winrt::Windows::Foundation::TimeSpan{hours(1)};
    
    // 3. 时间算术运算
    auto futureTime = now + oneHour;
    auto duration = futureTime - now;
    
    // 4. 时间转换
    auto systemTime = winrt::clock::to_sys(now);       // 转换为 std::chrono
    auto fileTime = winrt::clock::to_file_time(now);   // 转换为 FILETIME
    
    // 5. 格式化输出
    auto timeString = winrt::to_hstring(now.time_since_epoch().count());
    OutputDebugStringW((L"当前时间 (ticks): " + timeString).c_str());
}

3. PropertyValue 工厂

PropertyValue 用于创建基础类型的 WinRT 包装器:

cpp
void DemonstratePropertyValue()
{
    // 1. 创建各种类型的 PropertyValue
    auto boolValue = winrt::Windows::Foundation::PropertyValue::CreateBoolean(true);
    auto intValue = winrt::Windows::Foundation::PropertyValue::CreateInt32(42);
    auto doubleValue = winrt::Windows::Foundation::PropertyValue::CreateDouble(3.14159);
    auto stringValue = winrt::Windows::Foundation::PropertyValue::CreateString(L"Hello");
    
    // 2. 创建数组 PropertyValue
    std::array<int32_t, 3> numbers = {1, 2, 3};
    auto arrayValue = winrt::Windows::Foundation::PropertyValue::CreateInt32Array(numbers);
    
    // 3. 检查 PropertyValue 类型
    auto type = boolValue.Type();  // PropertyType::Boolean
    
    // 4. 从 PropertyValue 提取值
    bool extractedBool = boolValue.GetBoolean();
    int32_t extractedInt = intValue.GetInt32();
    
    // 5. 安全类型检查
    if (stringValue.Type() == winrt::Windows::Foundation::PropertyType::String)
    {
        hstring extractedString = stringValue.GetString();
        OutputDebugStringW((L"提取的字符串: " + extractedString).c_str());
    }
}

集合类型与操作

IObservableVector 详解

这是项目中使用的核心集合类型,支持变更通知:

cpp
// 从项目代码中的实际使用
void DemonstrateIObservableVector()
{
    // 1. 创建可观察向量
    auto observableVector = winrt::single_threaded_observable_vector<hstring>();
    
    // 2. 注册变更通知
    observableVector.VectorChanged([](auto&& sender, auto&& args)
    {
        // 处理集合变更事件
        auto action = args.CollectionChange();
        uint32_t index = args.Index();
        
        switch (action)
        {
        case winrt::Windows::Foundation::Collections::CollectionChange::ItemInserted:
            OutputDebugStringW((L"项目已插入,索引: " + winrt::to_hstring(index)).c_str());
            break;
        case winrt::Windows::Foundation::Collections::CollectionChange::ItemRemoved:
            OutputDebugStringW((L"项目已移除,索引: " + winrt::to_hstring(index)).c_str());
            break;
        case winrt::Windows::Foundation::Collections::CollectionChange::ItemChanged:
            OutputDebugStringW((L"项目已更改,索引: " + winrt::to_hstring(index)).c_str());
            break;
        case winrt::Windows::Foundation::Collections::CollectionChange::Reset:
            OutputDebugStringW(L"集合已重置");
            break;
        }
    });
    
    // 3. 操作集合
    observableVector.Append(L"第一项");                // 添加项目
    observableVector.InsertAt(0, L"插入的第一项");      // 插入项目
    observableVector.SetAt(1, L"修改的项目");          // 修改项目
    observableVector.RemoveAt(0);                     // 移除项目
    observableVector.Clear();                         // 清空集合
    
    // 4. 查询集合
    uint32_t size = observableVector.Size();          // 获取大小
    bool isEmpty = (size == 0);                       // 检查是否为空
    
    // 5. 遍历集合
    for (auto&& item : observableVector)
    {
        OutputDebugStringW((L"项目: " + item).c_str());
    }
}

IVector 与 IVectorView

cpp
// 理解只读和可写集合的区别
void DemonstrateVectorTypes()
{
    // 1. 创建可写向量
    auto writableVector = winrt::single_threaded_vector<hstring>();
    writableVector.Append(L"项目1");
    writableVector.Append(L"项目2");
    
    // 2. 获取只读视图
    winrt::Windows::Foundation::Collections::IVectorView<hstring> readOnlyView = 
        writableVector.GetView();
    
    // 3. 只读视图只能查询,不能修改
    uint32_t count = readOnlyView.Size();             // ✓ 可以
    hstring firstItem = readOnlyView.GetAt(0);        // ✓ 可以
    // readOnlyView.Append(L"新项目");                 // ✗ 编译错误
    
    // 4. 查找操作
    uint32_t index;
    bool found = readOnlyView.IndexOf(L"项目1", index);
    if (found)
    {
        OutputDebugStringW((L"找到项目,索引: " + winrt::to_hstring(index)).c_str());
    }
    
    // 5. 转换为标准容器
    std::vector<hstring> stdVector;
    for (uint32_t i = 0; i < readOnlyView.Size(); ++i)
    {
        stdVector.push_back(readOnlyView.GetAt(i));
    }
}

IMap 键值对集合

cpp
// 映射集合的使用
void DemonstrateIMap()
{
    // 1. 创建映射
    auto map = winrt::single_threaded_map<hstring, int32_t>();
    
    // 2. 添加键值对
    map.Insert(L"apple", 1);
    map.Insert(L"banana", 2);
    map.Insert(L"cherry", 3);
    
    // 3. 查询值
    bool hasKey = map.HasKey(L"apple");
    if (hasKey)
    {
        int32_t value = map.Lookup(L"apple");
        OutputDebugStringW((L"apple 的值: " + winrt::to_hstring(value)).c_str());
    }
    
    // 4. 安全查询(不抛出异常)
    int32_t defaultValue = 0;
    int32_t result = map.TryLookup(L"grape").value_or(defaultValue);
    
    // 5. 遍历映射
    for (auto&& pair : map)
    {
        hstring key = pair.Key();
        int32_t value = pair.Value();
        OutputDebugStringW((key + L" = " + winrt::to_hstring(value)).c_str());
    }
    
    // 6. 移除项目
    map.Remove(L"banana");
    map.Clear();
}

事件系统底层原理

事件令牌管理

WinRT 事件使用令牌系统管理订阅:

cpp
// 深入理解事件订阅和取消订阅
class EventDemonstration
{
private:
    std::vector<winrt::event_token> m_eventTokens;  // 存储事件令牌
    
public:
    void DemonstrateEventHandling()
    {
        auto button = winrt::Microsoft::UI::Xaml::Controls::Button();
        
        // 1. 订阅事件并保存令牌
        auto token1 = button.Click([this](auto&& sender, auto&& args)
        {
            OutputDebugStringW(L"处理器 1 被调用\n");
        });
        m_eventTokens.push_back(token1);
        
        // 2. 多个事件处理器
        auto token2 = button.Click([this](auto&& sender, auto&& args)
        {
            OutputDebugStringW(L"处理器 2 被调用\n");
        });
        m_eventTokens.push_back(token2);
        
        // 3. 取消订阅特定处理器
        button.Click(token1);  // 移除第一个处理器
        
        // 4. Lambda 捕获的生命周期管理
        auto weakThis = winrt::make_weak(*this);
        auto token3 = button.Click([weakThis](auto&& sender, auto&& args)
        {
            if (auto strongThis = weakThis.get())
            {
                // 安全访问对象成员
                strongThis->OnButtonClicked();
            }
        });
    }
    
    ~EventDemonstration()
    {
        // 5. 清理所有事件订阅
        // 通常不需要手动做,智能指针会自动处理
        // 但在某些情况下可能需要显式取消订阅
    }
    
private:
    void OnButtonClicked()
    {
        OutputDebugStringW(L"对象方法被调用\n");
    }
};

自定义事件实现

cpp
// 实现自己的事件系统
template<typename... Args>
class custom_event
{
private:
    std::map<winrt::event_token, std::function<void(Args...)>> m_handlers;
    winrt::event_token m_nextToken{1};
    
public:
    winrt::event_token add(std::function<void(Args...)> handler)
    {
        auto token = m_nextToken++;
        m_handlers[token] = std::move(handler);
        return token;
    }
    
    void remove(winrt::event_token const& token)
    {
        m_handlers.erase(token);
    }
    
    void operator()(Args... args)
    {
        for (auto&& [token, handler] : m_handlers)
        {
            try
            {
                handler(args...);
            }
            catch (...)
            {
                // 处理事件处理器中的异常
                OutputDebugStringW(L"事件处理器异常\n");
            }
        }
    }
};

// 使用自定义事件
class CustomEventSource
{
private:
    custom_event<hstring> m_messageEvent;
    
public:
    winrt::event_token MessageReceived(std::function<void(hstring)> handler)
    {
        return m_messageEvent.add(std::move(handler));
    }
    
    void MessageReceived(winrt::event_token const& token)
    {
        m_messageEvent.remove(token);
    }
    
    void SendMessage(hstring const& message)
    {
        m_messageEvent(message);
    }
};

字符串处理与国际化

字符编码处理

cpp
// 深入理解字符编码转换
class StringConversion
{
public:
    static std::string HStringToUtf8(hstring const& wideString)
    {
        if (wideString.empty())
            return {};
        
        // 计算需要的缓冲区大小
        int required = WideCharToMultiByte(
            CP_UTF8, 0,
            wideString.c_str(), static_cast<int>(wideString.size()),
            nullptr, 0,
            nullptr, nullptr);
        
        if (required <= 0)
            return {};
        
        // 执行转换
        std::string result(required, '\0');
        WideCharToMultiByte(
            CP_UTF8, 0,
            wideString.c_str(), static_cast<int>(wideString.size()),
            result.data(), required,
            nullptr, nullptr);
        
        return result;
    }
    
    static hstring Utf8ToHString(std::string const& utf8String)
    {
        if (utf8String.empty())
            return {};
        
        // 计算需要的缓冲区大小
        int required = MultiByteToWideChar(
            CP_UTF8, 0,
            utf8String.c_str(), static_cast<int>(utf8String.size()),
            nullptr, 0);
        
        if (required <= 0)
            return {};
        
        // 执行转换
        std::wstring wideString(required, L'\0');
        MultiByteToWideChar(
            CP_UTF8, 0,
            utf8String.c_str(), static_cast<int>(utf8String.size()),
            wideString.data(), required);
        
        return hstring{wideString};
    }
};

资源本地化

cpp
// 使用 Windows 运行时资源系统
void DemonstrateLocalization()
{
    // 1. 获取资源加载器
    auto resourceLoader = winrt::Windows::ApplicationModel::Resources::ResourceLoader::GetForCurrentView();
    
    // 2. 加载字符串资源
    hstring localizedText = resourceLoader.GetString(L"WelcomeMessage");
    
    // 3. 带上下文的资源加载
    auto contextResourceLoader = winrt::Windows::ApplicationModel::Resources::ResourceLoader::GetForCurrentView(L"ErrorMessages");
    hstring errorText = contextResourceLoader.GetString(L"NetworkError");
    
    // 4. 格式化本地化字符串
    hstring userName = L"张三";
    hstring template = resourceLoader.GetString(L"WelcomeTemplate");  // "欢迎, {0}!"
    hstring welcomeMessage = std::format(template.c_str(), userName.c_str());
    
    OutputDebugStringW((L"本地化消息: " + localizedText).c_str());
}

异常处理机制

WinRT 异常类型

cpp
// 理解和处理 WinRT 异常
void DemonstrateExceptionHandling()
{
    try
    {
        // 可能抛出异常的 WinRT 操作
        auto invalidUri = winrt::Windows::Foundation::Uri{L"不是一个有效的 URI"};
    }
    catch (winrt::hresult_error const& ex)
    {
        // WinRT 异常包含丰富的错误信息
        HRESULT hr = ex.code();                    // 错误代码
        hstring message = ex.message();            // 错误消息
        
        // 常见的 HRESULT 值
        switch (hr)
        {
        case E_INVALIDARG:
            OutputDebugStringW(L"无效参数\n");
            break;
        case E_OUTOFMEMORY:
            OutputDebugStringW(L"内存不足\n");
            break;
        case E_NOTIMPL:
            OutputDebugStringW(L"功能未实现\n");
            break;
        default:
            OutputDebugStringW((L"未知错误: 0x" + 
                               winrt::to_hstring(static_cast<uint32_t>(hr))).c_str());
            break;
        }
        
        OutputDebugStringW((L"错误消息: " + message).c_str());
    }
    catch (std::exception const& ex)
    {
        // 标准 C++ 异常
        OutputDebugStringA(("标准异常: " + std::string(ex.what())).c_str());
    }
    catch (...)
    {
        // 捕获所有其他异常
        OutputDebugStringW(L"未知异常类型\n");
    }
}

// 检查 HRESULT 的辅助函数
void CheckHResult(HRESULT hr)
{
    if (FAILED(hr))
    {
        winrt::throw_hresult(hr);  // 将 HRESULT 转换为 WinRT 异常
    }
}

// 使用 winrt::check_hresult 简化错误检查
void SafeWinRTOperation()
{
    // 自动检查 HRESULT 并抛出异常
    winrt::check_hresult(SomeWin32APICall());
}

异常传播和处理策略

cpp
// 异常处理的最佳实践
class ExceptionHandlingBestPractices
{
public:
    // 1. 在适当的层级处理异常
    void UIEventHandler()
    {
        try
        {
            BusinessLogicOperation();
        }
        catch (winrt::hresult_error const& ex)
        {
            // 在 UI 层显示用户友好的错误消息
            ShowErrorDialog(L"操作失败: " + ex.message());
        }
        catch (...)
        {
            // 处理意外异常
            ShowErrorDialog(L"发生了未知错误");
        }
    }
    
    // 2. 业务逻辑层:让异常向上传播
    void BusinessLogicOperation()
    {
        // 不要在这里捕获异常,让它传播到 UI 层
        DataAccessOperation();
    }
    
    // 3. 数据访问层:转换底层异常
    void DataAccessOperation()
    {
        try
        {
            LowLevelFileOperation();
        }
        catch (std::filesystem::filesystem_error const& ex)
        {
            // 将底层异常转换为 WinRT 异常
            winrt::throw_hresult(E_FAIL);
        }
    }
    
private:
    void ShowErrorDialog(hstring const& message)
    {
        // 显示错误对话框的实现
        OutputDebugStringW((L"错误对话框: " + message).c_str());
    }
    
    void LowLevelFileOperation()
    {
        // 可能抛出 std::filesystem 异常的操作
        throw std::filesystem::filesystem_error("文件不存在", std::error_code{});
    }
};

总结

本部分深入讲解了 WinRT 的基础概念:

  1. 类型系统:理解 WinRT 类型与 C++ 类型的映射关系
  2. 字符串处理:深入了解 hstring 的内部机制和用法
  3. 集合操作:掌握各种 WinRT 集合类型的特点和用法
  4. 事件系统:理解事件订阅、取消订阅和生命周期管理
  5. 异常处理:掌握 WinRT 异常系统和最佳实践

这些基础概念是深入学习 WinUI 3 开发的重要基石。


这是 WinUI 3 WinRT C++ 完整教程的第四部分。下一部分我们将学习 XAML 框架和数据绑定的深层机制。