windows托盘图标检测

由于业务需要,研究一下如何用程序判断右下角拖盘中某图标在什么时候隐藏到溢出区。经过多方资料查找,最终输出可用代码。支持windows xp至windows 10的32位和64位下的图标检测。

32位程序中读取64位explorer进程中的信息时,网上大多只给出了32位的结构体定义,所以根据调试时查看内存分布,调整了下64位的结构体。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181

typedef struct TBBUTTON64 {
int iBitmap;
int idCommand;
BYTE fsState; // TBSTATE_CHECKED 等
BYTE fsStyle; // TBSTYLE_BUTTON 等
BYTE bReserved[6];
int64_t dwData;
int64_t iString;
BYTE test[100];
} ;
struct TRAYDATA64
{
int64_t hWnd;
uint32_t uID;
uint32_t uCallbackMessage;
uint32_t Reserved1[2];
int64_t hIcon;
uint32_t Reserved2[4];
TCHAR szExePath[MAX_PATH];
TCHAR szTip[128];
};

struct TRAYDATA32
{
HWND hwnd;
UINT uID;
UINT uCallbackMessage;
DWORD Reserved[2];
HICON hIcon;
DWORD Reserved2[3];
TCHAR szExePath[MAX_PATH];
TCHAR szTip[128];
};

bool IsWow64ProcessEx( HANDLE hProcess )
{
/*判断ntdll中的导出函数,可知是否是64位OS*/
HMODULE hMod = GetModuleHandle(_T("ntdll.dll"));
FARPROC x64fun = ::GetProcAddress(hMod,"ZwWow64ReadVirtualMemory64");
if(!x64fun)
{
return false;
}

/*利用IsWow64Process判断是否是x64进程*/
typedef BOOL(WINAPI *pfnIsWow64Process)(HANDLE,PBOOL);
pfnIsWow64Process fnIsWow64Process = NULL;

hMod = GetModuleHandle(_T("kernel32.dll"));
fnIsWow64Process = (pfnIsWow64Process)GetProcAddress(hMod,"IsWow64Process");
if(!fnIsWow64Process) //如果没有导出则判定为32位
{
return false;
}

BOOL bX64;
if(!fnIsWow64Process(hProcess, &bX64))
{
return false;
}

return !bX64;
}

template<typename BUTTONTYPE, typename TRAYDATA_TYPE>
void EnumIcons(size_t buttonNumber, HANDLE hProcess, LPVOID pAddress, HWND hWnd)
{
for( int i=0; i< buttonNumber; i++ )
{
long lRet = 0;

// 读取图标结构体
BUTTONTYPE buttonInfo = {0};
DWORD dwBytes = 0;
::WriteProcessMemory(hProcess, pAddress, &buttonInfo, sizeof(buttonInfo), &dwBytes);
lRet = ::SendMessage( hWnd, TB_GETBUTTON, i, long(pAddress) );
lRet = ReadProcessMemory( hProcess, pAddress, &buttonInfo, sizeof(buttonInfo), &dwBytes);

// 读取TrayData
TRAYDATA_TYPE trayData;
lRet = ::ReadProcessMemory(hProcess, (LPVOID)buttonInfo.dwData, &trayData, sizeof(trayData), NULL);

// 方法1. 直接从ButtonInfo成员的string地址读取图标标题
TCHAR szTips[1024];
::ReadProcessMemory(hProcess, (void*)(buttonInfo.iString),szTips, 1024, &dwBytes);
OutputDebugString(szTips);

// 方法2. 从TrayData中读取图标标题和程序路径
OutputDebugString(trayData.szTip);
OutputDebugString(trayData.szExePath);

// 方法3. 发送消息,读取图标标题
SendMessage(hWnd, TB_GETBUTTONTEXT, buttonInfo.idCommand, LPARAM(LPARAM(pAddress) + sizeof(buttonInfo)));
::ReadProcessMemory(hProcess, (void*)(LPARAM(pAddress) + sizeof(buttonInfo)),szTips, 1024, &dwBytes);
OutputDebugString(szTips);
}
}

