[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$frn29ZA2YSaxsh8fwt0of4upqz_02nJ4wpXep39ADCeE":3,"$fJU-4tot_gC5fDkujNeoE-cGsdMy5V_KcdUXLuAnTFgw":16,"$f-D__txCvTfm-nex4nhNOZRS_PzB1uXDlB0GqPjp3iGE":423},{"slug":4,"title":5,"description":6,"content":7,"content_html":8,"pub_date":9,"tags":10,"draft":15},"mfc-dpi-adaptive","MFC 界面自适应不同分辨率","MFC 对话框程序实现控件和字体随分辨率自动缩放的完整方案，附 DPI Awareness 配置说明","# MFC 界面自适应不同分辨率\n\n随着高分辨率显示器（HiDPI）的普及，Windows 桌面应用的 DPI 适配成为必须解决的问题。本文从历史背景讲起，详细介绍在 MFC 中实现 DPI 自适应的完整方案。\n\n## DPI 缩放历史\n\n### 早期（Vista 之前）\n\nWindows 使用固定的 96 DPI 设计基准。所有 UI 元素按像素硬编码，分辨率高了但物理尺寸小的屏幕上界面会变得很小。\n\n### Vista\u002F7 的系统 DPI 缩放\n\nWindows 引入了系统级 DPI 设置，系统会对整个程序进行位图拉伸（DPI 虚拟化），让老程序在高 DPI 下仍可用，但图像会模糊。\n\n### Windows 8.1：Per-Monitor DPI\n\n引入 `Per-Monitor DPI Awareness`，允许应用程序在不同 DPI 的显示器上以不同比例渲染，但 API 支持有限。\n\n### Windows 10 1607：Per-Monitor V2\n\n最完善的 DPI 支持：\n- 窗口移到不同 DPI 显示器时自动通知\n- 非客户区（标题栏、边框）自动缩放\n- 对话框支持 DPI 变更\n\n### 现状（Windows 11）\n\n系统默认推荐使用 `PerMonitorV2`，老程序通过兼容性层处理。\n\n## DPI 感知模式\n\nWindows 定义了以下 DPI 感知模式：\n\n| 模式 | 说明 | 清晰度 |\n|------|------|--------|\n| 不感知（默认） | 系统拉伸，模糊 | 差 |\n| System Aware | 以主显示器 DPI 渲染 | 中 |\n| Per-Monitor | 监听 DPI 变化，手动处理 | 好 |\n| Per-Monitor V2 | Windows 处理更多，手动干预更少 | 最好 |\n\n## Manifest 配置\n\n在 `app.manifest`（Visual Studio 项目属性 → 清单工具）中配置：\n\n```xml\n\u003C?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n\u003Cassembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n  \u003CassemblyIdentity\n      version=\"1.0.0.0\"\n      processorArchitecture=\"*\"\n      name=\"MyMFCApp\"\n      type=\"win32\"\u002F>\n\n  \u003Capplication xmlns=\"urn:schemas-microsoft-com:asm.v3\">\n    \u003CwindowsSettings>\n      \u003C!-- Windows 10 1607+ 推荐 -->\n      \u003CdpiAwareness xmlns=\"http:\u002F\u002Fschemas.microsoft.com\u002FSMI\u002F2016\u002FWindowsSettings\">\n        PerMonitorV2, PerMonitor\n      \u003C\u002FdpiAwareness>\n      \u003C!-- 兼容旧版 Windows 8.1 的设置 -->\n      \u003CdpiAware xmlns=\"http:\u002F\u002Fschemas.microsoft.com\u002FSMI\u002F2005\u002FWindowsSettings\">\n        True\u002FPM\n      \u003C\u002FdpiAware>\n    \u003C\u002FwindowsSettings>\n  \u003C\u002Fapplication>\n\u003C\u002Fassembly>\n```\n\n注意：`PerMonitorV2, PerMonitor` 中间的逗号表示回退，Windows 10 1607+ 使用 V2，旧版回退到 V1。\n\n## GetDpiForWindow API\n\n核心 API（Windows 10 1607+）：\n\n```cpp\n\u002F\u002F 获取当前窗口的 DPI\nUINT dpi = GetDpiForWindow(m_hWnd);\n\n\u002F\u002F 基准 DPI（96 = 100% 缩放）\nconst UINT BASE_DPI = 96;\n\n\u002F\u002F 计算缩放比例\nfloat scale = (float)dpi \u002F BASE_DPI;\n\u002F\u002F scale = 1.0  → 100%（96 DPI）\n\u002F\u002F scale = 1.25 → 125%（120 DPI）\n\u002F\u002F scale = 1.5  → 150%（144 DPI）\n\u002F\u002F scale = 2.0  → 200%（192 DPI，常见 HiDPI）\n```\n\n旧版 Windows 的兼容方案：\n\n```cpp\nUINT GetWindowDPI(HWND hwnd) {\n    \u002F\u002F 尝试 Windows 10 1607+ API\n    typedef UINT(WINAPI* PFN_GetDpiForWindow)(HWND);\n    static PFN_GetDpiForWindow s_pfnGetDpiForWindow = nullptr;\n    static bool s_checked = false;\n    \n    if (!s_checked) {\n        HMODULE hUser32 = GetModuleHandle(L\"user32.dll\");\n        if (hUser32) {\n            s_pfnGetDpiForWindow = (PFN_GetDpiForWindow)\n                GetProcAddress(hUser32, \"GetDpiForWindow\");\n        }\n        s_checked = true;\n    }\n    \n    if (s_pfnGetDpiForWindow) {\n        return s_pfnGetDpiForWindow(hwnd);\n    }\n    \n    \u002F\u002F 回退：获取系统 DPI\n    HDC hdc = GetDC(hwnd);\n    UINT dpi = GetDeviceCaps(hdc, LOGPIXELSX);\n    ReleaseDC(hwnd, hdc);\n    return dpi;\n}\n```\n\n## 字体缩放\n\n字体大小需要根据 DPI 动态计算：\n\n```cpp\n\u002F\u002F 辅助函数：将逻辑尺寸转换为当前 DPI 下的像素\nint ScaleForDpi(int value, UINT dpi) {\n    return MulDiv(value, dpi, 96);\n}\n\n\u002F\u002F 创建 DPI 感知的字体\nCFont* CreateDpiAwareFont(HWND hwnd, int ptSize = 9, \n                           const wchar_t* faceName = L\"Microsoft YaHei UI\") {\n    UINT dpi = GetDpiForWindow(hwnd);\n    \n    LOGFONT lf = {};\n    lf.lfHeight = -MulDiv(ptSize, dpi, 72);  \u002F\u002F pt 转逻辑单位\n    lf.lfWeight = FW_NORMAL;\n    lf.lfCharSet = DEFAULT_CHARSET;\n    wcscpy_s(lf.lfFaceName, faceName);\n    \n    CFont* pFont = new CFont();\n    pFont->CreateFontIndirect(&lf);\n    return pFont;\n}\n\n\u002F\u002F 在 WM_DPICHANGED 时更新字体\nvoid CMyDialog::OnDpiChanged(UINT nDpiX, UINT nDpiY, LPRECT pNewRect) {\n    \u002F\u002F 重新创建字体\n    if (m_pFont) {\n        m_pFont->DeleteObject();\n        delete m_pFont;\n    }\n    m_pFont = CreateDpiAwareFont(m_hWnd);\n    \n    \u002F\u002F 应用到所有控件\n    EnumChildWindows(m_hWnd, [](HWND hwndChild, LPARAM lParam) -> BOOL {\n        CFont* pFont = (CFont*)lParam;\n        ::SendMessage(hwndChild, WM_SETFONT, (WPARAM)pFont->m_hObject, TRUE);\n        return TRUE;\n    }, (LPARAM)m_pFont);\n    \n    \u002F\u002F 移动窗口到建议位置\n    if (pNewRect) {\n        SetWindowPos(nullptr,\n            pNewRect->left, pNewRect->top,\n            pNewRect->right - pNewRect->left,\n            pNewRect->bottom - pNewRect->top,\n            SWP_NOZORDER | SWP_NOACTIVATE\n        );\n    }\n}\n```\n\n## 控件和布局缩放\n\nMFC 对话框的控件位置使用对话框单位（DLU），通常会随系统字体自动缩放。但如果用像素硬编码，就需要手动换算：\n\n```cpp\n\u002F\u002F 按 DPI 缩放控件位置和大小\nvoid CMyDialog::ScaleControl(CWnd* pCtrl, UINT dpiOld, UINT dpiNew) {\n    CRect rect;\n    pCtrl->GetWindowRect(&rect);\n    ScreenToClient(&rect);\n    \n    \u002F\u002F 缩放\n    rect.left   = MulDiv(rect.left,   dpiNew, dpiOld);\n    rect.top    = MulDiv(rect.top,    dpiNew, dpiOld);\n    rect.right  = MulDiv(rect.right,  dpiNew, dpiOld);\n    rect.bottom = MulDiv(rect.bottom, dpiNew, dpiOld);\n    \n    pCtrl->SetWindowPos(nullptr,\n        rect.left, rect.top,\n        rect.Width(), rect.Height(),\n        SWP_NOZORDER | SWP_NOACTIVATE\n    );\n}\n\n\u002F\u002F 在 WM_DPICHANGED 中缩放所有控件\nvoid CMyDialog::ScaleAllControls(UINT dpiOld, UINT dpiNew) {\n    CWnd* pChild = GetWindow(GW_CHILD);\n    while (pChild) {\n        ScaleControl(pChild, dpiOld, dpiNew);\n        pChild = pChild->GetNextWindow();\n    }\n    \n    \u002F\u002F 也需要调整对话框自身大小（由 pNewRect 给出）\n}\n```\n\n## 位图缩放\n\n图标和位图需要提供多个分辨率版本，或者动态缩放：\n\n```cpp\n\u002F\u002F 方法一：提供多分辨率图标（推荐）\n\u002F\u002F 在 .ico 文件中包含 16x16, 32x32, 48x48, 256x256 等多个尺寸\n\u002F\u002F Windows 会自动选择最合适的\n\n\u002F\u002F 方法二：动态缩放位图\nCBitmap* CreateScaledBitmap(HBITMAP hBitmapSrc, int srcW, int srcH, \n                             int dstW, int dstH) {\n    CBitmap* pBmp = new CBitmap();\n    \n    CDC dcMem, dcSrc;\n    dcMem.CreateCompatibleDC(nullptr);\n    dcSrc.CreateCompatibleDC(nullptr);\n    \n    pBmp->CreateCompatibleBitmap(CDC::FromHandle(GetDC(nullptr)), dstW, dstH);\n    \n    CBitmap* pOldDst = dcMem.SelectObject(pBmp);\n    CBitmap srcBmp;\n    srcBmp.Attach(hBitmapSrc);\n    CBitmap* pOldSrc = dcSrc.SelectObject(&srcBmp);\n    \n    \u002F\u002F 高质量缩放\n    dcMem.SetStretchBltMode(HALFTONE);\n    dcMem.StretchBlt(0, 0, dstW, dstH, &dcSrc, 0, 0, srcW, srcH, SRCCOPY);\n    \n    dcMem.SelectObject(pOldDst);\n    dcSrc.SelectObject(pOldSrc);\n    srcBmp.Detach();\n    \n    return pBmp;\n}\n```\n\n## 处理 WM_DPICHANGED\n\n当窗口移动到不同 DPI 的显示器时，系统发送 `WM_DPICHANGED`：\n\n```cpp\n\u002F\u002F 消息映射\nBEGIN_MESSAGE_MAP(CMyDialog, CDialog)\n    ON_MESSAGE(WM_DPICHANGED, &CMyDialog::OnDpiChanged)\nEND_MESSAGE_MAP()\n\nLRESULT CMyDialog::OnDpiChanged(WPARAM wParam, LPARAM lParam) {\n    UINT dpiX = LOWORD(wParam);\n    UINT dpiY = HIWORD(wParam);\n    RECT* pRect = reinterpret_cast\u003CRECT*>(lParam);  \u002F\u002F 建议的新窗口位置\u002F大小\n    \n    UINT dpiOld = m_currentDpi;\n    m_currentDpi = dpiX;\n    \n    \u002F\u002F 1. 更新字体\n    UpdateFonts();\n    \n    \u002F\u002F 2. 缩放控件\n    ScaleAllControls(dpiOld, dpiX);\n    \n    \u002F\u002F 3. 移到建议位置（保持物理大小不变）\n    SetWindowPos(nullptr,\n        pRect->left, pRect->top,\n        pRect->right - pRect->left,\n        pRect->bottom - pRect->top,\n        SWP_NOZORDER | SWP_NOACTIVATE\n    );\n    \n    \u002F\u002F 4. 重绘\n    Invalidate();\n    \n    return 0;\n}\n```\n\n## 测试方法\n\n1. **Windows 设置**：设置 → 显示 → 缩放，改为 125%、150%、200%，观察应用外观。\n\n2. **多显示器测试**：连接两台不同 DPI 的显示器，把窗口在两台显示器间拖动，观察是否正确调整。\n\n3. **工具辅助**：使用 `Inspect`（Accessibility Insights）或 `DpiScaling` 工具测试。\n\n4. **手动修改注册表**（测试用）：\n   ```\n   HKCU\\Control Panel\\Desktop\\LogPixels\n   ```\n   改为 120（125%）或 144（150%）后注销重登录。\n\n遵循以上方案，你的 MFC 应用就能在各种 DPI 设置和显示器组合下显示清晰、布局正确。\n","\u003Ch1>MFC 界面自适应不同分辨率\u003C\u002Fh1>\n\u003Cp>随着高分辨率显示器（HiDPI）的普及，Windows 桌面应用的 DPI 适配成为必须解决的问题。本文从历史背景讲起，详细介绍在 MFC 中实现 DPI 自适应的完整方案。\u003C\u002Fp>\n\u003Ch2 id=\"dpi-缩放历史\">DPI 缩放历史\u003C\u002Fh2>\n\u003Ch3 id=\"早期-vista-之前\">早期（Vista 之前）\u003C\u002Fh3>\n\u003Cp>Windows 使用固定的 96 DPI 设计基准。所有 UI 元素按像素硬编码，分辨率高了但物理尺寸小的屏幕上界面会变得很小。\u003C\u002Fp>\n\u003Ch3 id=\"vista-7-的系统-dpi-缩放\">Vista\u002F7 的系统 DPI 缩放\u003C\u002Fh3>\n\u003Cp>Windows 引入了系统级 DPI 设置，系统会对整个程序进行位图拉伸（DPI 虚拟化），让老程序在高 DPI 下仍可用，但图像会模糊。\u003C\u002Fp>\n\u003Ch3 id=\"windows-8-1-per-monitor-dpi\">Windows 8.1：Per-Monitor DPI\u003C\u002Fh3>\n\u003Cp>引入 \u003Ccode>Per-Monitor DPI Awareness\u003C\u002Fcode>，允许应用程序在不同 DPI 的显示器上以不同比例渲染，但 API 支持有限。\u003C\u002Fp>\n\u003Ch3 id=\"windows-10-1607-per-monitor-v2\">Windows 10 1607：Per-Monitor V2\u003C\u002Fh3>\n\u003Cp>最完善的 DPI 支持：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>窗口移到不同 DPI 显示器时自动通知\u003C\u002Fli>\n\u003Cli>非客户区（标题栏、边框）自动缩放\u003C\u002Fli>\n\u003Cli>对话框支持 DPI 变更\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3 id=\"现状-windows-11\">现状（Windows 11）\u003C\u002Fh3>\n\u003Cp>系统默认推荐使用 \u003Ccode>PerMonitorV2\u003C\u002Fcode>，老程序通过兼容性层处理。\u003C\u002Fp>\n\u003Ch2 id=\"dpi-感知模式\">DPI 感知模式\u003C\u002Fh2>\n\u003Cp>Windows 定义了以下 DPI 感知模式：\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>模式\u003C\u002Fth>\n\u003Cth>说明\u003C\u002Fth>\n\u003Cth>清晰度\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>不感知（默认）\u003C\u002Ftd>\n\u003Ctd>系统拉伸，模糊\u003C\u002Ftd>\n\u003Ctd>差\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>System Aware\u003C\u002Ftd>\n\u003Ctd>以主显示器 DPI 渲染\u003C\u002Ftd>\n\u003Ctd>中\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Per-Monitor\u003C\u002Ftd>\n\u003Ctd>监听 DPI 变化，手动处理\u003C\u002Ftd>\n\u003Ctd>好\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Per-Monitor V2\u003C\u002Ftd>\n\u003Ctd>Windows 处理更多，手动干预更少\u003C\u002Ftd>\n\u003Ctd>最好\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch2 id=\"manifest-配置\">Manifest 配置\u003C\u002Fh2>\n\u003Cp>在 \u003Ccode>app.manifest\u003C\u002Fcode>（Visual Studio 项目属性 → 清单工具）中配置：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-xml\">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;yes&quot;?&gt;\n&lt;assembly xmlns=&quot;urn:schemas-microsoft-com:asm.v1&quot; manifestVersion=&quot;1.0&quot;&gt;\n  &lt;assemblyIdentity\n      version=&quot;1.0.0.0&quot;\n      processorArchitecture=&quot;*&quot;\n      name=&quot;MyMFCApp&quot;\n      type=&quot;win32&quot;\u002F&gt;\n\n  &lt;application xmlns=&quot;urn:schemas-microsoft-com:asm.v3&quot;&gt;\n    &lt;windowsSettings&gt;\n      &lt;!-- Windows 10 1607+ 推荐 --&gt;\n      &lt;dpiAwareness xmlns=&quot;http:\u002F\u002Fschemas.microsoft.com\u002FSMI\u002F2016\u002FWindowsSettings&quot;&gt;\n        PerMonitorV2, PerMonitor\n      &lt;\u002FdpiAwareness&gt;\n      &lt;!-- 兼容旧版 Windows 8.1 的设置 --&gt;\n      &lt;dpiAware xmlns=&quot;http:\u002F\u002Fschemas.microsoft.com\u002FSMI\u002F2005\u002FWindowsSettings&quot;&gt;\n        True\u002FPM\n      &lt;\u002FdpiAware&gt;\n    &lt;\u002FwindowsSettings&gt;\n  &lt;\u002Fapplication&gt;\n&lt;\u002Fassembly&gt;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>注意：\u003Ccode>PerMonitorV2, PerMonitor\u003C\u002Fcode> 中间的逗号表示回退，Windows 10 1607+ 使用 V2，旧版回退到 V1。\u003C\u002Fp>\n\u003Ch2 id=\"getdpiforwindow-api\">GetDpiForWindow API\u003C\u002Fh2>\n\u003Cp>核心 API（Windows 10 1607+）：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F 获取当前窗口的 DPI\nUINT dpi = GetDpiForWindow(m_hWnd);\n\n\u002F\u002F 基准 DPI（96 = 100% 缩放）\nconst UINT BASE_DPI = 96;\n\n\u002F\u002F 计算缩放比例\nfloat scale = (float)dpi \u002F BASE_DPI;\n\u002F\u002F scale = 1.0  → 100%（96 DPI）\n\u002F\u002F scale = 1.25 → 125%（120 DPI）\n\u002F\u002F scale = 1.5  → 150%（144 DPI）\n\u002F\u002F scale = 2.0  → 200%（192 DPI，常见 HiDPI）\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>旧版 Windows 的兼容方案：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">UINT GetWindowDPI(HWND hwnd) {\n    \u002F\u002F 尝试 Windows 10 1607+ API\n    typedef UINT(WINAPI* PFN_GetDpiForWindow)(HWND);\n    static PFN_GetDpiForWindow s_pfnGetDpiForWindow = nullptr;\n    static bool s_checked = false;\n    \n    if (!s_checked) {\n        HMODULE hUser32 = GetModuleHandle(L&quot;user32.dll&quot;);\n        if (hUser32) {\n            s_pfnGetDpiForWindow = (PFN_GetDpiForWindow)\n                GetProcAddress(hUser32, &quot;GetDpiForWindow&quot;);\n        }\n        s_checked = true;\n    }\n    \n    if (s_pfnGetDpiForWindow) {\n        return s_pfnGetDpiForWindow(hwnd);\n    }\n    \n    \u002F\u002F 回退：获取系统 DPI\n    HDC hdc = GetDC(hwnd);\n    UINT dpi = GetDeviceCaps(hdc, LOGPIXELSX);\n    ReleaseDC(hwnd, hdc);\n    return dpi;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"字体缩放\">字体缩放\u003C\u002Fh2>\n\u003Cp>字体大小需要根据 DPI 动态计算：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F 辅助函数：将逻辑尺寸转换为当前 DPI 下的像素\nint ScaleForDpi(int value, UINT dpi) {\n    return MulDiv(value, dpi, 96);\n}\n\n\u002F\u002F 创建 DPI 感知的字体\nCFont* CreateDpiAwareFont(HWND hwnd, int ptSize = 9, \n                           const wchar_t* faceName = L&quot;Microsoft YaHei UI&quot;) {\n    UINT dpi = GetDpiForWindow(hwnd);\n    \n    LOGFONT lf = {};\n    lf.lfHeight = -MulDiv(ptSize, dpi, 72);  \u002F\u002F pt 转逻辑单位\n    lf.lfWeight = FW_NORMAL;\n    lf.lfCharSet = DEFAULT_CHARSET;\n    wcscpy_s(lf.lfFaceName, faceName);\n    \n    CFont* pFont = new CFont();\n    pFont-&gt;CreateFontIndirect(&amp;lf);\n    return pFont;\n}\n\n\u002F\u002F 在 WM_DPICHANGED 时更新字体\nvoid CMyDialog::OnDpiChanged(UINT nDpiX, UINT nDpiY, LPRECT pNewRect) {\n    \u002F\u002F 重新创建字体\n    if (m_pFont) {\n        m_pFont-&gt;DeleteObject();\n        delete m_pFont;\n    }\n    m_pFont = CreateDpiAwareFont(m_hWnd);\n    \n    \u002F\u002F 应用到所有控件\n    EnumChildWindows(m_hWnd, [](HWND hwndChild, LPARAM lParam) -&gt; BOOL {\n        CFont* pFont = (CFont*)lParam;\n        ::SendMessage(hwndChild, WM_SETFONT, (WPARAM)pFont-&gt;m_hObject, TRUE);\n        return TRUE;\n    }, (LPARAM)m_pFont);\n    \n    \u002F\u002F 移动窗口到建议位置\n    if (pNewRect) {\n        SetWindowPos(nullptr,\n            pNewRect-&gt;left, pNewRect-&gt;top,\n            pNewRect-&gt;right - pNewRect-&gt;left,\n            pNewRect-&gt;bottom - pNewRect-&gt;top,\n            SWP_NOZORDER | SWP_NOACTIVATE\n        );\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"控件和布局缩放\">控件和布局缩放\u003C\u002Fh2>\n\u003Cp>MFC 对话框的控件位置使用对话框单位（DLU），通常会随系统字体自动缩放。但如果用像素硬编码，就需要手动换算：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F 按 DPI 缩放控件位置和大小\nvoid CMyDialog::ScaleControl(CWnd* pCtrl, UINT dpiOld, UINT dpiNew) {\n    CRect rect;\n    pCtrl-&gt;GetWindowRect(&amp;rect);\n    ScreenToClient(&amp;rect);\n    \n    \u002F\u002F 缩放\n    rect.left   = MulDiv(rect.left,   dpiNew, dpiOld);\n    rect.top    = MulDiv(rect.top,    dpiNew, dpiOld);\n    rect.right  = MulDiv(rect.right,  dpiNew, dpiOld);\n    rect.bottom = MulDiv(rect.bottom, dpiNew, dpiOld);\n    \n    pCtrl-&gt;SetWindowPos(nullptr,\n        rect.left, rect.top,\n        rect.Width(), rect.Height(),\n        SWP_NOZORDER | SWP_NOACTIVATE\n    );\n}\n\n\u002F\u002F 在 WM_DPICHANGED 中缩放所有控件\nvoid CMyDialog::ScaleAllControls(UINT dpiOld, UINT dpiNew) {\n    CWnd* pChild = GetWindow(GW_CHILD);\n    while (pChild) {\n        ScaleControl(pChild, dpiOld, dpiNew);\n        pChild = pChild-&gt;GetNextWindow();\n    }\n    \n    \u002F\u002F 也需要调整对话框自身大小（由 pNewRect 给出）\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"位图缩放\">位图缩放\u003C\u002Fh2>\n\u003Cp>图标和位图需要提供多个分辨率版本，或者动态缩放：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F 方法一：提供多分辨率图标（推荐）\n\u002F\u002F 在 .ico 文件中包含 16x16, 32x32, 48x48, 256x256 等多个尺寸\n\u002F\u002F Windows 会自动选择最合适的\n\n\u002F\u002F 方法二：动态缩放位图\nCBitmap* CreateScaledBitmap(HBITMAP hBitmapSrc, int srcW, int srcH, \n                             int dstW, int dstH) {\n    CBitmap* pBmp = new CBitmap();\n    \n    CDC dcMem, dcSrc;\n    dcMem.CreateCompatibleDC(nullptr);\n    dcSrc.CreateCompatibleDC(nullptr);\n    \n    pBmp-&gt;CreateCompatibleBitmap(CDC::FromHandle(GetDC(nullptr)), dstW, dstH);\n    \n    CBitmap* pOldDst = dcMem.SelectObject(pBmp);\n    CBitmap srcBmp;\n    srcBmp.Attach(hBitmapSrc);\n    CBitmap* pOldSrc = dcSrc.SelectObject(&amp;srcBmp);\n    \n    \u002F\u002F 高质量缩放\n    dcMem.SetStretchBltMode(HALFTONE);\n    dcMem.StretchBlt(0, 0, dstW, dstH, &amp;dcSrc, 0, 0, srcW, srcH, SRCCOPY);\n    \n    dcMem.SelectObject(pOldDst);\n    dcSrc.SelectObject(pOldSrc);\n    srcBmp.Detach();\n    \n    return pBmp;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"处理-wm_dpichanged\">处理 WM_DPICHANGED\u003C\u002Fh2>\n\u003Cp>当窗口移动到不同 DPI 的显示器时，系统发送 \u003Ccode>WM_DPICHANGED\u003C\u002Fcode>：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F 消息映射\nBEGIN_MESSAGE_MAP(CMyDialog, CDialog)\n    ON_MESSAGE(WM_DPICHANGED, &amp;CMyDialog::OnDpiChanged)\nEND_MESSAGE_MAP()\n\nLRESULT CMyDialog::OnDpiChanged(WPARAM wParam, LPARAM lParam) {\n    UINT dpiX = LOWORD(wParam);\n    UINT dpiY = HIWORD(wParam);\n    RECT* pRect = reinterpret_cast&lt;RECT*&gt;(lParam);  \u002F\u002F 建议的新窗口位置\u002F大小\n    \n    UINT dpiOld = m_currentDpi;\n    m_currentDpi = dpiX;\n    \n    \u002F\u002F 1. 更新字体\n    UpdateFonts();\n    \n    \u002F\u002F 2. 缩放控件\n    ScaleAllControls(dpiOld, dpiX);\n    \n    \u002F\u002F 3. 移到建议位置（保持物理大小不变）\n    SetWindowPos(nullptr,\n        pRect-&gt;left, pRect-&gt;top,\n        pRect-&gt;right - pRect-&gt;left,\n        pRect-&gt;bottom - pRect-&gt;top,\n        SWP_NOZORDER | SWP_NOACTIVATE\n    );\n    \n    \u002F\u002F 4. 重绘\n    Invalidate();\n    \n    return 0;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"测试方法\">测试方法\u003C\u002Fh2>\n\u003Col>\n\u003Cli>\n\u003Cp>\u003Cstrong>Windows 设置\u003C\u002Fstrong>：设置 → 显示 → 缩放，改为 125%、150%、200%，观察应用外观。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>多显示器测试\u003C\u002Fstrong>：连接两台不同 DPI 的显示器，把窗口在两台显示器间拖动，观察是否正确调整。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>工具辅助\u003C\u002Fstrong>：使用 \u003Ccode>Inspect\u003C\u002Fcode>（Accessibility Insights）或 \u003Ccode>DpiScaling\u003C\u002Fcode> 工具测试。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>手动修改注册表\u003C\u002Fstrong>（测试用）：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>HKCU\\Control Panel\\Desktop\\LogPixels\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>改为 120（125%）或 144（150%）后注销重登录。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>遵循以上方案，你的 MFC 应用就能在各种 DPI 设置和显示器组合下显示清晰、布局正确。\u003C\u002Fp>\n","2022-08-17",[11,12,13,14],"mfc","cpp","windows","dpi",false,[17,30,41,53,63,70,77,84,91,98,108,117,127,136,144,152,161,170,179,189,196,205,211,218,224,233,240,247,255,265,274,283,293,303,313,321,331,342,351,360,368,374,382,390,397,405,408,415],{"slug":18,"title":19,"description":20,"pub_date":21,"tags":22,"draft":15,"word_count":29},"ide-skills-guide","Agent Skills 完全指南：21 款第三方 Skill 深度评测与使用心得","全面评测 21 款第三方 Agent Skills，涵盖 Vue 生态、前端设计、构建工具、实用工具四大分类。从安装配置到实际使用场景，带你了解每个 Skill 的功能特点、最佳实践与使用心得。","2026-06-15",[23,24,25,26,27,28],"agent","skills","AI","效率工具","前端","Vue",4169,{"slug":31,"title":32,"description":33,"pub_date":34,"tags":35,"draft":15,"word_count":40},"linux-kernel-skeleton-struct-funcptr-container_of","Linux 内核骨架：struct、函数指针与 container_of","读懂 Linux 内核源码的三件套：巨大的 struct 组合代替继承、函数指针表实现虚派发、container_of 宏从嵌入成员找回完整对象。","2026-05-09",[36,37,38,39],"linux","kernel","C","container_of",1369,{"slug":42,"title":43,"description":44,"pub_date":45,"tags":46,"draft":15,"word_count":52},"astro-complete-guide-2025","Astro 5 深度剖析：Islands 架构原理、构建优化与 Cloudflare Workers 边缘部署","从编译器视角解析 Astro 5 的 Islands 架构实现原理，Content Layer API 的 Vite 插件机制，Server Islands 的流式渲染，以及如何在 Cloudflare Workers + D1 边缘环境下榨干性能。","2026-05-08",[47,48,49,50,51],"astro","frontend","cloudflare","performance","architecture",3663,{"slug":54,"title":55,"description":56,"pub_date":57,"tags":58,"draft":15,"word_count":62},"llm-prompt-engineering","Prompt Engineering 实战：让 LLM 真正听话的技巧","System prompt 怎么写、Few-shot 怎么设计、Chain-of-Thought 原理，以及常见失败模式和调试方法。","2026-05-03",[59,60,61],"ai","llm","工程实践",1723,{"slug":64,"title":65,"description":66,"pub_date":57,"tags":67,"draft":15,"word_count":69},"rag-system-design","RAG 系统设计：从 naive 到 production-ready","Retrieval-Augmented Generation 不只是「向量数据库 + LLM」，分块策略、召回质量、重排序、缓存才是工程核心。",[59,68,60,61],"rag",1613,{"slug":71,"title":72,"description":73,"pub_date":57,"tags":74,"draft":15,"word_count":76},"git-advanced-workflow","Git 进阶工作流：rebase、cherry-pick、bisect 的正确使用","merge 会了，但 rebase 总搞错？bisect 找 bug 提交？interactive rebase 整理历史？这篇一次说清楚。",[75,61],"git",1396,{"slug":78,"title":79,"description":80,"pub_date":57,"tags":81,"draft":15,"word_count":83},"docker-practical-guide","Docker 实战：从会用到用好","会 docker run 不够，Dockerfile 最佳实践、多阶段构建、Compose 编排、镜像瘦身才是日常真正需要的。",[82,36,61],"docker",1268,{"slug":85,"title":86,"description":87,"pub_date":57,"tags":88,"draft":15,"word_count":90},"anthropics-skills-guide","anthropics\u002Fskills：Anthropic 官方 Agent Skills 仓库解析","Anthropic 官方开源的 Agent Skills 标准仓库，127k stars，解析 SKILL.md 规范、17 个示例 skill 的设计模式，以及如何在 Claude Code \u002F Claude.ai \u002F API 中使用",[59,89,23,24],"Claude",2090,{"slug":92,"title":93,"description":94,"pub_date":57,"tags":95,"draft":15,"word_count":97},"karpathy-claude-code-guidelines","Karpathy 的 LLM 编码批评与 CLAUDE.md 最佳实践","基于 Andrej Karpathy 对 LLM 编程助手的观察，forrestchang 提炼出一个 CLAUDE.md 文件，4 条原则解决 AI 编码的典型失控问题：乱猜假设、过度设计、乱改代码、目标不清",[59,89,96,61],"Claude Code",2699,{"slug":99,"title":100,"description":101,"pub_date":57,"tags":102,"draft":15,"word_count":107},"typescript-advanced-patterns","TypeScript 高级模式：让类型系统为你工作","基础 TS 会了但类型总是 any？条件类型、映射类型、模板字面量类型、infer 关键字才是 TS 的真正威力。",[103,104,105,106],"typescript","类型系统","前端工程","高级模式",1419,{"slug":109,"title":110,"description":111,"pub_date":57,"tags":112,"draft":15,"word_count":116},"linux-performance-tuning","Linux 性能调优实战：从 top 到 perf 的完整工具链","遇到性能问题不知道从哪下手？这篇建立系统化的排查思路，从 CPU\u002F内存\u002FIO\u002F网络逐层分析。",[36,113,114,115],"性能","运维","系统编程",1524,{"slug":118,"title":119,"description":120,"pub_date":57,"tags":121,"draft":15,"word_count":126},"python-functional-programming","Python 函数式编程：map\u002Ffilter\u002Freduce 之外","Python 不是纯函数式语言，但 functools、itertools、偏函数、闭包这些工具用好了能让代码简洁一个量级。",[122,123,124,125],"python","函数式","闭包","装饰器",1867,{"slug":128,"title":129,"description":130,"pub_date":57,"tags":131,"draft":15,"word_count":135},"python-oop-guide","Python 面向对象：__init__ 之外你需要知道的","Python OOP 不只是 class + __init__，魔术方法、描述符、元类才是真正的武器。",[122,132,133,134],"OOP","面向对象","魔术方法",1792,{"slug":137,"title":138,"description":139,"pub_date":57,"tags":140,"draft":15,"word_count":143},"python-data-structures","Python 内置数据结构深度解析","list、dict、set、tuple 不只是数据容器，搞懂它们的底层实现和时间复杂度，才能写出高性能 Python。",[122,141,113,142],"数据结构","算法",1517,{"slug":145,"title":146,"description":147,"pub_date":57,"tags":148,"draft":15,"word_count":151},"python-basics-quick-start","Python 快速上手：写给有编程基础的人","已经会其他语言，想快速掌握 Python 的语法特性和思维方式，这篇是捷径。",[122,149,150],"入门","基础",1607,{"slug":153,"title":154,"description":155,"pub_date":57,"tags":156,"draft":15,"word_count":160},"python-dataclass-pydantic","Python dataclass vs Pydantic：数据类选型指南","dataclass 是标准库的轻量选择，Pydantic v2 是带验证的重武器，什么时候用哪个，这篇说清楚。",[122,157,158,159],"dataclass","pydantic","数据验证",1323,{"slug":162,"title":163,"description":164,"pub_date":57,"tags":165,"draft":15,"word_count":169},"python-asyncio-practical","Python asyncio 实战：从回调地狱到协程优雅","asyncio 是 Python 异步编程的核心，搞懂 event loop、Task、gather 这些概念才能写出真正高效的异步代码。",[122,166,167,168],"asyncio","并发","网络编程",1258,{"slug":171,"title":172,"description":173,"pub_date":57,"tags":174,"draft":15,"word_count":178},"python-type-hints-guide","Python 类型注解完全指南：从入门到实践","Python 3.5+ 引入类型注解，配合 mypy\u002Fpyright 让 Python 也能享受静态类型检查的好处。",[122,175,176,177],"typescript-style","type-hints","工具链",1102,{"slug":180,"title":181,"description":182,"pub_date":183,"tags":184,"draft":15,"word_count":188},"pwa-install-update-button","PWA 踩坑：为什么安装按钮从来不出现","从 beforeinstallprompt 到 Service Worker waiting，把 PWA 的安装与更新提示真正做对","2026-05-02",[185,186,187],"pwa","javascript","web",1683,{"slug":190,"title":191,"description":192,"pub_date":193,"tags":194,"draft":15,"word_count":195},"openclaw-vs-hermes-agent","OpenClaw vs Hermes Agent：两个本地优先 Agent 的设计差异","OpenClaw（Novita AI）和 Hermes Agent（Nous Research）都是本地运行的个人 AI Agent，但在记忆系统、技能学习、运行环境和模型生态上走了不同的路。深入对比两种架构的核心差异。","2026-05-01",[59,23,60],1679,{"slug":197,"title":198,"description":199,"pub_date":193,"tags":200,"draft":15,"word_count":204},"cpp-random-design-patterns","C++ 设计模式实战：RAII、观察者、工厂","用现代 C++（C++17\u002F20）实现三种高频设计模式：RAII 资源管理、观察者模式事件系统、工厂模式插件架构。每种模式给出问题场景、实现代码和真实工程案例。",[12,201,202,203],"设计模式","c++17","工程",2613,{"slug":206,"title":207,"description":208,"pub_date":193,"tags":209,"draft":15,"word_count":210},"data-structures-fundamentals","数据结构基础：从数组到红黑树","系统梳理常用数据结构的核心原理、时间复杂度和适用场景。数组、链表、栈、队列、哈希表、二叉树、堆、图，每种结构附实现要点和 C++ 代码片段。",[141,142,12,150],3004,{"slug":212,"title":213,"description":214,"pub_date":215,"tags":216,"draft":15,"word_count":217},"ai-agent-what-is","什么是 AI Agent？从 LLM 到自主执行","LLM 本身是无状态问答机，Agent 是什么让它’动’起来的？本文深入解析 Agent 的四个核心能力、ReAct 框架、工具调用原理，以及主流框架横向对比。","2026-04-30",[59,23,60],2116,{"slug":219,"title":220,"description":221,"pub_date":215,"tags":222,"draft":15,"word_count":223},"ai-agent-memory","AI Agent 的记忆系统：从上下文窗口到长期记忆","深入拆解 AI Agent 的四种记忆类型、上下文窗口压缩策略、RAG 向量检索原理，以及三种典型失败模式和工程选型建议。",[59,23,68],2052,{"slug":225,"title":226,"description":227,"pub_date":215,"tags":228,"draft":15,"word_count":232},"network-proxy-vpn-guide","代理与翻墙技术原理：从 HTTP 代理到现代协议","深入解析代理与 VPN 的本质区别，梳理从 SOCKS5 到 Shadowsocks、V2Ray\u002FXray、Hysteria2 的协议演进，以及机场订阅的技术本质。",[229,230,231],"网络","代理","协议",2148,{"slug":234,"title":235,"description":236,"pub_date":215,"tags":237,"draft":15,"word_count":151},"algorithm-binary-search","二分查找：永远写不对？记住这个模板","彻底搞清楚二分查找的边界问题：闭区间和左闭右开两套模板、三道经典 LeetCode 题目完整 C++ 实现，以及二分答案的进阶思路。",[142,238,239,12],"二分查找","leetcode",{"slug":241,"title":242,"description":243,"pub_date":215,"tags":244,"draft":15,"word_count":246},"algorithm-sliding-window","滑动窗口算法：从暴力到 O(n) 的思维跃迁","系统讲解滑动窗口算法的核心模板、适用题型，配合三道经典 LeetCode 题目的完整 C++ 实现，彻底理解双指针收缩思路。",[142,245,239,12],"滑动窗口",1943,{"slug":248,"title":249,"description":250,"pub_date":215,"tags":251,"draft":15,"word_count":254},"network-clash-config","Clash \u002F Mihomo 配置详解：规则、策略组与分流","深入解析 Clash\u002FMihomo 的核心配置结构，包括代理节点、策略组类型、规则优先级、DNS fake-ip 模式，以及一份实用的完整配置模板。",[229,252,230,253],"clash","配置",1292,{"slug":256,"title":257,"description":258,"pub_date":259,"tags":260,"draft":15,"word_count":264},"hid-hotplug","HID 设备热插拔检测：从 udev 到 node-hid","在 Linux 上用 node-hid + usb 库实现可靠的 USB HID 设备热插拔检测，踩坑记录","2026-04-28",[12,261,36,262,263],"hid","nodejs","electron",2039,{"slug":266,"title":267,"description":268,"pub_date":269,"tags":270,"draft":15,"word_count":273},"electron-ipc-types","Electron IPC 类型安全：从 any 到完全类型化","用 TypeScript 泛型封装 Electron IPC，彻底消灭 any，preload 契约集中管理","2026-04-25",[263,103,271,272],"ipc","vue",1446,{"slug":275,"title":276,"description":277,"pub_date":278,"tags":279,"draft":15,"word_count":282},"element-plus-popover-hide","手动关闭多个 el-popover（不用 v-model:visible）","通过 ref + Reflect.get 调用 hide() 方法手动关闭 Element Plus Popover，解释 Vue3 Proxy 导致无法直接调用实例方法的原因。","2024-10-25",[272,280,281],"element-plus","vue3",1321,{"slug":284,"title":285,"description":286,"pub_date":287,"tags":288,"draft":15,"word_count":292},"vite-vue3-ts-elementplus-pinia","用 Vite+（vp）从零搭建 Vue3 + TypeScript + Element Plus + Pinia + Vue Router","使用 Vite+ 统一工具链（vp）一条命令搭建 Vue3 全家桶，涵盖按需导入、Pinia store、路由配置，以及常见坑的解决方案。","2024-08-27",[272,289,103,280,290,291],"vite","pinia","vite-plus",1960,{"slug":294,"title":295,"description":296,"pub_date":297,"tags":298,"draft":15,"word_count":302},"cef-lnk2038-iterator-debug-level","CEF LNK2038：解决 _ITERATOR_DEBUG_LEVEL 不匹配错误","分析 CEF（Chromium Embedded Framework）集成时出现的 LNK2038 _ITERATOR_DEBUG_LEVEL 链接错误，从根本原因到解决方案的完整指南。","2024-05-07",[12,299,300,301],"CEF","Visual Studio","链接错误",1509,{"slug":304,"title":305,"description":306,"pub_date":307,"tags":308,"draft":15,"word_count":312},"npm-electron-install-fix","彻底解决 npm 安装 Electron 失败的问题","分析 npm install electron 失败的根本原因（下载二进制超时\u002F被墙），通过国内镜像（npmmirror）彻底解决，并介绍多种备选方案和常见错误排查。","2024-03-01",[263,309,310,311],"npm","前端工具链","国内镜像",1494,{"slug":314,"title":315,"description":316,"pub_date":317,"tags":318,"draft":15,"word_count":320},"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",[75,36,319],"工具",2244,{"slug":322,"title":323,"description":324,"pub_date":325,"tags":326,"draft":15,"word_count":330},"vmware-tools-install","在 VMware 虚拟机中安装 open-vm-tools 完整指南","详解 VMware Tools 的作用、open-vm-tools 与官方 VMware Tools 的区别，以及在 Ubuntu 虚拟机中安装并生效的完整步骤和常见问题排查。","2023-11-21",[327,36,328,329],"VMware","Ubuntu","虚拟机",2523,{"slug":332,"title":333,"description":334,"pub_date":335,"tags":336,"draft":15,"word_count":341},"load-balancing-algorithms","负载均衡算法完全指南：从轮询到一致性哈希","系统梳理静态与动态负载均衡算法，涵盖轮询、随机、权重、IP Hash、一致性 Hash、最少连接、最快响应等，并对比 Nginx、Dubbo、Spring Cloud LoadBalancer 的实现差异。","2023-11-15",[337,338,339,340],"分布式","负载均衡","Nginx","微服务",1764,{"slug":343,"title":344,"description":345,"pub_date":346,"tags":347,"draft":15,"word_count":350},"win-cw2a-ca2w","ATL 字符串转换：CW2A 与 CA2W 完全指南","详解 ATL 宏 CW2A\u002FCA2W 在 Unicode 与 ANSI 之间的字符串转换用法、头文件依赖、USES_CONVERSION 宏的作用与常见陷阱。","2023-06-09",[12,13,348,349],"ATL","字符串",1665,{"slug":352,"title":353,"description":354,"pub_date":346,"tags":355,"draft":15,"word_count":359},"csharp-sendmessage-cpp","C# 通过 SendMessage 向 C++ 窗口发送消息与字符串","使用 P\u002FInvoke 调用 user32.dll 的 SendMessage，从 C# 发送自定义 WM_USER 消息及字符串指针给 C++ 原生窗口，并在 C++ 侧正确接收和转换。",[356,12,13,357,358],"C#","互操作","PInvoke",1554,{"slug":361,"title":362,"description":363,"pub_date":364,"tags":365,"draft":15,"word_count":367},"win-postmessage-vector","Windows PostMessage 跨线程传递 std::vector 指针","通过 PostMessage 在 Windows 消息队列中传递 std::vector 指针，使用 reinterpret_cast 将指针装入 LPARAM，并在接收方正确释放内存。","2023-05-26",[12,13,366],"WinAPI",1823,{"slug":369,"title":370,"description":371,"pub_date":364,"tags":372,"draft":15,"word_count":373},"exe-dll-single-package","将 EXE 和 DLL 打包成单一可执行文件","介绍两种将 exe 和依赖 dll 打包成单文件的方案：Enigma Virtual Box 和 WinRAR 自解压，适合发布 Windows 桌面程序时简化分发流程。",[13,12,319],1619,{"slug":375,"title":376,"description":377,"pub_date":364,"tags":378,"draft":15,"word_count":381},"cpp-random-mt19937","C++ 现代随机数生成：用 mt19937 彻底告别 rand()","深入讲解为什么 rand() 不够用，以及如何用 C++11 的 \u003Crandom> 库正确生成高质量随机数，涵盖 mt19937、各种分布和线程安全。",[12,379,380],"c++11","random",1549,{"slug":383,"title":384,"description":385,"pub_date":386,"tags":387,"draft":15,"word_count":389},"win-startup-registry","C++ 实现程序开机自启动：注册表方式详解","通过操作 Windows 注册表 Run 键实现程序开机自启动，包括 HKCU 与 HKLM 区别、完整封装代码、工作目录问题和 UAC 权限处理。","2022-12-26",[13,12,388],"registry",1201,{"slug":391,"title":392,"description":393,"pub_date":394,"tags":395,"draft":15,"word_count":396},"mfc-cstring-wparam","MFC 中 CString 与 WPARAM 之间的转换","详解 MFC 消息传递中 CString 无法直接强转为 WPARAM 的原因，以及两种正确的转换方案，并介绍结构体指针传递的正确姿势。","2022-11-25",[11,12,13],1546,{"slug":398,"title":399,"description":400,"pub_date":401,"tags":402,"draft":15,"word_count":404},"duilib-static-build","正确编译 Duilib 静态库：避免 ATL 依赖和链接错误","详解如何用 DuiLib_Static.vcxproj 编译 Duilib 静态库，解决 VARIANT 未定义、Unicode 配置不匹配和 ATL 依赖等常见问题。","2022-08-24",[12,403,13,11],"duilib",2639,{"slug":4,"title":5,"description":6,"pub_date":9,"tags":406,"draft":15,"word_count":407},[11,12,13,14],1414,{"slug":409,"title":410,"description":411,"pub_date":412,"tags":413,"draft":15,"word_count":414},"mfc-drag-window","MFC 无标题栏窗口客户区拖动：三种方法对比","MFC 对话框去掉标题栏后如何实现拖动移动窗口，三种方案完整实现与适用场景分析","2022-08-16",[11,12,13],1633,{"slug":416,"title":417,"description":418,"pub_date":419,"tags":420,"draft":15,"word_count":422},"algorithm-number-complement","整数的补数：位运算掩码解法","LeetCode 476 题，用掩码 XOR 实现整数补数，附 C++\u002FPython\u002FJava 三种实现及补数与补码的区别","2021-03-08",[142,421,239],"位运算",1374,[]]