[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f5VILLv-SLXTwdv1TwL0f8makcOG5lEVmAwXIkByOaz0":3,"$fJU-4tot_gC5fDkujNeoE-cGsdMy5V_KcdUXLuAnTFgw":15,"$fpE5dWHFbJuMFAeeGYUgmc3T5dYY7VW23koQg6YM1evw":423},{"slug":4,"title":5,"description":6,"content":7,"content_html":8,"pub_date":9,"tags":10,"draft":14},"mfc-drag-window","MFC 无标题栏窗口客户区拖动：三种方法对比","MFC 对话框去掉标题栏后如何实现拖动移动窗口，三种方案完整实现与适用场景分析","# MFC 无标题栏窗口客户区拖动：三种方法对比\n\n现代 GUI 应用越来越多地使用自定义标题栏或完全无标题栏的设计，以实现更流畅的视觉效果。但去掉标题栏后，用户就失去了拖动窗口的能力，需要开发者手动实现。本文介绍三种在 MFC 中实现无标题栏窗口客户区拖动的方法，并进行详细对比。\n\n## 无标题栏窗口需求场景\n\n以下场景常需要自定义拖动：\n\n1. **全屏覆盖式 UI**：如音乐播放器、视频播放器\n2. **悬浮工具窗口**：工具栏、调色板\n3. **自定义皮肤程序**：整个窗口外观由程序绘制\n4. **现代风格应用**：仿照 Windows 11 磁贴风格\n5. **游戏内叠加层**：如游戏内聊天框\n\n去掉标题栏通常通过修改窗口样式：\n\n```cpp\n\u002F\u002F 去掉标题栏（WS_CAPTION 包含 WS_BORDER）\nModifyStyle(WS_CAPTION | WS_THICKFRAME, 0);\n\n\u002F\u002F 或者在 PreCreateWindow 中设置\ncs.style &= ~(WS_CAPTION | WS_THICKFRAME);\ncs.style |= WS_POPUP;\n```\n\n## 方法一：WM_NCHITTEST 欺骗法\n\n### 原理\n\nWindows 用 `WM_NCHITTEST` 来确定鼠标点击了窗口的哪个区域（标题栏、边框、客户区等）。非客户区（如标题栏）的拖动由 Windows 系统处理。\n\n通过拦截 `WM_NCHITTEST`，当鼠标在客户区时返回 `HTCAPTION`（假装是标题栏），让 Windows 以为用户在拖动标题栏，从而触发内置的窗口移动逻辑。\n\n### 实现\n\n```cpp\n\u002F\u002F 在消息映射中添加\nBEGIN_MESSAGE_MAP(CMyDialog, CDialog)\n    ON_WM_NCHITTEST()\nEND_MESSAGE_MAP()\n\nLRESULT CMyDialog::OnNcHitTest(CPoint point)\n{\n    LRESULT hit = CDialog::OnNcHitTest(point);\n    \n    \u002F\u002F 如果点击在客户区，假装点击了标题栏\n    if (hit == HTCLIENT) {\n        \u002F\u002F 可以在这里添加例外区域（如按钮区域不拖动）\n        return HTCAPTION;\n    }\n    \n    return hit;\n}\n```\n\n### 高级版：排除控件区域\n\n```cpp\nLRESULT CMyDialog::OnNcHitTest(CPoint point)\n{\n    LRESULT hit = CDialog::OnNcHitTest(point);\n    \n    if (hit == HTCLIENT) {\n        \u002F\u002F 将屏幕坐标转为客户区坐标\n        CPoint clientPt = point;\n        ScreenToClient(&clientPt);\n        \n        \u002F\u002F 排除按钮区域（不触发拖动）\n        CRect btnRect;\n        m_btnClose.GetWindowRect(&btnRect);\n        ScreenToClient(&btnRect);\n        \n        if (!btnRect.PtInRect(clientPt)) {\n            return HTCAPTION;\n        }\n    }\n    \n    return hit;\n}\n```\n\n### 优缺点\n\n- ✅ 代码极简，Windows 处理所有移动逻辑（包括边界检测、磁吸等）\n- ✅ 自动支持窗口磁吸到屏幕边缘\n- ✅ 与系统的窗口动画兼容\n- ❌ 整个客户区都变成了\"标题栏\"，右键菜单行为会改变\n- ❌ 双击客户区会触发最大化（如果有 WS_MAXIMIZEBOX）\n- ❌ 无法精细控制可拖动区域\n\n## 方法二：鼠标消息追踪法（WM_LBUTTONDOWN\u002FMOUSEMOVE）\n\n### 原理\n\n手动追踪鼠标按下、移动、抬起事件，计算偏移量并移动窗口。\n\n### 实现\n\n```cpp\n\u002F\u002F 头文件中添加成员变量\nclass CMyDialog : public CDialog {\n    BOOL m_bDragging;\n    CPoint m_ptDragStart;  \u002F\u002F 鼠标按下时的屏幕坐标\n    CPoint m_ptWndStart;   \u002F\u002F 鼠标按下时窗口左上角屏幕坐标\n    \u002F\u002F ...\n};\n\n\u002F\u002F 初始化\nCMyDialog::CMyDialog(CWnd* pParent)\n    : CDialog(IDD_MYDIALOG, pParent)\n    , m_bDragging(FALSE)\n{\n}\n\nBEGIN_MESSAGE_MAP(CMyDialog, CDialog)\n    ON_WM_LBUTTONDOWN()\n    ON_WM_LBUTTONUP()\n    ON_WM_MOUSEMOVE()\n    ON_WM_CAPTURECHANGED()\nEND_MESSAGE_MAP()\n\nvoid CMyDialog::OnLButtonDown(UINT nFlags, CPoint point)\n{\n    \u002F\u002F 可选：排除某些区域不触发拖动\n    \u002F\u002F （point 是客户区坐标）\n    \n    m_bDragging = TRUE;\n    \n    \u002F\u002F 记录起始位置\n    m_ptDragStart = point;\n    ClientToScreen(&m_ptDragStart);  \u002F\u002F 转为屏幕坐标\n    \n    CRect rect;\n    GetWindowRect(&rect);\n    m_ptWndStart = rect.TopLeft();\n    \n    \u002F\u002F 捕获鼠标（即使鼠标移出窗口也能收到消息）\n    SetCapture();\n    \n    CDialog::OnLButtonDown(nFlags, point);\n}\n\nvoid CMyDialog::OnLButtonUp(UINT nFlags, CPoint point)\n{\n    if (m_bDragging) {\n        m_bDragging = FALSE;\n        ReleaseCapture();\n    }\n    \n    CDialog::OnLButtonUp(nFlags, point);\n}\n\nvoid CMyDialog::OnMouseMove(UINT nFlags, CPoint point)\n{\n    if (m_bDragging && (nFlags & MK_LBUTTON)) {\n        \u002F\u002F 计算当前鼠标屏幕坐标\n        CPoint ptCurrent = point;\n        ClientToScreen(&ptCurrent);\n        \n        \u002F\u002F 计算位移\n        int dx = ptCurrent.x - m_ptDragStart.x;\n        int dy = ptCurrent.y - m_ptDragStart.y;\n        \n        \u002F\u002F 移动窗口\n        SetWindowPos(nullptr,\n            m_ptWndStart.x + dx,\n            m_ptWndStart.y + dy,\n            0, 0,\n            SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE\n        );\n    }\n    \n    CDialog::OnMouseMove(nFlags, point);\n}\n\nvoid CMyDialog::OnCaptureChanged(CWnd* pWnd)\n{\n    \u002F\u002F 如果鼠标捕获被其他窗口夺走，停止拖动\n    m_bDragging = FALSE;\n    CDialog::OnCaptureChanged(pWnd);\n}\n```\n\n### 优缺点\n\n- ✅ 完全控制拖动区域（可精确排除按钮等控件）\n- ✅ 不影响右键菜单等消息\n- ✅ 可以添加自定义逻辑（如限制移动范围）\n- ❌ 代码量较多\n- ❌ 不会自动支持屏幕边缘磁吸（需自行实现）\n- ❌ 在某些 DPI 场景下需要额外处理\n\n## 方法三：SendMessage WM_SYSCOMMAND SC_MOVE 法\n\n### 原理\n\n直接发送 `WM_SYSCOMMAND` + `SC_MOVE` 消息，模拟用户点击了系统菜单的\"移动\"命令，让系统接管移动逻辑。\n\n```cpp\nvoid CMyDialog::OnLButtonDown(UINT nFlags, CPoint point)\n{\n    \u002F\u002F 释放当前鼠标捕获\n    ReleaseCapture();\n    \n    \u002F\u002F 发送系统移动命令\n    SendMessage(WM_SYSCOMMAND, SC_MOVE | HTCAPTION, 0);\n    \n    \u002F\u002F 注意：SendMessage 会阻塞直到移动结束\n    \u002F\u002F 这里之后的代码在移动完成后才执行\n}\n```\n\n### 注意事项\n\n这个方法本质上和方法一（WM_NCHITTEST）效果相同，但调用时机不同。\n\n- `SC_MOVE | HTCAPTION`：模拟从标题栏开始移动\n- 调用后 Windows 进入移动模式，直到用户松开鼠标\n\n```cpp\n\u002F\u002F 更完整的写法，防止在某些情况下出现问题\nvoid CMyDialog::OnLButtonDown(UINT nFlags, CPoint point)\n{\n    CDialog::OnLButtonDown(nFlags, point);\n    \n    if (GetCapture() == this) {\n        ReleaseCapture();\n    }\n    \n    PostMessage(WM_SYSCOMMAND, SC_MOVE | HTCAPTION, 0);\n}\n```\n\n使用 `PostMessage` 而非 `SendMessage` 可以避免在消息处理中递归调用。\n\n### 优缺点\n\n- ✅ 代码简洁\n- ✅ 支持系统拖动特性（磁吸等）\n- ❌ 与方法一本质相同，局限性类似\n- ❌ 不够直观\n\n## 三种方法对比表格\n\n| 对比项 | WM_NCHITTEST 法 | 鼠标消息追踪法 | WM_SYSCOMMAND 法 |\n|--------|-----------------|----------------|-----------------|\n| 代码量 | 极少（~10 行） | 较多（~60 行） | 少（~5 行） |\n| 实现复杂度 | 低 | 中 | 低 |\n| 拖动区域控制 | 中（需过滤控件） | 高（完全自定义） | 中 |\n| 系统磁吸支持 | ✅ 自动 | ❌ 需手动 | ✅ 自动 |\n| 右键菜单影响 | 有 | 无 | 有 |\n| 双击最大化影响 | 有 | 无 | 有 |\n| DPI 友好性 | ✅ 系统处理 | 需注意 | ✅ 系统处理 |\n| 自定义动画 | ❌ | ✅ 可以 | ❌ |\n| 适用场景 | 简单无标题栏窗口 | 复杂自定义 UI | 简单场景 |\n\n## DPI 适配注意事项\n\n在方法二（鼠标消息追踪法）中，DPI 缩放可能导致拖动不准确：\n\n```cpp\nvoid CMyDialog::OnMouseMove(UINT nFlags, CPoint point)\n{\n    if (m_bDragging && (nFlags & MK_LBUTTON)) {\n        \u002F\u002F Windows 10 1607+ 的 Per-Monitor DPI v2\n        \u002F\u002F ClientToScreen 在 DPI 感知应用中通常是正确的\n        \u002F\u002F 但如果使用了 DPI 虚拟化，需要额外处理\n        \n        CPoint ptCurrent = point;\n        ClientToScreen(&ptCurrent);  \u002F\u002F 已经处理了 DPI 缩放\n        \n        int dx = ptCurrent.x - m_ptDragStart.x;\n        int dy = ptCurrent.y - m_ptDragStart.y;\n        \n        SetWindowPos(nullptr,\n            m_ptWndStart.x + dx,\n            m_ptWndStart.y + dy,\n            0, 0,\n            SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE\n        );\n    }\n}\n```\n\n如果应用在 manifest 中声明了 `PerMonitorV2` DPI 感知，`ClientToScreen` 和 `SetWindowPos` 使用的是物理像素，通常不需要额外换算。\n\n### manifest 配置\n\n```xml\n\u003C!-- app.manifest -->\n\u003Capplication xmlns=\"urn:schemas-microsoft-com:asm.v3\">\n    \u003CwindowsSettings>\n        \u003CdpiAwareness xmlns=\"http:\u002F\u002Fschemas.microsoft.com\u002FSMI\u002F2016\u002FWindowsSettings\">\n            PerMonitorV2\n        \u003C\u002FdpiAwareness>\n    \u003C\u002FwindowsSettings>\n\u003C\u002Fapplication>\n```\n\n## 推荐选择\n\n- **简单工具窗口**：用方法一（WM_NCHITTEST），代码最少\n- **复杂自定义界面**（有按钮、控件等需要精确点击）：用方法二（鼠标消息追踪），控制最精细\n- **需要限制移动范围**：用方法二，可在 `OnMouseMove` 中添加边界约束\n\n实际项目中，方法一和方法二最为常用，可根据 UI 复杂程度灵活选择。\n","\u003Ch1>MFC 无标题栏窗口客户区拖动：三种方法对比\u003C\u002Fh1>\n\u003Cp>现代 GUI 应用越来越多地使用自定义标题栏或完全无标题栏的设计，以实现更流畅的视觉效果。但去掉标题栏后，用户就失去了拖动窗口的能力，需要开发者手动实现。本文介绍三种在 MFC 中实现无标题栏窗口客户区拖动的方法，并进行详细对比。\u003C\u002Fp>\n\u003Ch2 id=\"无标题栏窗口需求场景\">无标题栏窗口需求场景\u003C\u002Fh2>\n\u003Cp>以下场景常需要自定义拖动：\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>全屏覆盖式 UI\u003C\u002Fstrong>：如音乐播放器、视频播放器\u003C\u002Fli>\n\u003Cli>\u003Cstrong>悬浮工具窗口\u003C\u002Fstrong>：工具栏、调色板\u003C\u002Fli>\n\u003Cli>\u003Cstrong>自定义皮肤程序\u003C\u002Fstrong>：整个窗口外观由程序绘制\u003C\u002Fli>\n\u003Cli>\u003Cstrong>现代风格应用\u003C\u002Fstrong>：仿照 Windows 11 磁贴风格\u003C\u002Fli>\n\u003Cli>\u003Cstrong>游戏内叠加层\u003C\u002Fstrong>：如游戏内聊天框\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>去掉标题栏通常通过修改窗口样式：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F 去掉标题栏（WS_CAPTION 包含 WS_BORDER）\nModifyStyle(WS_CAPTION | WS_THICKFRAME, 0);\n\n\u002F\u002F 或者在 PreCreateWindow 中设置\ncs.style &amp;= ~(WS_CAPTION | WS_THICKFRAME);\ncs.style |= WS_POPUP;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"方法一-wm_nchittest-欺骗法\">方法一：WM_NCHITTEST 欺骗法\u003C\u002Fh2>\n\u003Ch3 id=\"原理\">原理\u003C\u002Fh3>\n\u003Cp>Windows 用 \u003Ccode>WM_NCHITTEST\u003C\u002Fcode> 来确定鼠标点击了窗口的哪个区域（标题栏、边框、客户区等）。非客户区（如标题栏）的拖动由 Windows 系统处理。\u003C\u002Fp>\n\u003Cp>通过拦截 \u003Ccode>WM_NCHITTEST\u003C\u002Fcode>，当鼠标在客户区时返回 \u003Ccode>HTCAPTION\u003C\u002Fcode>（假装是标题栏），让 Windows 以为用户在拖动标题栏，从而触发内置的窗口移动逻辑。\u003C\u002Fp>\n\u003Ch3 id=\"实现\">实现\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F 在消息映射中添加\nBEGIN_MESSAGE_MAP(CMyDialog, CDialog)\n    ON_WM_NCHITTEST()\nEND_MESSAGE_MAP()\n\nLRESULT CMyDialog::OnNcHitTest(CPoint point)\n{\n    LRESULT hit = CDialog::OnNcHitTest(point);\n    \n    \u002F\u002F 如果点击在客户区，假装点击了标题栏\n    if (hit == HTCLIENT) {\n        \u002F\u002F 可以在这里添加例外区域（如按钮区域不拖动）\n        return HTCAPTION;\n    }\n    \n    return hit;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"高级版-排除控件区域\">高级版：排除控件区域\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-cpp\">LRESULT CMyDialog::OnNcHitTest(CPoint point)\n{\n    LRESULT hit = CDialog::OnNcHitTest(point);\n    \n    if (hit == HTCLIENT) {\n        \u002F\u002F 将屏幕坐标转为客户区坐标\n        CPoint clientPt = point;\n        ScreenToClient(&amp;clientPt);\n        \n        \u002F\u002F 排除按钮区域（不触发拖动）\n        CRect btnRect;\n        m_btnClose.GetWindowRect(&amp;btnRect);\n        ScreenToClient(&amp;btnRect);\n        \n        if (!btnRect.PtInRect(clientPt)) {\n            return HTCAPTION;\n        }\n    }\n    \n    return hit;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"优缺点\">优缺点\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>✅ 代码极简，Windows 处理所有移动逻辑（包括边界检测、磁吸等）\u003C\u002Fli>\n\u003Cli>✅ 自动支持窗口磁吸到屏幕边缘\u003C\u002Fli>\n\u003Cli>✅ 与系统的窗口动画兼容\u003C\u002Fli>\n\u003Cli>❌ 整个客户区都变成了&quot;标题栏&quot;，右键菜单行为会改变\u003C\u002Fli>\n\u003Cli>❌ 双击客户区会触发最大化（如果有 WS_MAXIMIZEBOX）\u003C\u002Fli>\n\u003Cli>❌ 无法精细控制可拖动区域\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"方法二-鼠标消息追踪法-wm_lbuttondown-mousemove\">方法二：鼠标消息追踪法（WM_LBUTTONDOWN\u002FMOUSEMOVE）\u003C\u002Fh2>\n\u003Ch3 id=\"原理\">原理\u003C\u002Fh3>\n\u003Cp>手动追踪鼠标按下、移动、抬起事件，计算偏移量并移动窗口。\u003C\u002Fp>\n\u003Ch3 id=\"实现\">实现\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F 头文件中添加成员变量\nclass CMyDialog : public CDialog {\n    BOOL m_bDragging;\n    CPoint m_ptDragStart;  \u002F\u002F 鼠标按下时的屏幕坐标\n    CPoint m_ptWndStart;   \u002F\u002F 鼠标按下时窗口左上角屏幕坐标\n    \u002F\u002F ...\n};\n\n\u002F\u002F 初始化\nCMyDialog::CMyDialog(CWnd* pParent)\n    : CDialog(IDD_MYDIALOG, pParent)\n    , m_bDragging(FALSE)\n{\n}\n\nBEGIN_MESSAGE_MAP(CMyDialog, CDialog)\n    ON_WM_LBUTTONDOWN()\n    ON_WM_LBUTTONUP()\n    ON_WM_MOUSEMOVE()\n    ON_WM_CAPTURECHANGED()\nEND_MESSAGE_MAP()\n\nvoid CMyDialog::OnLButtonDown(UINT nFlags, CPoint point)\n{\n    \u002F\u002F 可选：排除某些区域不触发拖动\n    \u002F\u002F （point 是客户区坐标）\n    \n    m_bDragging = TRUE;\n    \n    \u002F\u002F 记录起始位置\n    m_ptDragStart = point;\n    ClientToScreen(&amp;m_ptDragStart);  \u002F\u002F 转为屏幕坐标\n    \n    CRect rect;\n    GetWindowRect(&amp;rect);\n    m_ptWndStart = rect.TopLeft();\n    \n    \u002F\u002F 捕获鼠标（即使鼠标移出窗口也能收到消息）\n    SetCapture();\n    \n    CDialog::OnLButtonDown(nFlags, point);\n}\n\nvoid CMyDialog::OnLButtonUp(UINT nFlags, CPoint point)\n{\n    if (m_bDragging) {\n        m_bDragging = FALSE;\n        ReleaseCapture();\n    }\n    \n    CDialog::OnLButtonUp(nFlags, point);\n}\n\nvoid CMyDialog::OnMouseMove(UINT nFlags, CPoint point)\n{\n    if (m_bDragging &amp;&amp; (nFlags &amp; MK_LBUTTON)) {\n        \u002F\u002F 计算当前鼠标屏幕坐标\n        CPoint ptCurrent = point;\n        ClientToScreen(&amp;ptCurrent);\n        \n        \u002F\u002F 计算位移\n        int dx = ptCurrent.x - m_ptDragStart.x;\n        int dy = ptCurrent.y - m_ptDragStart.y;\n        \n        \u002F\u002F 移动窗口\n        SetWindowPos(nullptr,\n            m_ptWndStart.x + dx,\n            m_ptWndStart.y + dy,\n            0, 0,\n            SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE\n        );\n    }\n    \n    CDialog::OnMouseMove(nFlags, point);\n}\n\nvoid CMyDialog::OnCaptureChanged(CWnd* pWnd)\n{\n    \u002F\u002F 如果鼠标捕获被其他窗口夺走，停止拖动\n    m_bDragging = FALSE;\n    CDialog::OnCaptureChanged(pWnd);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"优缺点\">优缺点\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>✅ 完全控制拖动区域（可精确排除按钮等控件）\u003C\u002Fli>\n\u003Cli>✅ 不影响右键菜单等消息\u003C\u002Fli>\n\u003Cli>✅ 可以添加自定义逻辑（如限制移动范围）\u003C\u002Fli>\n\u003Cli>❌ 代码量较多\u003C\u002Fli>\n\u003Cli>❌ 不会自动支持屏幕边缘磁吸（需自行实现）\u003C\u002Fli>\n\u003Cli>❌ 在某些 DPI 场景下需要额外处理\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"方法三-sendmessage-wm_syscommand-sc_move-法\">方法三：SendMessage WM_SYSCOMMAND SC_MOVE 法\u003C\u002Fh2>\n\u003Ch3 id=\"原理\">原理\u003C\u002Fh3>\n\u003Cp>直接发送 \u003Ccode>WM_SYSCOMMAND\u003C\u002Fcode> + \u003Ccode>SC_MOVE\u003C\u002Fcode> 消息，模拟用户点击了系统菜单的&quot;移动&quot;命令，让系统接管移动逻辑。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">void CMyDialog::OnLButtonDown(UINT nFlags, CPoint point)\n{\n    \u002F\u002F 释放当前鼠标捕获\n    ReleaseCapture();\n    \n    \u002F\u002F 发送系统移动命令\n    SendMessage(WM_SYSCOMMAND, SC_MOVE | HTCAPTION, 0);\n    \n    \u002F\u002F 注意：SendMessage 会阻塞直到移动结束\n    \u002F\u002F 这里之后的代码在移动完成后才执行\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"注意事项\">注意事项\u003C\u002Fh3>\n\u003Cp>这个方法本质上和方法一（WM_NCHITTEST）效果相同，但调用时机不同。\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>SC_MOVE | HTCAPTION\u003C\u002Fcode>：模拟从标题栏开始移动\u003C\u002Fli>\n\u003Cli>调用后 Windows 进入移动模式，直到用户松开鼠标\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F 更完整的写法，防止在某些情况下出现问题\nvoid CMyDialog::OnLButtonDown(UINT nFlags, CPoint point)\n{\n    CDialog::OnLButtonDown(nFlags, point);\n    \n    if (GetCapture() == this) {\n        ReleaseCapture();\n    }\n    \n    PostMessage(WM_SYSCOMMAND, SC_MOVE | HTCAPTION, 0);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>使用 \u003Ccode>PostMessage\u003C\u002Fcode> 而非 \u003Ccode>SendMessage\u003C\u002Fcode> 可以避免在消息处理中递归调用。\u003C\u002Fp>\n\u003Ch3 id=\"优缺点\">优缺点\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>✅ 代码简洁\u003C\u002Fli>\n\u003Cli>✅ 支持系统拖动特性（磁吸等）\u003C\u002Fli>\n\u003Cli>❌ 与方法一本质相同，局限性类似\u003C\u002Fli>\n\u003Cli>❌ 不够直观\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"三种方法对比表格\">三种方法对比表格\u003C\u002Fh2>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>对比项\u003C\u002Fth>\n\u003Cth>WM_NCHITTEST 法\u003C\u002Fth>\n\u003Cth>鼠标消息追踪法\u003C\u002Fth>\n\u003Cth>WM_SYSCOMMAND 法\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>代码量\u003C\u002Ftd>\n\u003Ctd>极少（~10 行）\u003C\u002Ftd>\n\u003Ctd>较多（~60 行）\u003C\u002Ftd>\n\u003Ctd>少（~5 行）\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>实现复杂度\u003C\u002Ftd>\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\u003Ctd>中\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>系统磁吸支持\u003C\u002Ftd>\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\u003Ctd>有\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>双击最大化影响\u003C\u002Ftd>\n\u003Ctd>有\u003C\u002Ftd>\n\u003Ctd>无\u003C\u002Ftd>\n\u003Ctd>有\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>DPI 友好性\u003C\u002Ftd>\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\u003Ctd>❌\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>适用场景\u003C\u002Ftd>\n\u003Ctd>简单无标题栏窗口\u003C\u002Ftd>\n\u003Ctd>复杂自定义 UI\u003C\u002Ftd>\n\u003Ctd>简单场景\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch2 id=\"dpi-适配注意事项\">DPI 适配注意事项\u003C\u002Fh2>\n\u003Cp>在方法二（鼠标消息追踪法）中，DPI 缩放可能导致拖动不准确：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">void CMyDialog::OnMouseMove(UINT nFlags, CPoint point)\n{\n    if (m_bDragging &amp;&amp; (nFlags &amp; MK_LBUTTON)) {\n        \u002F\u002F Windows 10 1607+ 的 Per-Monitor DPI v2\n        \u002F\u002F ClientToScreen 在 DPI 感知应用中通常是正确的\n        \u002F\u002F 但如果使用了 DPI 虚拟化，需要额外处理\n        \n        CPoint ptCurrent = point;\n        ClientToScreen(&amp;ptCurrent);  \u002F\u002F 已经处理了 DPI 缩放\n        \n        int dx = ptCurrent.x - m_ptDragStart.x;\n        int dy = ptCurrent.y - m_ptDragStart.y;\n        \n        SetWindowPos(nullptr,\n            m_ptWndStart.x + dx,\n            m_ptWndStart.y + dy,\n            0, 0,\n            SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE\n        );\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>如果应用在 manifest 中声明了 \u003Ccode>PerMonitorV2\u003C\u002Fcode> DPI 感知，\u003Ccode>ClientToScreen\u003C\u002Fcode> 和 \u003Ccode>SetWindowPos\u003C\u002Fcode> 使用的是物理像素，通常不需要额外换算。\u003C\u002Fp>\n\u003Ch3 id=\"manifest-配置\">manifest 配置\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-xml\">&lt;!-- app.manifest --&gt;\n&lt;application xmlns=&quot;urn:schemas-microsoft-com:asm.v3&quot;&gt;\n    &lt;windowsSettings&gt;\n        &lt;dpiAwareness xmlns=&quot;http:\u002F\u002Fschemas.microsoft.com\u002FSMI\u002F2016\u002FWindowsSettings&quot;&gt;\n            PerMonitorV2\n        &lt;\u002FdpiAwareness&gt;\n    &lt;\u002FwindowsSettings&gt;\n&lt;\u002Fapplication&gt;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"推荐选择\">推荐选择\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>\u003Cstrong>简单工具窗口\u003C\u002Fstrong>：用方法一（WM_NCHITTEST），代码最少\u003C\u002Fli>\n\u003Cli>\u003Cstrong>复杂自定义界面\u003C\u002Fstrong>（有按钮、控件等需要精确点击）：用方法二（鼠标消息追踪），控制最精细\u003C\u002Fli>\n\u003Cli>\u003Cstrong>需要限制移动范围\u003C\u002Fstrong>：用方法二，可在 \u003Ccode>OnMouseMove\u003C\u002Fcode> 中添加边界约束\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>实际项目中，方法一和方法二最为常用，可根据 UI 复杂程度灵活选择。\u003C\u002Fp>\n","2022-08-16",[11,12,13],"mfc","cpp","windows",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,367,373,381,389,396,404,412,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 资源管理、观察者模式事件系统、工厂模式插件架构。每种模式给出问题场景、实现代码和真实工程案例。",[12,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,12,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,12],"二分查找","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,12],"滑动窗口",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",[12,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",[12,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",[12,13,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,12,13,356,357],"C#","互操作","PInvoke",1554,{"slug":360,"title":361,"description":362,"pub_date":363,"tags":364,"draft":14,"word_count":366},"win-postmessage-vector","Windows PostMessage 跨线程传递 std::vector 指针","通过 PostMessage 在 Windows 消息队列中传递 std::vector 指针，使用 reinterpret_cast 将指针装入 LPARAM，并在接收方正确释放内存。","2023-05-26",[12,13,365],"WinAPI",1823,{"slug":368,"title":369,"description":370,"pub_date":363,"tags":371,"draft":14,"word_count":372},"exe-dll-single-package","将 EXE 和 DLL 打包成单一可执行文件","介绍两种将 exe 和依赖 dll 打包成单文件的方案：Enigma Virtual Box 和 WinRAR 自解压，适合发布 Windows 桌面程序时简化分发流程。",[13,12,318],1619,{"slug":374,"title":375,"description":376,"pub_date":363,"tags":377,"draft":14,"word_count":380},"cpp-random-mt19937","C++ 现代随机数生成：用 mt19937 彻底告别 rand()","深入讲解为什么 rand() 不够用，以及如何用 C++11 的 \u003Crandom> 库正确生成高质量随机数，涵盖 mt19937、各种分布和线程安全。",[12,378,379],"c++11","random",1549,{"slug":382,"title":383,"description":384,"pub_date":385,"tags":386,"draft":14,"word_count":388},"win-startup-registry","C++ 实现程序开机自启动：注册表方式详解","通过操作 Windows 注册表 Run 键实现程序开机自启动，包括 HKCU 与 HKLM 区别、完整封装代码、工作目录问题和 UAC 权限处理。","2022-12-26",[13,12,387],"registry",1201,{"slug":390,"title":391,"description":392,"pub_date":393,"tags":394,"draft":14,"word_count":395},"mfc-cstring-wparam","MFC 中 CString 与 WPARAM 之间的转换","详解 MFC 消息传递中 CString 无法直接强转为 WPARAM 的原因，以及两种正确的转换方案，并介绍结构体指针传递的正确姿势。","2022-11-25",[11,12,13],1546,{"slug":397,"title":398,"description":399,"pub_date":400,"tags":401,"draft":14,"word_count":403},"duilib-static-build","正确编译 Duilib 静态库：避免 ATL 依赖和链接错误","详解如何用 DuiLib_Static.vcxproj 编译 Duilib 静态库，解决 VARIANT 未定义、Unicode 配置不匹配和 ATL 依赖等常见问题。","2022-08-24",[12,402,13,11],"duilib",2639,{"slug":405,"title":406,"description":407,"pub_date":408,"tags":409,"draft":14,"word_count":411},"mfc-dpi-adaptive","MFC 界面自适应不同分辨率","MFC 对话框程序实现控件和字体随分辨率自动缩放的完整方案，附 DPI Awareness 配置说明","2022-08-17",[11,12,13,410],"dpi",1414,{"slug":4,"title":5,"description":6,"pub_date":9,"tags":413,"draft":14,"word_count":414},[11,12,13],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,[]]