如何在 Win32 中从 NM_CUSTOMDRAW 消息中提取颜色?

问题描述

我有一个来自 x86 程序 (MSVS 2017) 的带有 WH_CALLWNDPROC 的挂钩 dll。代码如下:

extern "C" __declspec(dllexport) LRESULT SysMessageProc(int nCode,WParaM wParam,LParaM lParam)
{
if (nCode < 0) return CallNextHookEx(NULL,nCode,wParam,lParam);

    if (nCode == HC_ACTION) {

        cwpSTRUCT* pcwp = (cwpSTRUCT*)lParam;

        HWND hWnd = pcwp->hwnd;

        char wclass[256]; wclass[0] = 0;
        if (GetClassNameA(hWnd,wclass,255) != 0) {
            
            if (pcwp->message == WM_NOTIFY && (strcmp(wclass,"SysHeader32") == 0 || strcmp(wclass,"SysListView32") == 0)) {

                // LPNMLISTVIEW pnm = (LPNMLISTVIEW)pcwp->lParam;
                // pnm->lParam is always 0

                NMHDR* hdr = (NMHDR*)pcwp->lParam;

                if ((int)hdr->code == NM_CUSTOMDRAW) {

                    char out[256]; out[0] = 0;
                    sprintf_s(out,255,"class: '%s',hWnd: %08X,msg: %08X,ptr: %08X,HDR[%08X,%i,%i]\n",(UINT)hWnd,pcwp->message,(UINT)pcwp->lParam,(UINT)hdr->hwndFrom,hdr->idFrom,(int)hdr->code);

                    FILE *stream;
                    if (fopen_s(&stream,"F:\\tmp\\_dll\\out.txt","a+") == 0) {
                        fprintf(stream,out);
                        fclose(stream);
                    }

                   // how to get
                   // LPNMLVCUSTOMDRAW lplvcd = ???;
                   // lplvcd->clrFace,lplvcd->clrText,lplvcd->clrTextBk ???
                   

               }

          }

     }

当我记录消息时,我得到与 Spy++ 相同的结果:

enter image description here

  1. hdr->code 是 UINT 并且 NM_CUSTOMDRAW = -12 似乎很奇怪,所以我必须转换为 (int) 但结果与 Spy++ 的输出匹配。

  2. 最初我认为我可以做到这一点:

LPNMLISTVIEW pnm = (LPNMLISTVIEW)pcwp->lParam;
...
LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)pnm->lParam;
...
print(lplvcd->clrFace,lplvcd->clrTextBk);

但 pnm->lParam 始终为零; pcwp->wParam 也为零。

  1. 如何访问 LPNMLISTVIEW 和 LPNMLVCUSTOMDRAW 结构?

编辑:

cwpSTRUCT* pcwp = (cwpSTRUCT*)lParam;
HWND hWnd = pcwp->hwnd;
UINT msg = pcwp->message;

char wclass[256]; wclass[0] = 0;
if (RealGetwindowClassA(hWnd,255) != 0) {
                
    if (msg == WM_NOTIFY && (strcmp(wclass,"SysListView32") == 0)) {

        NMHDR* hdr = (NMHDR*)pcwp->lParam;

        if (hdr->code == NM_CUSTOMDRAW) {
            
            if (strcmp(wclass,"SysListView32") == 0) {

                LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)pcwp->lParam;
                
                if (lplvcd) {
                    RECT r = lplvcd->rcText;
                    char out[512]; out[0] = 0;
                    sprintf_s(out,511,\
draw: %08X,rect: (%i,%i)-(%i,%i),face: %08X,txt: %08X,txtBg: %08X\n",msg,(UINT)lplvcd,lplvcd->nmcd.dwDrawStage,r.left,r.top,r.right,r.bottom,lplvcd->clrFace,lplvcd->clrTextBk);
                    
                    FILE *stream;
                    if (fopen_s(&stream,out);
                        fclose(stream);
                    }
                }
            }

示例输出

class: 'SysListView32',hWnd: 000A04F4,msg: 0000004E,ptr: 02E3F33C,绘制:00000001,矩形:(0,1)-(-1241442379,48493508),面:FFB617B5, txt: 02E3F400,txtBg: 6F010726

解决方法

您从错误的 NMLVCUSTOMDRAW 访问 lParam

ListView's NM_CUSTOMDRAW notification 中,wParam 始终为 0,而 lParamNMLVCUSTOMDRAW*,但您将其强制转换为 NMLISTVIEW*,即是错的。 NMLISTVIEW 中没有提供 NM_CUSTOMDRAW。即使有,NMLISTVIEW::lParam 字段也包含 ListView 项的用户定义数据,由应用程序通过 LVM_SETITEM/ListView_SetItem() 设置。所以这不是你想要的。

您需要将 pCWP->lParam 直接转换为 NMLVCUSTOMDRAW*。并且仅适用于 SysListView32 控件。 SysHeader32 控件使用 NMCUSTOMDRAW* 代替,例如:

if (pCWP->message == WM_NOTIFY) {
    ...
    NMHDR* hdr = (NMHDR*)pCWP->lParam;
    ...
    if (hdr->code == NM_CUSTOMDRAW) {
        if (strcmp(wclass,"SysListView32") == 0) {
            LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)pCWP->lParam;
            // use lplvcd as needed...
        }
        else if (strcmp(wclass,"SysHeader32") == 0) {
            LPNMCUSTOMDRAW lpcd = (LPNMCUSTOMDRAW)pCWP->lParam;
            // use lpcd as needed...
        }
        ...
    }
}

通用 NM_CUSTOMDRAW 的文档显示了哪种结构由哪种类型的自定义可绘制控件使用。有 5 种可能性:

image