问题描述
我有一个来自 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++ 相同的结果:
-
hdr->code 是 UINT 并且 NM_CUSTOMDRAW = -12 似乎很奇怪,所以我必须转换为 (int) 但结果与 Spy++ 的输出匹配。
-
最初我认为我可以做到这一点:
LPNMLISTVIEW pnm = (LPNMLISTVIEW)pcwp->lParam;
...
LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)pnm->lParam;
...
print(lplvcd->clrFace,lplvcd->clrTextBk);
但 pnm->lParam 始终为零; pcwp->wParam 也为零。
- 如何访问 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,而 lParam
为 NMLVCUSTOMDRAW*
,但您将其强制转换为 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 种可能性: