从 LoadResource 返回的 Win32 DLGTEMPLATEEX 结构是否意味着用户可写?

Is the Win32 DLGTEMPLATEEX structure returned from LoadResource meant to be user-writable?

我正在使用内部 MFC 应用程序,它试图通过在使用 Win32 LoadResource 函数获得的 DLGTEMPLATEEX 结构中找到它并直接写入该对话框的字体大小成员,以编程方式更改对话框的字体大小结构体。当 运行 在 VS 2019 中的调试器下时,这会触发内存写入访问冲突。

BOOL CMk20Dlg::Create(UINT nIDTemplate, CWnd* pParentWnd)
{
    LPCTSTR lpszTemplateName = MAKEINTRESOURCE(nIDTemplate);

    ASSERT(IS_INTRESOURCE(lpszTemplateName) || AfxIsValidString(lpszTemplateName));

    m_lpszTemplateName = lpszTemplateName;
    if (IS_INTRESOURCE(m_lpszTemplateName) && m_nIDHelp == 0)
        m_nIDHelp = LOWORD(reinterpret_cast<DWORD_PTR>(m_lpszTemplateName));

    HINSTANCE hInst = AfxFindResourceHandle(lpszTemplateName, RT_DIALOG);
    HRSRC hResource = ::FindResource(hInst, lpszTemplateName, RT_DIALOG);
    if (hResource == NULL)
        TRACE(_T("FindResource failed with error %d\n"), ::GetLastError());

    HGLOBAL hTemplate = ::LoadResource(hInst, hResource);
    DLGTEMPLATE* lpTemplate = static_cast<DLGTEMPLATE*>(::LockResource(hTemplate));

    WORD* pwFontSize = GetResFontSizeOffset(lpTemplate);
    *pwFontSize = static_cast<WORD>((8.0 * theApp.GetFontScaleFactor()) + 0.5);
    // ^-- crash on write to *pwFontSize

我相信代码在 returned 结构中找到正确的目标内存位置,因为该结构在预期偏移处具有正确的签名(0xFFFF 将其标识为 DLGTEMPLATEEX 而不仅仅是 DLGTEMPLATE --尽管代码没有任何错误检查(如果不是),我单步执行了计算指向字体大小成员的指针的代码。它指向一个值为“8”的 DWORD,这对于系统字体大小来说似乎是合理的。

WORD* CMk20Dlg::GetResFontSizeOffset(DLGTEMPLATE* lpTemplate)
{
   WORD* pwPtr = reinterpret_cast<WORD*>(lpTemplate);

    // lpTemplate is in the format of a DLGTEMPLATEEX
    // Refer Win32 SDK documentation of "struct" DLGTEMPLATEEX
    ++pwPtr;        // dlgVer
    ++pwPtr;        // signature
    /* ^-- At this point *pwPtr has value 0xFFFF in the debugger, indicating DLGTEMPLATEEX */

     /* The ++'ing of pwPtr continues through the rest of the struct members,
        then it skips over the menu array, window class array, title, etc.  Finally: */

    // We are now pointing at the font size word
    return pwPtr;
}

return 值指向 DWORD“8”,当代码试图通过写入指针实际将该“8”更改为另一个数字时,它在 Visual Studio 2019 调试器中崩溃记忆 "write access violation".

我对我在这里看到的东西有很多顾虑。

首先,像这样破坏这个内存位置甚至有什么用吗? OS 如何看到值发生了变化?我希望正确的方法是像 "SetDlgFontSize" 之类的一些 Win32 函数调用,而不是在数据结构中四处探索。

其次,这段代码是如何工作的?它是否适用于 Windows 的旧版本,但不适用于 Windows 10?现在失败是不是因为我把项目升级到了VS 2019(之前是2012)。还是 VS 2019 调试器(或调试版本)先发制人地在我不应该写的操作系统结构的内存页上设置写保护位?

第三,我需要决定是通过注释掉崩溃代码还是设置字体大小来解决此问题 "properly." 这取决于有问题的代码是否真的产生过影响。如果没有,我可以将其注释掉,但如果我需要正确复制效果,那么到目前为止我找到的唯一用于执行此操作的 MFC 代码将字体大小和字体系列设置在一起。那么我还需要添加代码来找出正确的字体...

只要有问题的对话框是在 clobber 之后创建的,这样做就可以改变用于创建对话框的模板(只要内存权限允许写入),但是我会希望资源内存是只读的,所以我很惊讶这在没有访问冲突的情况下工作。

之所以有效,是因为每次创建对话框时都会读取对话框模板。如果在执行此序列时已经创建了对话框,那么它应该没有任何效果。

加载资源(通过 FindResource + LoadResource + LockResource)将整个对话框模板复制到显式分配的缓冲区,进行任何需要的更改,然后使用 DialogBoxIndirect 或 DialogBoxIndirectParam 从中实例化对话框会更有意义-内存模板(而不是来自资源 ide identifier)。制作这样的副本会 side-step 内存权限问题的可能性。

我想您也可以使用 VirtualProtect 并使有问题的页面可写,但我认为这是解决此类问题的一种令人讨厌的方法。