[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fw-BZxHRTJrMlHWLybFnUQHongNQ9sO7_nScpCPj3U5Y":3,"$fJU-4tot_gC5fDkujNeoE-cGsdMy5V_KcdUXLuAnTFgw":15,"$f1uQiRNMPM2AYC91WvZJPCqLc-oy03_jSu34AB4btB0E":423},{"slug":4,"title":5,"description":6,"content":7,"content_html":8,"pub_date":9,"tags":10,"draft":14},"win-postmessage-vector","Windows PostMessage 跨线程传递 std::vector 指针","通过 PostMessage 在 Windows 消息队列中传递 std::vector 指针，使用 reinterpret_cast 将指针装入 LPARAM，并在接收方正确释放内存。","# Windows PostMessage 跨线程传递 std::vector 指针\n\n在 Windows 多线程编程中，线程间通信是一个常见需求。`PostMessage` 是 Win32 API 中异步发送消息的利器，但当你需要通过它传递复杂数据（如 `std::vector`）时，就需要格外小心。\n\n## Windows 消息机制回顾\n\nWindows 消息系统是事件驱动编程的基础。每个创建了窗口的线程都有一个消息队列，`PostMessage` 将消息异步放入目标窗口所在线程的队列，由该线程的消息循环取出处理。\n\n```\n发送线程:  PostMessage(hwnd, WM_USER+1, wParam, lParam)\n              ↓ 异步，立即返回\n目标线程:  GetMessage() → DispatchMessage() → WndProc()\n```\n\n与 `SendMessage` 的区别：\n\n| 特性 | PostMessage | SendMessage |\n|------|-------------|-------------|\n| 同步\u002F异步 | 异步（立即返回） | 同步（等待处理完成） |\n| 返回值 | 是否成功入队 | 消息处理的返回值 |\n| 跨线程安全 | 安全，但数据生命周期需自管理 | 安全（有锁），但可能死锁 |\n| 跨进程 | 支持（有限制） | 支持 |\n\n## WPARAM\u002FLPARAM 的本质限制\n\n`PostMessage` 的签名：\n\n```cpp\nBOOL PostMessage(\n    HWND   hWnd,     \u002F\u002F 目标窗口句柄\n    UINT   Msg,      \u002F\u002F 消息 ID\n    WPARAM wParam,   \u002F\u002F 附加参数1\n    LPARAM lParam    \u002F\u002F 附加参数2\n);\n```\n\n`WPARAM` 和 `LPARAM` 本质上都是 `UINT_PTR`，即与指针同宽的无符号整数：\n- 32 位系统：4 字节\n- 64 位系统：8 字节\n\n这意味着你**可以把任意指针塞进 lParam\u002FwParam**，只要指针宽度不超过 `UINT_PTR`。\n\n但这只是技术上可行，实际操作中有严重的生命周期风险。\n\n## 指针传递的生命周期风险\n\n### 栈上对象：绝对不行\n\n```cpp\n\u002F\u002F ❌ 极度危险：栈上的 vector 在函数返回后就销毁了\nvoid SomeFunction() {\n    std::vector\u003Cint> data = {1, 2, 3};\n    PostMessage(hwnd, WM_USER_DATA, 0, (LPARAM)&data);\n    \u002F\u002F 函数返回后 data 被销毁，接收方拿到的是悬空指针！\n}\n```\n\n### 局部动态对象：也很危险\n\n```cpp\n\u002F\u002F ❌ 危险：智能指针管理的对象可能在接收前就释放\nvoid SomeFunction() {\n    auto data = std::make_unique\u003Cstd::vector\u003Cint>>(std::initializer_list\u003Cint>{1,2,3});\n    PostMessage(hwnd, WM_USER_DATA, 0, (LPARAM)data.get());\n    \u002F\u002F data 在这里析构，裸指针悬空\n}\n```\n\n### 全局\u002F静态对象：有竞争\n\n```cpp\n\u002F\u002F ⚠️ 有竞争：如果多次调用，接收方可能读到更新后的数据\nstatic std::vector\u003Cint> g_data;\n\nvoid SomeFunction() {\n    g_data = {1, 2, 3};\n    PostMessage(hwnd, WM_USER_DATA, 0, (LPARAM)&g_data);\n    \u002F\u002F 如果再次调用，g_data 被修改，接收方还没处理完\n}\n```\n\n## std::vector 动态分配传递方案\n\n### 方案一：new\u002Fdelete 手动管理（基础方案）\n\n发送方 `new` 分配，接收方处理完后 `delete`：\n\n```cpp\n\u002F\u002F ===== 发送方 =====\nvoid SendVectorData(HWND hwnd, const std::vector\u003Cint>& src) {\n    \u002F\u002F 在堆上创建副本，接收方负责 delete\n    auto* pData = new std::vector\u003Cint>(src);\n    \n    if (!PostMessage(hwnd, WM_USER_VECTOR_DATA, 0, (LPARAM)pData)) {\n        \u002F\u002F PostMessage 失败，必须自己释放，否则内存泄漏\n        delete pData;\n        \u002F\u002F 处理错误...\n    }\n    \u002F\u002F PostMessage 成功，所有权转移给接收方\n}\n\n\u002F\u002F ===== 接收方（窗口过程）=====\nLRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {\n    switch (msg) {\n    case WM_USER_VECTOR_DATA: {\n        \u002F\u002F 取回指针，接管所有权\n        auto* pData = reinterpret_cast\u003Cstd::vector\u003Cint>*>(lParam);\n        \n        \u002F\u002F 使用数据\n        for (int val : *pData) {\n            \u002F\u002F 处理...\n        }\n        \n        \u002F\u002F 必须 delete，否则内存泄漏\n        delete pData;\n        return 0;\n    }\n    \u002F\u002F ...\n    }\n    return DefWindowProc(hwnd, msg, wParam, lParam);\n}\n```\n\n### 方案二：shared_ptr 引用计数（推荐）\n\n用 `shared_ptr` 管理生命周期，不需要手动 delete：\n\n```cpp\n#include \u003Cmemory>\n#include \u003Cvector>\n\n\u002F\u002F 定义消息\nconstexpr UINT WM_SHARED_VECTOR = WM_USER + 100;\n\n\u002F\u002F ===== 发送方 =====\nvoid SendVectorData(HWND hwnd, std::vector\u003Cint> data) {\n    \u002F\u002F 将 data move 进 shared_ptr\n    auto spData = std::make_shared\u003Cstd::vector\u003Cint>>(std::move(data));\n    \n    \u002F\u002F 增加引用计数，把裸指针传过去\n    \u002F\u002F 接收方负责通过 spData.get() 重建 shared_ptr\n    auto* raw = new std::shared_ptr\u003Cstd::vector\u003Cint>>(spData);\n    \n    if (!PostMessage(hwnd, WM_SHARED_VECTOR, 0, (LPARAM)raw)) {\n        delete raw;  \u002F\u002F 入队失败，释放包装的 shared_ptr\n    }\n}\n\n\u002F\u002F ===== 接收方 =====\ncase WM_SHARED_VECTOR: {\n    auto* pWrapper = reinterpret_cast\u003Cstd::shared_ptr\u003Cstd::vector\u003Cint>>*>(lParam);\n    auto spData = *pWrapper;  \u002F\u002F 拷贝 shared_ptr，增加引用计数\n    delete pWrapper;          \u002F\u002F 释放包装，但 vector 还活着\n    \n    \u002F\u002F 安全使用 spData\n    for (int val : *spData) {\n        ProcessValue(val);\n    }\n    \u002F\u002F spData 出作用域，引用计数归零，vector 自动销毁\n    return 0;\n}\n```\n\n### 方案三：消息结构体（复杂数据推荐）\n\n对于需要传递多个字段的情况，定义专用结构体：\n\n```cpp\nstruct VectorMessage {\n    std::vector\u003Cint> data;\n    int requestId;\n    DWORD senderThreadId;\n    \n    \u002F\u002F 如果需要回调通知\n    std::function\u003Cvoid(bool)> onComplete;\n};\n\n\u002F\u002F 发送\nauto* msg = new VectorMessage{\n    .data = {1, 2, 3, 4, 5},\n    .requestId = 42,\n    .senderThreadId = GetCurrentThreadId()\n};\nPostMessage(hwnd, WM_USER_VECTOR_MSG, 0, (LPARAM)msg);\n\n\u002F\u002F 接收\ncase WM_USER_VECTOR_MSG: {\n    auto* msg = reinterpret_cast\u003CVectorMessage*>(lParam);\n    ProcessData(msg->data);\n    if (msg->onComplete) msg->onComplete(true);\n    delete msg;\n    return 0;\n}\n```\n\n## 线程同步注意事项\n\n### 避免发送方等待接收方\n\n`PostMessage` 是异步的，如果发送方需要知道接收方何时处理完，有几种方式：\n\n**1. 反向 PostMessage 通知**\n```cpp\n\u002F\u002F 接收方处理完后，回发消息给发送方\ncase WM_USER_VECTOR_DATA: {\n    auto* pData = reinterpret_cast\u003Cstd::vector\u003Cint>*>(lParam);\n    ProcessData(*pData);\n    delete pData;\n    \n    \u002F\u002F 通知发送方\n    PostMessage(senderHwnd, WM_USER_DATA_DONE, requestId, 0);\n    return 0;\n}\n```\n\n**2. 事件对象**\n```cpp\n\u002F\u002F 发送方创建事件\nHANDLE hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);\n\nstruct Task {\n    std::vector\u003Cint> data;\n    HANDLE hDoneEvent;\n};\n\nauto* task = new Task{{1,2,3}, hEvent};\nPostMessage(hwnd, WM_TASK, 0, (LPARAM)task);\n\n\u002F\u002F 等待完成（如果真的需要同步）\nWaitForSingleObject(hEvent, INFINITE);\nCloseHandle(hEvent);\n```\n\n### 队列溢出问题\n\nWindows 消息队列有大小限制（默认约 10000 条）。如果发送速率远超处理速率，`PostMessage` 会返回 `FALSE`（`GetLastError()` 返回 `ERROR_NOT_ENOUGH_QUOTA`）。\n\n```cpp\nvoid SafePost(HWND hwnd, std::vector\u003Cint> data) {\n    auto* pData = new std::vector\u003Cint>(std::move(data));\n    \n    if (!PostMessage(hwnd, WM_USER_DATA, 0, (LPARAM)pData)) {\n        DWORD err = GetLastError();\n        if (err == ERROR_NOT_ENOUGH_QUOTA) {\n            \u002F\u002F 队列满了，可以：\n            \u002F\u002F 1. 等一下重试\n            \u002F\u002F 2. 丢弃数据\n            \u002F\u002F 3. 使用其他 IPC 机制\n        }\n        delete pData;  \u002F\u002F 记得释放！\n    }\n}\n```\n\n## 内存管理最佳实践\n\n### 所有权转移原则\n\n通过 `PostMessage` 传递指针时，遵循**所有权转移**原则：\n\n1. `PostMessage` 成功 → 所有权转移给接收方，发送方不再访问\n2. `PostMessage` 失败 → 发送方保留所有权，负责释放\n3. 接收方处理完毕 → 接收方负责 `delete`\n\n### 用 RAII 包装简化管理\n\n```cpp\n\u002F\u002F 自定义 deleter，用于 unique_ptr 接管从消息传来的裸指针\ntemplate\u003Ctypename T>\nstruct MessagePtrDeleter {\n    void operator()(T* p) const { delete p; }\n};\n\n\u002F\u002F 接收方安全包装\ncase WM_USER_VECTOR_DATA: {\n    \u002F\u002F 立即用 unique_ptr 接管，确保不会泄漏\n    std::unique_ptr\u003Cstd::vector\u003Cint>> pData(\n        reinterpret_cast\u003Cstd::vector\u003Cint>*>(lParam)\n    );\n    \n    \u002F\u002F 即使 ProcessData 抛异常，pData 也会正确析构\n    ProcessData(*pData);\n    return 0;  \u002F\u002F pData 在此析构，自动 delete\n}\n```\n\n### 防止双重释放\n\n如果担心消息可能被处理多次（逻辑 bug），可以用 exchange 模式：\n\n```cpp\n\u002F\u002F 使用 atomic 防止双重处理\ncase WM_USER_VECTOR_DATA: {\n    auto rawPtr = lParam;\n    \u002F\u002F 用完立即清空，防止意外重入\n    auto* pData = reinterpret_cast\u003Cstd::vector\u003Cint>*>(rawPtr);\n    if (!pData) return 0;\n    \n    ProcessData(*pData);\n    delete pData;\n    return 0;\n}\n```\n\n## 安全注意事项总结\n\n1. **永远不要传递栈上对象的指针**——函数返回后指针悬空。\n\n2. **PostMessage 失败时必须释放内存**——否则内存泄漏。不要假设 PostMessage 总会成功。\n\n3. **接收方必须释放内存**——发送方转移所有权后不再负责，接收方是最后的\"主人\"。\n\n4. **注意窗口销毁时的消息残留**——如果目标窗口在处理消息前被销毁，队列里的消息（和里面的指针）会丢失，造成内存泄漏。可以在 `WM_DESTROY` 中清空残留消息。\n\n5. **跨进程传递要用 WM_COPYDATA**——`PostMessage` 跨进程传递指针无意义（指针在另一个进程地址空间无效），应使用 `WM_COPYDATA` 或共享内存。\n\n6. **考虑使用现代替代方案**——对于复杂的跨线程通信，考虑使用 `std::queue` + 互斥锁、无锁队列、或者 Windows 的 IO Completion Ports。\n\n`PostMessage` + 指针传递是一种简单有效的跨线程通信方式，但需要严格遵守所有权协议，才能避免内存泄漏和悬空指针问题。\n","\u003Ch1>Windows PostMessage 跨线程传递 std::vector 指针\u003C\u002Fh1>\n\u003Cp>在 Windows 多线程编程中，线程间通信是一个常见需求。\u003Ccode>PostMessage\u003C\u002Fcode> 是 Win32 API 中异步发送消息的利器，但当你需要通过它传递复杂数据（如 \u003Ccode>std::vector\u003C\u002Fcode>）时，就需要格外小心。\u003C\u002Fp>\n\u003Ch2 id=\"windows-消息机制回顾\">Windows 消息机制回顾\u003C\u002Fh2>\n\u003Cp>Windows 消息系统是事件驱动编程的基础。每个创建了窗口的线程都有一个消息队列，\u003Ccode>PostMessage\u003C\u002Fcode> 将消息异步放入目标窗口所在线程的队列，由该线程的消息循环取出处理。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>发送线程:  PostMessage(hwnd, WM_USER+1, wParam, lParam)\n              ↓ 异步，立即返回\n目标线程:  GetMessage() → DispatchMessage() → WndProc()\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>与 \u003Ccode>SendMessage\u003C\u002Fcode> 的区别：\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>特性\u003C\u002Fth>\n\u003Cth>PostMessage\u003C\u002Fth>\n\u003Cth>SendMessage\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>同步\u002F异步\u003C\u002Ftd>\n\u003Ctd>异步（立即返回）\u003C\u002Ftd>\n\u003Ctd>同步（等待处理完成）\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>返回值\u003C\u002Ftd>\n\u003Ctd>是否成功入队\u003C\u002Ftd>\n\u003Ctd>消息处理的返回值\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>跨线程安全\u003C\u002Ftd>\n\u003Ctd>安全，但数据生命周期需自管理\u003C\u002Ftd>\n\u003Ctd>安全（有锁），但可能死锁\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>跨进程\u003C\u002Ftd>\n\u003Ctd>支持（有限制）\u003C\u002Ftd>\n\u003Ctd>支持\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch2 id=\"wparam-lparam-的本质限制\">WPARAM\u002FLPARAM 的本质限制\u003C\u002Fh2>\n\u003Cp>\u003Ccode>PostMessage\u003C\u002Fcode> 的签名：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">BOOL PostMessage(\n    HWND   hWnd,     \u002F\u002F 目标窗口句柄\n    UINT   Msg,      \u002F\u002F 消息 ID\n    WPARAM wParam,   \u002F\u002F 附加参数1\n    LPARAM lParam    \u002F\u002F 附加参数2\n);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>WPARAM\u003C\u002Fcode> 和 \u003Ccode>LPARAM\u003C\u002Fcode> 本质上都是 \u003Ccode>UINT_PTR\u003C\u002Fcode>，即与指针同宽的无符号整数：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>32 位系统：4 字节\u003C\u002Fli>\n\u003Cli>64 位系统：8 字节\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>这意味着你\u003Cstrong>可以把任意指针塞进 lParam\u002FwParam\u003C\u002Fstrong>，只要指针宽度不超过 \u003Ccode>UINT_PTR\u003C\u002Fcode>。\u003C\u002Fp>\n\u003Cp>但这只是技术上可行，实际操作中有严重的生命周期风险。\u003C\u002Fp>\n\u003Ch2 id=\"指针传递的生命周期风险\">指针传递的生命周期风险\u003C\u002Fh2>\n\u003Ch3 id=\"栈上对象-绝对不行\">栈上对象：绝对不行\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F ❌ 极度危险：栈上的 vector 在函数返回后就销毁了\nvoid SomeFunction() {\n    std::vector&lt;int&gt; data = {1, 2, 3};\n    PostMessage(hwnd, WM_USER_DATA, 0, (LPARAM)&amp;data);\n    \u002F\u002F 函数返回后 data 被销毁，接收方拿到的是悬空指针！\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"局部动态对象-也很危险\">局部动态对象：也很危险\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F ❌ 危险：智能指针管理的对象可能在接收前就释放\nvoid SomeFunction() {\n    auto data = std::make_unique&lt;std::vector&lt;int&gt;&gt;(std::initializer_list&lt;int&gt;{1,2,3});\n    PostMessage(hwnd, WM_USER_DATA, 0, (LPARAM)data.get());\n    \u002F\u002F data 在这里析构，裸指针悬空\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"全局-静态对象-有竞争\">全局\u002F静态对象：有竞争\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F ⚠️ 有竞争：如果多次调用，接收方可能读到更新后的数据\nstatic std::vector&lt;int&gt; g_data;\n\nvoid SomeFunction() {\n    g_data = {1, 2, 3};\n    PostMessage(hwnd, WM_USER_DATA, 0, (LPARAM)&amp;g_data);\n    \u002F\u002F 如果再次调用，g_data 被修改，接收方还没处理完\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"std-vector-动态分配传递方案\">std::vector 动态分配传递方案\u003C\u002Fh2>\n\u003Ch3 id=\"方案一-new-delete-手动管理-基础方案\">方案一：new\u002Fdelete 手动管理（基础方案）\u003C\u002Fh3>\n\u003Cp>发送方 \u003Ccode>new\u003C\u002Fcode> 分配，接收方处理完后 \u003Ccode>delete\u003C\u002Fcode>：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F ===== 发送方 =====\nvoid SendVectorData(HWND hwnd, const std::vector&lt;int&gt;&amp; src) {\n    \u002F\u002F 在堆上创建副本，接收方负责 delete\n    auto* pData = new std::vector&lt;int&gt;(src);\n    \n    if (!PostMessage(hwnd, WM_USER_VECTOR_DATA, 0, (LPARAM)pData)) {\n        \u002F\u002F PostMessage 失败，必须自己释放，否则内存泄漏\n        delete pData;\n        \u002F\u002F 处理错误...\n    }\n    \u002F\u002F PostMessage 成功，所有权转移给接收方\n}\n\n\u002F\u002F ===== 接收方（窗口过程）=====\nLRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {\n    switch (msg) {\n    case WM_USER_VECTOR_DATA: {\n        \u002F\u002F 取回指针，接管所有权\n        auto* pData = reinterpret_cast&lt;std::vector&lt;int&gt;*&gt;(lParam);\n        \n        \u002F\u002F 使用数据\n        for (int val : *pData) {\n            \u002F\u002F 处理...\n        }\n        \n        \u002F\u002F 必须 delete，否则内存泄漏\n        delete pData;\n        return 0;\n    }\n    \u002F\u002F ...\n    }\n    return DefWindowProc(hwnd, msg, wParam, lParam);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"方案二-shared_ptr-引用计数-推荐\">方案二：shared_ptr 引用计数（推荐）\u003C\u002Fh3>\n\u003Cp>用 \u003Ccode>shared_ptr\u003C\u002Fcode> 管理生命周期，不需要手动 delete：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">#include &lt;memory&gt;\n#include &lt;vector&gt;\n\n\u002F\u002F 定义消息\nconstexpr UINT WM_SHARED_VECTOR = WM_USER + 100;\n\n\u002F\u002F ===== 发送方 =====\nvoid SendVectorData(HWND hwnd, std::vector&lt;int&gt; data) {\n    \u002F\u002F 将 data move 进 shared_ptr\n    auto spData = std::make_shared&lt;std::vector&lt;int&gt;&gt;(std::move(data));\n    \n    \u002F\u002F 增加引用计数，把裸指针传过去\n    \u002F\u002F 接收方负责通过 spData.get() 重建 shared_ptr\n    auto* raw = new std::shared_ptr&lt;std::vector&lt;int&gt;&gt;(spData);\n    \n    if (!PostMessage(hwnd, WM_SHARED_VECTOR, 0, (LPARAM)raw)) {\n        delete raw;  \u002F\u002F 入队失败，释放包装的 shared_ptr\n    }\n}\n\n\u002F\u002F ===== 接收方 =====\ncase WM_SHARED_VECTOR: {\n    auto* pWrapper = reinterpret_cast&lt;std::shared_ptr&lt;std::vector&lt;int&gt;&gt;*&gt;(lParam);\n    auto spData = *pWrapper;  \u002F\u002F 拷贝 shared_ptr，增加引用计数\n    delete pWrapper;          \u002F\u002F 释放包装，但 vector 还活着\n    \n    \u002F\u002F 安全使用 spData\n    for (int val : *spData) {\n        ProcessValue(val);\n    }\n    \u002F\u002F spData 出作用域，引用计数归零，vector 自动销毁\n    return 0;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"方案三-消息结构体-复杂数据推荐\">方案三：消息结构体（复杂数据推荐）\u003C\u002Fh3>\n\u003Cp>对于需要传递多个字段的情况，定义专用结构体：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">struct VectorMessage {\n    std::vector&lt;int&gt; data;\n    int requestId;\n    DWORD senderThreadId;\n    \n    \u002F\u002F 如果需要回调通知\n    std::function&lt;void(bool)&gt; onComplete;\n};\n\n\u002F\u002F 发送\nauto* msg = new VectorMessage{\n    .data = {1, 2, 3, 4, 5},\n    .requestId = 42,\n    .senderThreadId = GetCurrentThreadId()\n};\nPostMessage(hwnd, WM_USER_VECTOR_MSG, 0, (LPARAM)msg);\n\n\u002F\u002F 接收\ncase WM_USER_VECTOR_MSG: {\n    auto* msg = reinterpret_cast&lt;VectorMessage*&gt;(lParam);\n    ProcessData(msg-&gt;data);\n    if (msg-&gt;onComplete) msg-&gt;onComplete(true);\n    delete msg;\n    return 0;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"线程同步注意事项\">线程同步注意事项\u003C\u002Fh2>\n\u003Ch3 id=\"避免发送方等待接收方\">避免发送方等待接收方\u003C\u002Fh3>\n\u003Cp>\u003Ccode>PostMessage\u003C\u002Fcode> 是异步的，如果发送方需要知道接收方何时处理完，有几种方式：\u003C\u002Fp>\n\u003Cp>\u003Cstrong>1. 反向 PostMessage 通知\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F 接收方处理完后，回发消息给发送方\ncase WM_USER_VECTOR_DATA: {\n    auto* pData = reinterpret_cast&lt;std::vector&lt;int&gt;*&gt;(lParam);\n    ProcessData(*pData);\n    delete pData;\n    \n    \u002F\u002F 通知发送方\n    PostMessage(senderHwnd, WM_USER_DATA_DONE, requestId, 0);\n    return 0;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>2. 事件对象\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F 发送方创建事件\nHANDLE hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);\n\nstruct Task {\n    std::vector&lt;int&gt; data;\n    HANDLE hDoneEvent;\n};\n\nauto* task = new Task{{1,2,3}, hEvent};\nPostMessage(hwnd, WM_TASK, 0, (LPARAM)task);\n\n\u002F\u002F 等待完成（如果真的需要同步）\nWaitForSingleObject(hEvent, INFINITE);\nCloseHandle(hEvent);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"队列溢出问题\">队列溢出问题\u003C\u002Fh3>\n\u003Cp>Windows 消息队列有大小限制（默认约 10000 条）。如果发送速率远超处理速率，\u003Ccode>PostMessage\u003C\u002Fcode> 会返回 \u003Ccode>FALSE\u003C\u002Fcode>（\u003Ccode>GetLastError()\u003C\u002Fcode> 返回 \u003Ccode>ERROR_NOT_ENOUGH_QUOTA\u003C\u002Fcode>）。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">void SafePost(HWND hwnd, std::vector&lt;int&gt; data) {\n    auto* pData = new std::vector&lt;int&gt;(std::move(data));\n    \n    if (!PostMessage(hwnd, WM_USER_DATA, 0, (LPARAM)pData)) {\n        DWORD err = GetLastError();\n        if (err == ERROR_NOT_ENOUGH_QUOTA) {\n            \u002F\u002F 队列满了，可以：\n            \u002F\u002F 1. 等一下重试\n            \u002F\u002F 2. 丢弃数据\n            \u002F\u002F 3. 使用其他 IPC 机制\n        }\n        delete pData;  \u002F\u002F 记得释放！\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"内存管理最佳实践\">内存管理最佳实践\u003C\u002Fh2>\n\u003Ch3 id=\"所有权转移原则\">所有权转移原则\u003C\u002Fh3>\n\u003Cp>通过 \u003Ccode>PostMessage\u003C\u002Fcode> 传递指针时，遵循\u003Cstrong>所有权转移\u003C\u002Fstrong>原则：\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Ccode>PostMessage\u003C\u002Fcode> 成功 → 所有权转移给接收方，发送方不再访问\u003C\u002Fli>\n\u003Cli>\u003Ccode>PostMessage\u003C\u002Fcode> 失败 → 发送方保留所有权，负责释放\u003C\u002Fli>\n\u003Cli>接收方处理完毕 → 接收方负责 \u003Ccode>delete\u003C\u002Fcode>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3 id=\"用-raii-包装简化管理\">用 RAII 包装简化管理\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F 自定义 deleter，用于 unique_ptr 接管从消息传来的裸指针\ntemplate&lt;typename T&gt;\nstruct MessagePtrDeleter {\n    void operator()(T* p) const { delete p; }\n};\n\n\u002F\u002F 接收方安全包装\ncase WM_USER_VECTOR_DATA: {\n    \u002F\u002F 立即用 unique_ptr 接管，确保不会泄漏\n    std::unique_ptr&lt;std::vector&lt;int&gt;&gt; pData(\n        reinterpret_cast&lt;std::vector&lt;int&gt;*&gt;(lParam)\n    );\n    \n    \u002F\u002F 即使 ProcessData 抛异常，pData 也会正确析构\n    ProcessData(*pData);\n    return 0;  \u002F\u002F pData 在此析构，自动 delete\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"防止双重释放\">防止双重释放\u003C\u002Fh3>\n\u003Cp>如果担心消息可能被处理多次（逻辑 bug），可以用 exchange 模式：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F 使用 atomic 防止双重处理\ncase WM_USER_VECTOR_DATA: {\n    auto rawPtr = lParam;\n    \u002F\u002F 用完立即清空，防止意外重入\n    auto* pData = reinterpret_cast&lt;std::vector&lt;int&gt;*&gt;(rawPtr);\n    if (!pData) return 0;\n    \n    ProcessData(*pData);\n    delete pData;\n    return 0;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"安全注意事项总结\">安全注意事项总结\u003C\u002Fh2>\n\u003Col>\n\u003Cli>\n\u003Cp>\u003Cstrong>永远不要传递栈上对象的指针\u003C\u002Fstrong>——函数返回后指针悬空。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>PostMessage 失败时必须释放内存\u003C\u002Fstrong>——否则内存泄漏。不要假设 PostMessage 总会成功。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>接收方必须释放内存\u003C\u002Fstrong>——发送方转移所有权后不再负责，接收方是最后的&quot;主人&quot;。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>注意窗口销毁时的消息残留\u003C\u002Fstrong>——如果目标窗口在处理消息前被销毁，队列里的消息（和里面的指针）会丢失，造成内存泄漏。可以在 \u003Ccode>WM_DESTROY\u003C\u002Fcode> 中清空残留消息。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>跨进程传递要用 WM_COPYDATA\u003C\u002Fstrong>——\u003Ccode>PostMessage\u003C\u002Fcode> 跨进程传递指针无意义（指针在另一个进程地址空间无效），应使用 \u003Ccode>WM_COPYDATA\u003C\u002Fcode> 或共享内存。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>考虑使用现代替代方案\u003C\u002Fstrong>——对于复杂的跨线程通信，考虑使用 \u003Ccode>std::queue\u003C\u002Fcode> + 互斥锁、无锁队列、或者 Windows 的 IO Completion Ports。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>\u003Ccode>PostMessage\u003C\u002Fcode> + 指针传递是一种简单有效的跨线程通信方式，但需要严格遵守所有权协议，才能避免内存泄漏和悬空指针问题。\u003C\u002Fp>\n","2023-05-26",[11,12,13],"cpp","windows","WinAPI",false,[16,29,40,52,62,69,76,83,90,97,107,116,126,135,143,151,160,169,178,188,195,204,210,217,223,232,239,246,254,264,273,282,292,302,312,320,330,341,350,359,362,368,376,384,392,400,408,415],{"slug":17,"title":18,"description":19,"pub_date":20,"tags":21,"draft":14,"word_count":28},"ide-skills-guide","Agent Skills 完全指南：21 款第三方 Skill 深度评测与使用心得","全面评测 21 款第三方 Agent Skills，涵盖 Vue 生态、前端设计、构建工具、实用工具四大分类。从安装配置到实际使用场景，带你了解每个 Skill 的功能特点、最佳实践与使用心得。","2026-06-15",[22,23,24,25,26,27],"agent","skills","AI","效率工具","前端","Vue",4169,{"slug":30,"title":31,"description":32,"pub_date":33,"tags":34,"draft":14,"word_count":39},"linux-kernel-skeleton-struct-funcptr-container_of","Linux 内核骨架：struct、函数指针与 container_of","读懂 Linux 内核源码的三件套：巨大的 struct 组合代替继承、函数指针表实现虚派发、container_of 宏从嵌入成员找回完整对象。","2026-05-09",[35,36,37,38],"linux","kernel","C","container_of",1369,{"slug":41,"title":42,"description":43,"pub_date":44,"tags":45,"draft":14,"word_count":51},"astro-complete-guide-2025","Astro 5 深度剖析：Islands 架构原理、构建优化与 Cloudflare Workers 边缘部署","从编译器视角解析 Astro 5 的 Islands 架构实现原理，Content Layer API 的 Vite 插件机制，Server Islands 的流式渲染，以及如何在 Cloudflare Workers + D1 边缘环境下榨干性能。","2026-05-08",[46,47,48,49,50],"astro","frontend","cloudflare","performance","architecture",3663,{"slug":53,"title":54,"description":55,"pub_date":56,"tags":57,"draft":14,"word_count":61},"llm-prompt-engineering","Prompt Engineering 实战：让 LLM 真正听话的技巧","System prompt 怎么写、Few-shot 怎么设计、Chain-of-Thought 原理，以及常见失败模式和调试方法。","2026-05-03",[58,59,60],"ai","llm","工程实践",1723,{"slug":63,"title":64,"description":65,"pub_date":56,"tags":66,"draft":14,"word_count":68},"rag-system-design","RAG 系统设计：从 naive 到 production-ready","Retrieval-Augmented Generation 不只是「向量数据库 + LLM」，分块策略、召回质量、重排序、缓存才是工程核心。",[58,67,59,60],"rag",1613,{"slug":70,"title":71,"description":72,"pub_date":56,"tags":73,"draft":14,"word_count":75},"git-advanced-workflow","Git 进阶工作流：rebase、cherry-pick、bisect 的正确使用","merge 会了，但 rebase 总搞错？bisect 找 bug 提交？interactive rebase 整理历史？这篇一次说清楚。",[74,60],"git",1396,{"slug":77,"title":78,"description":79,"pub_date":56,"tags":80,"draft":14,"word_count":82},"docker-practical-guide","Docker 实战：从会用到用好","会 docker run 不够，Dockerfile 最佳实践、多阶段构建、Compose 编排、镜像瘦身才是日常真正需要的。",[81,35,60],"docker",1268,{"slug":84,"title":85,"description":86,"pub_date":56,"tags":87,"draft":14,"word_count":89},"anthropics-skills-guide","anthropics\u002Fskills：Anthropic 官方 Agent Skills 仓库解析","Anthropic 官方开源的 Agent Skills 标准仓库，127k stars，解析 SKILL.md 规范、17 个示例 skill 的设计模式，以及如何在 Claude Code \u002F Claude.ai \u002F API 中使用",[58,88,22,23],"Claude",2090,{"slug":91,"title":92,"description":93,"pub_date":56,"tags":94,"draft":14,"word_count":96},"karpathy-claude-code-guidelines","Karpathy 的 LLM 编码批评与 CLAUDE.md 最佳实践","基于 Andrej Karpathy 对 LLM 编程助手的观察，forrestchang 提炼出一个 CLAUDE.md 文件，4 条原则解决 AI 编码的典型失控问题：乱猜假设、过度设计、乱改代码、目标不清",[58,88,95,60],"Claude Code",2699,{"slug":98,"title":99,"description":100,"pub_date":56,"tags":101,"draft":14,"word_count":106},"typescript-advanced-patterns","TypeScript 高级模式：让类型系统为你工作","基础 TS 会了但类型总是 any？条件类型、映射类型、模板字面量类型、infer 关键字才是 TS 的真正威力。",[102,103,104,105],"typescript","类型系统","前端工程","高级模式",1419,{"slug":108,"title":109,"description":110,"pub_date":56,"tags":111,"draft":14,"word_count":115},"linux-performance-tuning","Linux 性能调优实战：从 top 到 perf 的完整工具链","遇到性能问题不知道从哪下手？这篇建立系统化的排查思路，从 CPU\u002F内存\u002FIO\u002F网络逐层分析。",[35,112,113,114],"性能","运维","系统编程",1524,{"slug":117,"title":118,"description":119,"pub_date":56,"tags":120,"draft":14,"word_count":125},"python-functional-programming","Python 函数式编程：map\u002Ffilter\u002Freduce 之外","Python 不是纯函数式语言，但 functools、itertools、偏函数、闭包这些工具用好了能让代码简洁一个量级。",[121,122,123,124],"python","函数式","闭包","装饰器",1867,{"slug":127,"title":128,"description":129,"pub_date":56,"tags":130,"draft":14,"word_count":134},"python-oop-guide","Python 面向对象：__init__ 之外你需要知道的","Python OOP 不只是 class + __init__，魔术方法、描述符、元类才是真正的武器。",[121,131,132,133],"OOP","面向对象","魔术方法",1792,{"slug":136,"title":137,"description":138,"pub_date":56,"tags":139,"draft":14,"word_count":142},"python-data-structures","Python 内置数据结构深度解析","list、dict、set、tuple 不只是数据容器，搞懂它们的底层实现和时间复杂度，才能写出高性能 Python。",[121,140,112,141],"数据结构","算法",1517,{"slug":144,"title":145,"description":146,"pub_date":56,"tags":147,"draft":14,"word_count":150},"python-basics-quick-start","Python 快速上手：写给有编程基础的人","已经会其他语言，想快速掌握 Python 的语法特性和思维方式，这篇是捷径。",[121,148,149],"入门","基础",1607,{"slug":152,"title":153,"description":154,"pub_date":56,"tags":155,"draft":14,"word_count":159},"python-dataclass-pydantic","Python dataclass vs Pydantic：数据类选型指南","dataclass 是标准库的轻量选择，Pydantic v2 是带验证的重武器，什么时候用哪个，这篇说清楚。",[121,156,157,158],"dataclass","pydantic","数据验证",1323,{"slug":161,"title":162,"description":163,"pub_date":56,"tags":164,"draft":14,"word_count":168},"python-asyncio-practical","Python asyncio 实战：从回调地狱到协程优雅","asyncio 是 Python 异步编程的核心，搞懂 event loop、Task、gather 这些概念才能写出真正高效的异步代码。",[121,165,166,167],"asyncio","并发","网络编程",1258,{"slug":170,"title":171,"description":172,"pub_date":56,"tags":173,"draft":14,"word_count":177},"python-type-hints-guide","Python 类型注解完全指南：从入门到实践","Python 3.5+ 引入类型注解，配合 mypy\u002Fpyright 让 Python 也能享受静态类型检查的好处。",[121,174,175,176],"typescript-style","type-hints","工具链",1102,{"slug":179,"title":180,"description":181,"pub_date":182,"tags":183,"draft":14,"word_count":187},"pwa-install-update-button","PWA 踩坑：为什么安装按钮从来不出现","从 beforeinstallprompt 到 Service Worker waiting，把 PWA 的安装与更新提示真正做对","2026-05-02",[184,185,186],"pwa","javascript","web",1683,{"slug":189,"title":190,"description":191,"pub_date":192,"tags":193,"draft":14,"word_count":194},"openclaw-vs-hermes-agent","OpenClaw vs Hermes Agent：两个本地优先 Agent 的设计差异","OpenClaw（Novita AI）和 Hermes Agent（Nous Research）都是本地运行的个人 AI Agent，但在记忆系统、技能学习、运行环境和模型生态上走了不同的路。深入对比两种架构的核心差异。","2026-05-01",[58,22,59],1679,{"slug":196,"title":197,"description":198,"pub_date":192,"tags":199,"draft":14,"word_count":203},"cpp-random-design-patterns","C++ 设计模式实战：RAII、观察者、工厂","用现代 C++（C++17\u002F20）实现三种高频设计模式：RAII 资源管理、观察者模式事件系统、工厂模式插件架构。每种模式给出问题场景、实现代码和真实工程案例。",[11,200,201,202],"设计模式","c++17","工程",2613,{"slug":205,"title":206,"description":207,"pub_date":192,"tags":208,"draft":14,"word_count":209},"data-structures-fundamentals","数据结构基础：从数组到红黑树","系统梳理常用数据结构的核心原理、时间复杂度和适用场景。数组、链表、栈、队列、哈希表、二叉树、堆、图，每种结构附实现要点和 C++ 代码片段。",[140,141,11,149],3004,{"slug":211,"title":212,"description":213,"pub_date":214,"tags":215,"draft":14,"word_count":216},"ai-agent-what-is","什么是 AI Agent？从 LLM 到自主执行","LLM 本身是无状态问答机，Agent 是什么让它’动’起来的？本文深入解析 Agent 的四个核心能力、ReAct 框架、工具调用原理，以及主流框架横向对比。","2026-04-30",[58,22,59],2116,{"slug":218,"title":219,"description":220,"pub_date":214,"tags":221,"draft":14,"word_count":222},"ai-agent-memory","AI Agent 的记忆系统：从上下文窗口到长期记忆","深入拆解 AI Agent 的四种记忆类型、上下文窗口压缩策略、RAG 向量检索原理，以及三种典型失败模式和工程选型建议。",[58,22,67],2052,{"slug":224,"title":225,"description":226,"pub_date":214,"tags":227,"draft":14,"word_count":231},"network-proxy-vpn-guide","代理与翻墙技术原理：从 HTTP 代理到现代协议","深入解析代理与 VPN 的本质区别，梳理从 SOCKS5 到 Shadowsocks、V2Ray\u002FXray、Hysteria2 的协议演进，以及机场订阅的技术本质。",[228,229,230],"网络","代理","协议",2148,{"slug":233,"title":234,"description":235,"pub_date":214,"tags":236,"draft":14,"word_count":150},"algorithm-binary-search","二分查找：永远写不对？记住这个模板","彻底搞清楚二分查找的边界问题：闭区间和左闭右开两套模板、三道经典 LeetCode 题目完整 C++ 实现，以及二分答案的进阶思路。",[141,237,238,11],"二分查找","leetcode",{"slug":240,"title":241,"description":242,"pub_date":214,"tags":243,"draft":14,"word_count":245},"algorithm-sliding-window","滑动窗口算法：从暴力到 O(n) 的思维跃迁","系统讲解滑动窗口算法的核心模板、适用题型，配合三道经典 LeetCode 题目的完整 C++ 实现，彻底理解双指针收缩思路。",[141,244,238,11],"滑动窗口",1943,{"slug":247,"title":248,"description":249,"pub_date":214,"tags":250,"draft":14,"word_count":253},"network-clash-config","Clash \u002F Mihomo 配置详解：规则、策略组与分流","深入解析 Clash\u002FMihomo 的核心配置结构，包括代理节点、策略组类型、规则优先级、DNS fake-ip 模式，以及一份实用的完整配置模板。",[228,251,229,252],"clash","配置",1292,{"slug":255,"title":256,"description":257,"pub_date":258,"tags":259,"draft":14,"word_count":263},"hid-hotplug","HID 设备热插拔检测：从 udev 到 node-hid","在 Linux 上用 node-hid + usb 库实现可靠的 USB HID 设备热插拔检测，踩坑记录","2026-04-28",[11,260,35,261,262],"hid","nodejs","electron",2039,{"slug":265,"title":266,"description":267,"pub_date":268,"tags":269,"draft":14,"word_count":272},"electron-ipc-types","Electron IPC 类型安全：从 any 到完全类型化","用 TypeScript 泛型封装 Electron IPC，彻底消灭 any，preload 契约集中管理","2026-04-25",[262,102,270,271],"ipc","vue",1446,{"slug":274,"title":275,"description":276,"pub_date":277,"tags":278,"draft":14,"word_count":281},"element-plus-popover-hide","手动关闭多个 el-popover（不用 v-model:visible）","通过 ref + Reflect.get 调用 hide() 方法手动关闭 Element Plus Popover，解释 Vue3 Proxy 导致无法直接调用实例方法的原因。","2024-10-25",[271,279,280],"element-plus","vue3",1321,{"slug":283,"title":284,"description":285,"pub_date":286,"tags":287,"draft":14,"word_count":291},"vite-vue3-ts-elementplus-pinia","用 Vite+（vp）从零搭建 Vue3 + TypeScript + Element Plus + Pinia + Vue Router","使用 Vite+ 统一工具链（vp）一条命令搭建 Vue3 全家桶，涵盖按需导入、Pinia store、路由配置，以及常见坑的解决方案。","2024-08-27",[271,288,102,279,289,290],"vite","pinia","vite-plus",1960,{"slug":293,"title":294,"description":295,"pub_date":296,"tags":297,"draft":14,"word_count":301},"cef-lnk2038-iterator-debug-level","CEF LNK2038：解决 _ITERATOR_DEBUG_LEVEL 不匹配错误","分析 CEF（Chromium Embedded Framework）集成时出现的 LNK2038 _ITERATOR_DEBUG_LEVEL 链接错误，从根本原因到解决方案的完整指南。","2024-05-07",[11,298,299,300],"CEF","Visual Studio","链接错误",1509,{"slug":303,"title":304,"description":305,"pub_date":306,"tags":307,"draft":14,"word_count":311},"npm-electron-install-fix","彻底解决 npm 安装 Electron 失败的问题","分析 npm install electron 失败的根本原因（下载二进制超时\u002F被墙），通过国内镜像（npmmirror）彻底解决，并介绍多种备选方案和常见错误排查。","2024-03-01",[262,308,309,310],"npm","前端工具链","国内镜像",1494,{"slug":313,"title":314,"description":315,"pub_date":316,"tags":317,"draft":14,"word_count":319},"git-out-of-memory","解决 git 报错：Fatal: Out of memory, malloc failed","分析 git 大仓库操作时出现 Out of memory malloc failed 的根本原因，通过调整 pack.windowMemory、http.postBuffer 和 git repack 彻底解决。","2024-01-31",[74,35,318],"工具",2244,{"slug":321,"title":322,"description":323,"pub_date":324,"tags":325,"draft":14,"word_count":329},"vmware-tools-install","在 VMware 虚拟机中安装 open-vm-tools 完整指南","详解 VMware Tools 的作用、open-vm-tools 与官方 VMware Tools 的区别，以及在 Ubuntu 虚拟机中安装并生效的完整步骤和常见问题排查。","2023-11-21",[326,35,327,328],"VMware","Ubuntu","虚拟机",2523,{"slug":331,"title":332,"description":333,"pub_date":334,"tags":335,"draft":14,"word_count":340},"load-balancing-algorithms","负载均衡算法完全指南：从轮询到一致性哈希","系统梳理静态与动态负载均衡算法，涵盖轮询、随机、权重、IP Hash、一致性 Hash、最少连接、最快响应等，并对比 Nginx、Dubbo、Spring Cloud LoadBalancer 的实现差异。","2023-11-15",[336,337,338,339],"分布式","负载均衡","Nginx","微服务",1764,{"slug":342,"title":343,"description":344,"pub_date":345,"tags":346,"draft":14,"word_count":349},"win-cw2a-ca2w","ATL 字符串转换：CW2A 与 CA2W 完全指南","详解 ATL 宏 CW2A\u002FCA2W 在 Unicode 与 ANSI 之间的字符串转换用法、头文件依赖、USES_CONVERSION 宏的作用与常见陷阱。","2023-06-09",[11,12,347,348],"ATL","字符串",1665,{"slug":351,"title":352,"description":353,"pub_date":345,"tags":354,"draft":14,"word_count":358},"csharp-sendmessage-cpp","C# 通过 SendMessage 向 C++ 窗口发送消息与字符串","使用 P\u002FInvoke 调用 user32.dll 的 SendMessage，从 C# 发送自定义 WM_USER 消息及字符串指针给 C++ 原生窗口，并在 C++ 侧正确接收和转换。",[355,11,12,356,357],"C#","互操作","PInvoke",1554,{"slug":4,"title":5,"description":6,"pub_date":9,"tags":360,"draft":14,"word_count":361},[11,12,13],1823,{"slug":363,"title":364,"description":365,"pub_date":9,"tags":366,"draft":14,"word_count":367},"exe-dll-single-package","将 EXE 和 DLL 打包成单一可执行文件","介绍两种将 exe 和依赖 dll 打包成单文件的方案：Enigma Virtual Box 和 WinRAR 自解压，适合发布 Windows 桌面程序时简化分发流程。",[12,11,318],1619,{"slug":369,"title":370,"description":371,"pub_date":9,"tags":372,"draft":14,"word_count":375},"cpp-random-mt19937","C++ 现代随机数生成：用 mt19937 彻底告别 rand()","深入讲解为什么 rand() 不够用，以及如何用 C++11 的 \u003Crandom> 库正确生成高质量随机数，涵盖 mt19937、各种分布和线程安全。",[11,373,374],"c++11","random",1549,{"slug":377,"title":378,"description":379,"pub_date":380,"tags":381,"draft":14,"word_count":383},"win-startup-registry","C++ 实现程序开机自启动：注册表方式详解","通过操作 Windows 注册表 Run 键实现程序开机自启动，包括 HKCU 与 HKLM 区别、完整封装代码、工作目录问题和 UAC 权限处理。","2022-12-26",[12,11,382],"registry",1201,{"slug":385,"title":386,"description":387,"pub_date":388,"tags":389,"draft":14,"word_count":391},"mfc-cstring-wparam","MFC 中 CString 与 WPARAM 之间的转换","详解 MFC 消息传递中 CString 无法直接强转为 WPARAM 的原因，以及两种正确的转换方案，并介绍结构体指针传递的正确姿势。","2022-11-25",[390,11,12],"mfc",1546,{"slug":393,"title":394,"description":395,"pub_date":396,"tags":397,"draft":14,"word_count":399},"duilib-static-build","正确编译 Duilib 静态库：避免 ATL 依赖和链接错误","详解如何用 DuiLib_Static.vcxproj 编译 Duilib 静态库，解决 VARIANT 未定义、Unicode 配置不匹配和 ATL 依赖等常见问题。","2022-08-24",[11,398,12,390],"duilib",2639,{"slug":401,"title":402,"description":403,"pub_date":404,"tags":405,"draft":14,"word_count":407},"mfc-dpi-adaptive","MFC 界面自适应不同分辨率","MFC 对话框程序实现控件和字体随分辨率自动缩放的完整方案，附 DPI Awareness 配置说明","2022-08-17",[390,11,12,406],"dpi",1414,{"slug":409,"title":410,"description":411,"pub_date":412,"tags":413,"draft":14,"word_count":414},"mfc-drag-window","MFC 无标题栏窗口客户区拖动：三种方法对比","MFC 对话框去掉标题栏后如何实现拖动移动窗口，三种方案完整实现与适用场景分析","2022-08-16",[390,11,12],1633,{"slug":416,"title":417,"description":418,"pub_date":419,"tags":420,"draft":14,"word_count":422},"algorithm-number-complement","整数的补数：位运算掩码解法","LeetCode 476 题，用掩码 XOR 实现整数补数，附 C++\u002FPython\u002FJava 三种实现及补数与补码的区别","2021-03-08",[141,421,238],"位运算",1374,[]]