一种清除windows通知区域“僵尸”图标的方案——XP系统解决方案
XP下“僵尸”圖標的解決方案
? ? ? ? 從《一種清除windows通知區域“僵尸”圖標的方案——問題分析》(以后簡稱《問題分析》)一文中分析的通知區域結構可以看出,XP的通知區域結構是相對簡單的。如果我們解決了XP下的問題,那么Win7上的問題至少解決了一半——只有那個隱藏系統通知區域需要研究下。所以,我們先選擇XP作為研究對象。(轉載請指明出于breaksoftware的csdn博客)
? ? ? ? ?從SPY++抓到的結構可以看出來,通知區域是一個ToolbarWindow32窗口類對象。ToolbarWindow32是windows一個標準的窗口類,雖然我們不能直接調用該類的一些方法來操作元素,但是該類對象響應Toolbar的標準消息。這也是我最開始的解決思路。
獲取圖標信息
? ? ? ? 在嘗試去掉“僵尸”圖標之前,有幾個問題擺在我們面前
- 如何獲取圖標的總數
- 如何枚舉到每個圖標
- 如何獲取圖標的信息
- 如何找到我們創建的圖標
? ? ? ? 查閱MSDN后,我發現Toolbar消息中TB_BUTTONCOUNT可以獲取到通知區域圖標總數。如此,我們便可以一個For循環枚舉到所有圖標。問題1和2便迎刃而解。那如何找到我們創建的圖標呢?在《問題分析》一文中,我們在介紹初始化圖標時,特別提出,我給圖標Tip取了一個晦澀的名字——“中A英1文”。如此設計,也是因為我試圖通過這個特征來識別圖標(雖然這種方案存在不嚴謹性,但是圖標的識別不是本文的主要的探討課題)。
? ? ? ? 那么我們如何去獲取圖標的文字呢?查閱MSDN,發現TB_GETBUTTONTEXT這個消息可以獲取圖標的文字。其參數說明是
wParam
Command identifier of the button whose text is to be retrieved.
lParam
Pointer to a buffer that receives the button text.? ? ? ? 這兒要注意幾個問題:
- 什么是Command identfier?它和我們For循環傳遞的遞增參數是一致的么?
- lParam指向的地址到底在哪個進程中?因為通知區域的進程載體是Explorer,而Explorer自然不可以訪問到我們進程中的空間。如果這段空間在Explorer進程中,我們進程又如何才能讀取到Explorer進程中的空間呢?
1 Command identifier of the button whose text is to be retrieved.
2 Zero-based index of the button for which to retrieve information.? ? ? ? 可以見得兩者是不可以混用的。這兩者存在一個推導關系,即可以通過2推導出1。實現這個過程的是TB_GETBUTTON消息。 wParam
Zero-based index of the button for which to retrieve information.
lParam
Pointer to the TBBUTTON structure that receives the button information.? ? ? ? lParam指向一個獲取的結構體TBBUTTON信息 typedef struct {int iBitmap;int idCommand;BYTE fsState;BYTE fsStyle;
#ifdef _WIN64BYTE bReserved[6];
#else
#if defined(_WIN32)BYTE bReserved[2];
#endif
#endif DWORD_PTR dwData;INT_PTR iString;
} TBBUTTON, *PTBBUTTON, *LPTBBUTTON;? ? ? ? 其中idCommand就是我們之前提到的Command identifier。? ? ? ? 問題1我們解決了,那么問題2呢?我們不僅在發送TB_GETBUTTONTEXT消息時遇到這個問題,在發送TB_GETBUTTON消息時也會遇到。這個問題說到底就是跨進程的數據通信,那使用內存映射?No!管道?No!Socket?No!…… 我們采用《VC下提前注入進程的一些方法2——遠線程帶參數》一文中的方法——在其他進程空間中申請可讀寫內存。具體的代碼如下。
#define SENDMSGTIME 100CSendMessageToProcess::CSendMessageToProcess(void)
{
}CSendMessageToProcess::CSendMessageToProcess( HWND hwnd, ENUMWINOWSENDPROC lpFunc )
{m_hWnd = hwnd;m_lpFun = lpFunc;m_stRemoteBuffer.lpBuffer = NULL;m_stRemoteBuffer.dwBufferSize = BUFFERSIZE;m_hProcess = NULL;InitializeRemoteBuffer();
}CSendMessageToProcess::~CSendMessageToProcess(void)
{UnitializeRemoteBuffer();
}BOOL CSendMessageToProcess::InitializeRemoteBuffer()
{if ( NULL != m_stRemoteBuffer.lpBuffer ) {return TRUE;}BOOL bSuc = FALSE;do {if ( 0 == m_stRemoteBuffer.dwBufferSize ) {::SetLastError(ERROR_INVALID_PARAMETER);break;}DWORD dwProcessID = 0;GetWindowThreadProcessId(m_hWnd, &dwProcessID);m_hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ|PROCESS_VM_WRITE , FALSE, dwProcessID);if ( NULL == m_hProcess ) {break;;}m_stRemoteBuffer.lpBuffer = (LPBYTE)VirtualAllocEx(m_hProcess, NULL, m_stRemoteBuffer.dwBufferSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);if ( NULL == m_stRemoteBuffer.lpBuffer ) {break;}bSuc = TRUE;}while (0);return bSuc;
}VOID CSendMessageToProcess::UnitializeRemoteBuffer()
{if ( NULL != m_hProcess ) {VirtualFreeEx(m_hProcess, m_stRemoteBuffer.lpBuffer, 0, MEM_RELEASE);m_stRemoteBuffer.lpBuffer = NULL;m_stRemoteBuffer.dwBufferSize = 0;CloseHandle(m_hProcess);m_hProcess = NULL;}
}BOOL CSendMessageToProcess::SendRemoteMessage( DWORD dwMsgID, DWORD dwIndexOrCmdId, PStBufferInfo lpstLocalBuffer, DWORD& dwRet )
{BOOL bSuc = FALSE;do {if ( NULL == lpstLocalBuffer ) {if ( 0 == ::SendMessageTimeout( m_hWnd, dwMsgID, dwIndexOrCmdId, 0, SMTO_ABORTIFHUNG, SENDMSGTIME, &dwRet ) ) {break;}bSuc = TRUE;break;}if ( NULL == lpstLocalBuffer || NULL == m_stRemoteBuffer.lpBuffer ) {::SetLastError(ERROR_INVALID_PARAMETER);break;}if ( NULL == m_stRemoteBuffer.lpBuffer || 0 == m_stRemoteBuffer.dwBufferSize ) {::SetLastError(ERROR_INVALID_PARAMETER);break;}if ( lpstLocalBuffer->dwBufferSize > m_stRemoteBuffer.dwBufferSize ) {::SetLastError(ERROR_MORE_DATA);break;}DWORD dwWrite = 0;if ( NULL != lpstLocalBuffer->lpBuffer && 0 != lpstLocalBuffer->dwBufferSize ) {// 使用傳入的數據初始化數據空間if ( FALSE == WriteProcessMemory(m_hProcess, m_stRemoteBuffer.lpBuffer, lpstLocalBuffer->lpBuffer, lpstLocalBuffer->dwBufferSize , &dwWrite) ) {break;}}if ( 0 == ::SendMessageTimeout( m_hWnd, dwMsgID, dwIndexOrCmdId, (LPARAM)m_stRemoteBuffer.lpBuffer, SMTO_ABORTIFHUNG, SENDMSGTIME, &dwRet ) ) {break;}DWORD dwRead = 0;if ( NULL != lpstLocalBuffer->lpBuffer && 0 != lpstLocalBuffer->dwBufferSize ) {if ( FALSE == ReadProcessMemory( m_hProcess, m_stRemoteBuffer.lpBuffer, lpstLocalBuffer->lpBuffer, lpstLocalBuffer->dwBufferSize, &dwRead)) {break;}}bSuc = TRUE;} while (0);return bSuc;
}? ? ? ? 這兒有個特別需要說明的:我使用的是SendMessageTimeout發送消息,而不是SendMessage。因為我們對其他進程發送消息時,我們無法保證其他進程在處理消息時是否會非常費時,或者壓根就不返回,從而導致我們發起消息的線程被堵塞。一般來說,比較好的方式是采用PostMessage向其他進程發送消息。但是由于我們的流程需要同步執行,所以在保證同步的情況下,同時兼顧SendMessage執行超時,采用了SendMessageTimeout方式。 打通一切問題后,我們把枚舉方法也給出 BOOL CSendMessageToProcess::EnumChild()
{BOOL bSuc = FALSE;do {DWORD dwChildCount = 0;if ( 0 == ::SendMessageTimeout(m_hWnd, TB_BUTTONCOUNT, 0, 0, SMTO_ABORTIFHUNG, SENDMSGTIME, &dwChildCount) ) {break;}BOOL bContinue = TRUE;for ( DWORD dwIndex = 0; dwIndex < dwChildCount && bContinue; dwIndex++ ) {bContinue = m_lpFun(this, dwIndex);}bSuc = TRUE;} while (0);return bSuc;
}? ? ? ? 這兒m_lpFun是一個回調函數,其用來判定和執行清除“僵尸”圖標的目的。其回調函數是 static BOOL CALLBACK DealChildComp(CSendMessageToProcess* lpThis, DWORD dwIndex)
{BOOL bContinue = TRUE;do {BYTE byLocalBuffer[BUFFERSIZE] = {0};WCHAR wchBuffer[BUFFERSIZE] = {0};memset(byLocalBuffer, 0, sizeof(byLocalBuffer));TBBUTTON TBButton;memset(&TBButton, 0, sizeof(TBButton));StBufferInfo stLocalBuffer;stLocalBuffer.lpBuffer = &TBButton;stLocalBuffer.dwBufferSize = sizeof(TBBUTTON);DWORD dwSucGetButton = 0;if ( FALSE == lpThis->SendRemoteMessage(TB_GETBUTTON, dwIndex, &stLocalBuffer, dwSucGetButton ) ) {continue;}memset(byLocalBuffer, 0, sizeof(byLocalBuffer));stLocalBuffer.lpBuffer = byLocalBuffer;stLocalBuffer.dwBufferSize = sizeof(byLocalBuffer);DWORD dwRetLenth = 0xFFFFFFFF;if ( FALSE == lpThis->SendRemoteMessage(TB_GETBUTTONTEXTW, TBButton.idCommand, &stLocalBuffer, dwRetLenth ) ) {continue;}if ( 0xFFFFFFFF == dwRetLenth ) {continue;}memset(wchBuffer, 0, sizeof(wchBuffer));memcpy_s( wchBuffer, sizeof(wchBuffer), byLocalBuffer, dwRetLenth * sizeof(WCHAR));std::cout<<wchBuffer<<std::endl;if ( 0 != lstrcmp( L"中A英1文", wchBuffer)) {continue;}g_bFind = TRUE;//bContinue = lpThis->ClearIcon(TBButton.idCommand, FALSE);bContinue = lpThis->ClearIcon(dwIndex, TRUE);} while (0);return bContinue;
}? ? ? ? 下一步,我們將要將重心放在如何清除“僵尸”圖標上。注意一下ClearIcon這個函數,從我上面列出的代碼可以看出,貌似是存在兩種方法。是的,的確是兩種。之后我將詳細介紹這兩種方法。 直接刪除“僵尸”圖標
?? ? ? ?MSDN上給出了Toolbar消息的所有名稱,其中最開始吸引我的是TB_DELETEBUTTON這個消息。在經過上面一系列努力后,我們只要發送這個消息給通知區域便可以干凈利索優雅的清除“僵尸”圖標。其參數說明是
wParam
Zero-based index of the button to delete.
lParam
Must be zero.? ? ? ? 我們的代碼是 BOOL CSendMessageToProcess::ClearIcon(DWORD dwIndexOrCmdID, BOOL bByDelete/* = FALSE*/)
{BOOL bContinue = FALSE;do {if ( bByDelete ) {DWORD dwNULL = 0;SendRemoteMessage(TB_DELETEBUTTON, dwIndexOrCmdID, NULL, dwNULL);bContinue = TRUE;break;}? ? ? ? 這樣看似沒什么問題了,但是我們看下執行的結果
? ? ? ? ?看下紅色框住的區域(非水印內容),“僵尸”圖標的確是被刪除了,但是任務欄的長度卻沒有變化!這是這種最優雅的方法的最失敗的地方,也正是這個缺陷促使我再次尋找能徹底解決的方法。但是其實這個技術缺陷可以通過產品設計的方法來規避:我們進程啟動時,清除“僵尸”圖標,然后創建一個可用的圖標。這樣會促使通知區域重新計算區域大小,從而觸發一次自動調整。
模擬鼠標方式是最符合“常規”的一種方法。因為正常情況下,鼠標劃過“僵尸”圖標會導致通知區域刪除之。那么我們在程序中模擬鼠標滑動,不也是可以解決這個問題么?現在的問題就集中在以下問題上模擬鼠標方式去除“僵尸”圖標
- 如何計算出“僵尸”圖標的位置
- 發送哪些消息
wParam
Command identifier of the button.
lParam
Pointer to a RECT structure that will receive the bounding rectangle information.? ? ? ? 第二個問題我們可以在計算好滑動區域的情況下,發送WM_MOUSEMOVE,對應的代碼是
else {RECT rc;StBufferInfo stLocalBuffer;stLocalBuffer.lpBuffer = &rc;stLocalBuffer.dwBufferSize = sizeof(rc);DWORD dwGetRectRet = 0;if ( FALSE == SendRemoteMessage(TB_GETRECT, dwIndexOrCmdID, &stLocalBuffer, dwGetRectRet ) ) {break;;}if ( 0 == dwGetRectRet ) {break;}MouseMoveOnWindow(m_hWnd, &rc);}} while (0);return bContinue;
}VOID MouseMoveOnWindow( HWND hWnd, LPRECT lpRect )
{if ( FALSE == ::IsWindow(hWnd) || NULL == lpRect) {return;}RECT rc;memset(&rc, 0, sizeof(rc));if ( FALSE == ::GetWindowRect(hWnd, &rc) ) {return;}// 滑動的點POINT pt;DWORD dwStart = 0;DWORD dwEnd = 0;dwStart = lpRect->left;dwEnd = lpRect->right;pt.y = (lpRect->top + lpRect->bottom)/2;for ( DWORD loffset = dwStart; loffset < dwEnd; loffset = loffset + STOPLENGTH) {pt.x = loffset; ::PostMessage(hWnd, WM_MOUSEMOVE, 0, MAKELONG(pt.x, pt.y));Sleep(10);}
}
之前的方案看似已經可以滿足我們的需求了,但是其中存在一個問題:如果我們進程權限很低,將無法打開Explorer進程,進而之后的發送消息也將無法談起。當我在MSDN查閱通知區域相關文檔時,無意中看到我們可以使用IAccessible接口查詢到通知區域的信息。 我并不打算在此詳細介紹IAccessible接口的枚舉方法,只是要提出一點:因為我們無法使用IAccessible接口刪除圖標,所以我們在找到“僵尸”圖標后,將使用上面的模擬鼠標的方法。對應的代碼是使用IAccessible接口枚舉并刪除“僵尸”圖標
#include "Accessible.h"BOOL EnumAccessible( HWND hwnd, XENUMACCESSIBLEPROC lpEnumAccessibleProc )
{BOOL bRet = FALSE;_ASSERTE (::IsWindow(hwnd));_ASSERTE(lpEnumAccessibleProc);if (::IsWindow(hwnd) && lpEnumAccessibleProc){CComPtr<IAccessible> pIAcc;HRESULT hr = AccessibleObjectFromWindow(hwnd, OBJID_WINDOW, IID_IAccessible, (void**)&pIAcc);if (SUCCEEDED(hr) && pIAcc){CComVariant varChild;CComPtr<IAccessible> pIAccChild;FindChild(pIAcc, pIAccChild, varChild, lpEnumAccessibleProc);bRet = TRUE;}}return bRet;
}static BOOL FindChild(CComPtr<IAccessible>& pIAccParent, CComPtr<IAccessible>& pIAccChild,CComVariant& varChild, XENUMACCESSIBLEPROC lpEnumAccessibleProc)
{BOOL bSuc = FALSE;BOOL bContinue = TRUE;do {if ( NULL == pIAccParent || NULL ==lpEnumAccessibleProc) {break;}BOOL bContinue = TRUE;CComPtr<IEnumVARIANT> pEnum;HRESULT hr = pIAccParent->QueryInterface(IID_IEnumVARIANT, (PVOID*) &pEnum);if ( SUCCEEDED(hr) && pEnum) {pEnum->Reset();}// get child countlong nChildren = 0;unsigned long nFetched = 0;pIAccParent->get_accChildCount(&nChildren);for (long index = 1; (index <= nChildren) && bContinue; index++) {varChild.Clear();if ( pEnum ) {hr = pEnum->Next(1, &varChild, &nFetched );if ( FAILED(hr)) {bContinue = FALSE;break;}}else {varChild.vt = VT_I4;varChild.lVal = index;}// get IDispatch interface for the childCComPtr<IDispatch> pDisp;if ( VT_I4 == varChild.vt ) {hr = pIAccParent->get_accChild(varChild, &pDisp);}else if ( VT_DISPATCH == varChild.vt ) {pDisp = varChild.pdispVal;}// get IAccessible interface for the childCComPtr<IAccessible> pCAcc;if ( NULL != pDisp ) {hr = pDisp->QueryInterface(IID_IAccessible, (void**)&pCAcc);if ( FAILED(hr) ) {continue;}}// get information about the childif ( NULL != pCAcc) {varChild.Clear();varChild.vt = VT_I4;varChild.lVal = CHILDID_SELF;pIAccChild = pCAcc;}else {pIAccChild = pIAccParent;}DWORD dwState = 0;if ( FALSE == GetObjectState(pIAccChild, varChild, dwState) ) {continue;}// check if object is availableif (dwState & STATE_SYSTEM_INVISIBLE ) {continue;}HWND hwndChild = 0;WindowFromAccessibleObject(pIAccChild, &hwndChild);// call enum callbackbContinue = lpEnumAccessibleProc(pIAccChild, varChild, hwndChild);if (bContinue && pCAcc) {bContinue = FindChild(pCAcc, pIAccChild, varChild, lpEnumAccessibleProc);}}bSuc = TRUE;} while (0);return bContinue;
}BOOL GetObjectState( CComPtr<IAccessible>& pAcc, CComVariant& varChild, DWORD& dwState )
{BOOL bRet = FALSE;dwState = 0;do {if ( NULL == pAcc ) {break;}CComVariant varState;HRESULT hr = pAcc->get_accState(varChild, &varState);if ( FAILED(hr) || VT_I4 != varState.vt ) {break;}dwState = varState.lVal;bRet = TRUE;} while (0);return bRet;
}BOOL GetObjectRole( CComPtr<IAccessible>& pAcc, CComVariant& varChild, DWORD& dwRole )
{BOOL bRet = FALSE;dwRole = 0;do {if ( NULL == pAcc ) {break;}CComVariant varRole;HRESULT hr = pAcc->get_accRole(varChild, &varRole);if ( FAILED(hr) || VT_I4 != varRole.vt ) {break;}dwRole = varRole.lVal;bRet = TRUE;} while (0);return bRet;
}BOOL GetObjectName( CComPtr<IAccessible>& pAcc, CComVariant& varChild, LPVOID lpName, DWORD dwBufferSize, DWORD& dwWrite )
{BOOL bRet = FALSE;do {if ( NULL == pAcc || NULL == lpName ) {break;}CComBSTR bstrName;HRESULT hr = pAcc->get_accName(varChild, &bstrName);if ( FAILED(hr) ) {break;}if ( dwBufferSize < bstrName.ByteLength() ) {break;}if ( 0 != memcpy_s(lpName, dwBufferSize, (LPWSTR)bstrName, bstrName.ByteLength()) ) {break;}dwWrite = bstrName.ByteLength();bRet = TRUE;} while (0);return bRet;
}BOOL GetObjectRoleString( CComPtr<IAccessible>& pAcc, CComVariant& varChild, LPVOID lpBuffer, DWORD dwBufferLength, DWORD& dwWrite )
{BOOL bRet = FALSE;DWORD dwRole = 0;do {if ( NULL == pAcc || NULL == lpBuffer ) {break;}CComVariant varRole;HRESULT hr = pAcc->get_accRole(varChild, &varRole);if ( FAILED(hr) ) {break;}if ( VT_I4 == varRole.vt ) {dwRole = varRole.lVal;dwWrite = ::GetRoleText(dwRole, (LPWSTR)lpBuffer, dwBufferLength / sizeof(WCHAR));if ( 0 == dwWrite ) {break;}dwWrite *= sizeof(WCHAR);}else if ( VT_BSTR == varRole.vt ) {CComBSTR bstrRoletext(varRole.bstrVal);if ( dwBufferLength < bstrRoletext.ByteLength() ) {break;}if ( 0 != memcpy_s(lpBuffer, dwBufferLength, (LPWSTR)bstrRoletext, bstrRoletext.ByteLength())) {break;}dwWrite = bstrRoletext.ByteLength();}bRet = TRUE;} while (0);return bRet;
}BOOL GetObjectDescription( CComPtr<IAccessible>& pAcc, CComVariant& varChild, LPVOID lpBuffer, DWORD dwBufferLength, DWORD& dwWrite )
{BOOL bRet = FALSE;do {if ( NULL == pAcc || NULL == lpBuffer ) {break;}CComBSTR bstrDescription;HRESULT hr = pAcc->get_accDescription(varChild, &bstrDescription);if ( FAILED(hr) ) {break;}if ( dwBufferLength < bstrDescription.ByteLength() ) {break;}if ( 0 != memcpy_s(lpBuffer, dwBufferLength, (LPWSTR)bstrDescription, bstrDescription.ByteLength()) ) {break;}dwWrite = bstrDescription.ByteLength();bRet = TRUE;} while (0);return bRet;
}BOOL GetObjectValue( CComPtr<IAccessible>& pAcc, CComVariant& varChild, LPVOID lpBuffer, DWORD dwBufferLength, DWORD& dwWrite )
{BOOL bRet = FALSE;do {if ( NULL == pAcc || NULL == lpBuffer ) {break;}CComBSTR bstrValue;HRESULT hr = pAcc->get_accValue(varChild, &bstrValue);if ( FAILED(hr) ) {break;}if ( dwBufferLength < bstrValue.ByteLength() ) {break;}if ( 0 != memcpy_s(lpBuffer, dwBufferLength, (LPWSTR)bstrValue, bstrValue.ByteLength() ) ) {break;}dwWrite = bstrValue.ByteLength();bRet = TRUE;} while (0);return bRet;
}BOOL GetObjectLocation( CComPtr<IAccessible>& pAcc, CComVariant& varChild, RECT& rect )
{BOOL bRet = FALSE;do {if ( NULL == pAcc) {break;}HRESULT hr = pAcc->accLocation(&rect.left, &rect.top, &rect.right, &rect.bottom, varChild);if ( FAILED(hr) ) {break;}// accLocation returns width and heightrect.right += rect.left;rect.bottom += rect.top;bRet = TRUE;} while (0);return bRet;
}總結
以上是生活随笔為你收集整理的一种清除windows通知区域“僵尸”图标的方案——XP系统解决方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一种清除windows通知区域“僵尸”图
- 下一篇: 一种清除windows通知区域“僵尸”图