// 判断在通知区域是否有托盘图标
BOOL IsTrayIconExsitInNotifyArea()
{
HWND hWnd = NULL, hWndPager = NULL;
unsigned long ulPID = 0;
long lRet = 0, lButtons = 0;
HANDLE hProcess = NULL;
LPVOID pAddress = NULL;
long lTextAdr = 0, lHwndAdr = 0, lHwnd = 0, lButtonID = 0;
char strBuff[1024] = { 0 };
char_t *pStr = NULL;
char *pTemp = NULL;

hWnd = ::FindWindow( _T("Shell_TrayWnd"), NULL );
hWnd = ::FindWindowEx( hWnd, 0, _T("TrayNotifyWnd"), NULL );
hWndPager = ::FindWindowEx( hWnd, 0, _T("SysPager"), NULL );
if( hWndPager == NULL )
{
hWnd = ::FindWindowEx( hWnd, 0, _T("ToolbarWindow32"), NULL ); // 对于Win2000,没有SysPager窗口
}
else
{
hWnd = ::FindWindowEx( hWndPager, 0, _T("ToolbarWindow32"), NULL );
}

lRet = GetWindowThreadProcessId( hWnd, &ulPID );
hProcess = OpenProcess(PROCESS_ALL_ACCESS|PROCESS_VM_OPERATION|PROCESS_VM_READ|PROCESS_VM_WRITE, 0, ulPID );
pAddress = VirtualAllocEx( hProcess, 0, 0x4096, MEM_COMMIT, PAGE_READWRITE );
lButtons = ::SendMessage( hWnd, TB_BUTTONCOUNT, 0, 0 );

if (IsWow64ProcessEx(hProcess))
{
EnumIcons<TBBUTTON64, TRAYDATA64>(lButtons, hProcess, pAddress, hWnd);
}
else
{
EnumIcons<TBBUTTON, TRAYDATA32>(lButtons, hProcess, pAddress, hWnd);
}

VirtualFreeEx( hProcess, pAddress, 0X4096, MEM_RELEASE );
CloseHandle( hProcess );

return FALSE;
}


// 判断在托盘溢出区域是否有托盘图标
BOOL IsTrayIconExsitInOverflowWindow()
{
HWND hWnd = NULL, hWndPager = NULL;
unsigned long ulPID = 0;
long lRet = 0, lButtons = 0;
HANDLE hProcess = NULL;
LPVOID pAddress = NULL;
long lTextAdr = 0, lHwndAdr = 0, lHwnd = 0, lButtonID = 0;
char strBuff[1024] = { 0 };
char_t *pStr = NULL;
char *pTemp = NULL;

// 对于Win7、Win8系统,新增了通知溢出区域,所以要检查该区域中是否有托盘图标
hWnd = ::FindWindow( _T("NotifyIconOverflowWindow"), NULL );
hWnd = ::FindWindowEx( hWnd, 0, _T("ToolbarWindow32"), NULL );

lRet = GetWindowThreadProcessId( hWnd, &ulPID );
hProcess = OpenProcess(PROCESS_ALL_ACCESS|PROCESS_VM_OPERATION|PROCESS_VM_READ|PROCESS_VM_WRITE, 0, ulPID );
pAddress = VirtualAllocEx( hProcess, 0, 0x4096, MEM_COMMIT, PAGE_READWRITE );
lButtons = ::SendMessage( hWnd, TB_BUTTONCOUNT, 0, 0 );

if (IsWow64ProcessEx(hProcess))
{
EnumIcons<TBBUTTON64, TRAYDATA64>(lButtons, hProcess, pAddress, hWnd);
}
else
{
EnumIcons<TBBUTTON, TRAYDATA32>(lButtons, hProcess, pAddress, hWnd);
}

VirtualFreeEx( hProcess, pAddress, 0X4096, MEM_RELEASE );
CloseHandle( hProcess );

return FALSE;
}

参考资料: