Windows PostMessage 跨线程传递 std::vector 指针

通过 PostMessage 在 Windows 消息队列中传递 std::vector 指针,使用 reinterpret_cast 将指针装入 LPARAM,并在接收方正确释放内存。

背景

在 Windows GUI 应用中,UI 线程与工作线程之间经常需要交换数据。PostMessage 是异步消息投递的标准方式——但它的 wParam / lParam 只是两个指针大小的整数,无法直接容纳一个 std::vector

常见需求场景:

  • 后台线程采集数据,填充到 std::vector<Item>,通知 UI 线程刷新列表
  • 工作线程批量处理结果,回调到主窗口

原理:指针即整数

LPARAM 在 64 位系统上是 8 字节整数,与指针大小相同。因此可以把堆上分配的 std::vector* 强转为 LPARAM 传递,接收方再强转回来。

PostMessage 发送方              消息队列              接收方 WndProc
─────────────────────     ────────────────     ─────────────────────
new vector<T>  ──(reinterpret_cast)──> LPARAM ──(reinterpret_cast)──> vector<T>*

                                                                        使用完毕

                                                                        delete ✓

发送方:分配并投递

// 自定义消息 ID(WM_APP 之后是安全范围)
constexpr UINT WM_DATA_READY = WM_APP + 1;

void WorkerThread(HWND hMainWnd)
{
    // 1. 在堆上分配 vector(生命周期由接收方负责)
    auto* pData = new std::vector<std::string>();
    pData->push_back("item_1");
    pData->push_back("item_2");
    pData->push_back("item_3");

    // 2. reinterpret_cast 到 LPARAM(pointer → integer,同等大小)
    LPARAM lParam = reinterpret_cast<LPARAM>(pData);

    // 3. PostMessage 是异步的:本函数立即返回,不等窗口处理完
    //    注意:此后 *pData 的所有权已转移,不能再使用 pData!
    PostMessage(hMainWnd, WM_DATA_READY, 0, lParam);
}

接收方:还原并释放

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_DATA_READY:
    {
        // 1. 将 LPARAM 还原为指针
        auto* pData = reinterpret_cast<std::vector<std::string>*>(lParam);

        // 2. 使用数据
        for (const auto& s : *pData)
        {
            // 更新列表框、刷新 UI 等操作
            ::SendMessage(hListBox, LB_ADDSTRING, 0,
                          reinterpret_cast<LPARAM>(s.c_str()));
        }

        // 3. 释放堆内存——接收方负责 delete!
        delete pData;
        return 0;
    }
    // ... 其他消息处理
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

注意事项

1. 所有权转移必须明确

PostMessage 后发送方不得再访问该指针,所有权完全转移给接收方。推荐加一个注释或将局部变量设为 nullptr

PostMessage(hMainWnd, WM_DATA_READY, 0, reinterpret_cast<LPARAM>(pData));
pData = nullptr; // 明确放弃所有权

2. PostMessage 失败时内存泄漏

PostMessage 在消息队列满或窗口句柄无效时会返回 FALSE。必须处理这种情况:

if (!PostMessage(hMainWnd, WM_DATA_READY, 0, reinterpret_cast<LPARAM>(pData)))
{
    // 发送失败,自己释放,避免泄漏
    delete pData;
    pData = nullptr;
}

3. SendMessage vs PostMessage

特性SendMessagePostMessage
执行方式同步(等待处理完成)异步(投入队列立即返回)
跨线程安全是(会切换到目标线程)
所有权调用返回后指针仍有效投递后指针不可再用

若同线程使用 SendMessage,调用返回后仍可 delete(不需要接收方释放),但跨线程异步场景必须用 PostMessage + 接收方 delete

4. 更现代的替代方案

C++17 之后更推荐使用智能指针包裹或通过线程安全队列(如 std::queue + std::mutex)传数据,避免手动内存管理。但在与 MFC/WinAPI 深度集成的场景中,指针转型仍是最轻量的方案。

延伸:传递其他复杂类型

同样的技术可以用于任意堆分配类型:

// 自定义结构体
struct TaskResult { int code; std::string message; };
auto* result = new TaskResult{200, "OK"};
PostMessage(hWnd, WM_TASK_DONE, 0, reinterpret_cast<LPARAM>(result));

// 接收方
auto* r = reinterpret_cast<TaskResult*>(lParam);
// ... 使用
delete r;

原理完全相同。关键是在代码中明确标注所有权归属,避免双重释放或内存泄漏。