一、應用場景
封裝一個OCX控件,該控件的作用是來播放一個視頻文件,需要在一個進程中放置四個控件實例。 由於控件是提供給別人用的,因此需要考慮很多東西。
二、考慮因素
1、控件的父窗口resize時需要控件也隨之resize
子窗體不能知道父窗口的resize情況,因為父窗口不會主動把這一情況通知子窗口。 因此需要放一個鈎子來主動得知父窗口的resize事件,然后告訴控件窗口做出適當的改變。
這里用了一個局部鈎子,即線程鈎子。 被放置鈎子的線程是控件的父窗口所在的線程(這也是考慮第3個因素的原因)。
2、這四個控件是否為同一個父窗口
在該場景中考慮到了多個控件在同一個父窗口的情況:這種情況下,當父窗口resize的時候,就需要通知每一個控件(子窗口)。 又因為多個控件的父窗口可能不是同一個,因此鈎子就要被放置到每一個父窗口所在的線程。
3、執行四個控件UI相關代碼的線程是否為同一個線程
由於控件的所在場景相對無法控制,因此控件的父窗口的代碼有可能是在不同的線程中執行的(雖然99.999%可能性是同一個線程),這就需要在每個不同的線程中放置一個鈎子(這基於一個前提,那就是 放置鈎子的線程 == 執行鈎子回調函數的線程)。
綜合3個因素,可以得出這樣一個 對應關系
一個線程Thread: 被放置鈎子的線程 == 控件的父窗口所在的線程(可能這樣說不准確,但權且這樣說) == 執行鈎子回調函數的線程
一個Hook: 每放置一個鈎子就產生一個HOOK, 對於同一個線程只放置一次,如果四個控件的父窗口不是同一個線程,那么就要放置多個。還因為要在代碼中使用CallNextHook將當前線程對應的鈎子作為參數傳給該函數,因此要保存線程和鈎子的對應關系。
一個父窗口句柄: 即相同父窗口的控件的父窗口
若干個子窗口(控件):同一個父窗口有多個子窗口
對應關系如下
用代碼描述對應關系
//key為父窗口, value為若干個子窗口
typedef map<HWND, vector<HWND> > MYMAP; typedef struct _tagHOOKInfo { HHOOK hHook; //鈎子、線程、父窗口 三者一一對應
MYMAP parentAndOcx; //該父窗口和它的若干個子窗口(控件)
}HOOKInfo, *PHOOKInfo; //key為線程ID , value為該線程對應的{鈎子、父窗口、若干個子窗口 }
map<DWORD, HOOKInfo> g_ThreadParentOcxMap2;
三、關於鈎子相關函數
1、WH_CALLWNDPROC鈎子
這個鈎子專門來截獲SendMessage到窗口過程的消息,在消息被傳遞給窗口過程之前先被其截獲。但是不能更改和丟棄消息。
2、 API 和 數據結構
//放置鈎子
HHOOK SetWindowsHookEx( __in int idHook, //鈎子類型
__in HOOKPROC lpfn, //回調函數
__in HINSTANCE hMod, //模塊句柄,如果是全局鈎子則為DLL句柄,dwThreadID為0; 如果是局部鈎子則為NULL,dwThreadID為某一線程ID
__in DWORD dwThreadId //全局鈎子為0, 句柄鈎子為線程ID
);
SetWindowsHookEx失敗返回NULL,否則成功
WH_WNDWNDPROC對應的回調函數以及其他函數
//回調函數原型 LRESULT CALLBACK CallWndProc( __in int nCode, /*標識是否鈎子回調函數必須處理這個消息。如果該值是HC_ACTION,回調函數必須處理。 如果該值大於等於0,強烈建議調用CallNextHookEx並且返回他的返回值。 如果該值小於0,鈎子回調必須調用CallNextHookEx函數並且不能處理這個消息,然后必須返回CallNextHookEx的返回值。 如果鈎子回調沒有調用CallNextHookEx,則該函數必須返回0. */ __in WPARAM wParam, //標識消息是否由當前線程發送,非0是,0不是。 __in LPARAM lParam //一個指向CWPSTRUCT結構的指針,包含了該消息的許多細節 );
typedef struct { LPARAM lParam; //附加信息 WPARAM wParam; //附加信息 UINT message; //消息值 HWND hwnd; //消息的目標窗口 } CWPSTRUCT, *PCWPSTRUCT;
撤銷鈎子
BOOL UnhookWindowsHookEx( __in HHOOK hhk //SetWindowsHookEx返回值
);
在控件的代碼里獲取其父窗口所在的線程ID,將鈎子放置在該線程里
//根據窗口句柄獲取他所在的進程和線程ID DWORD GetWindowThreadProcessId( __in HWND hWnd, //窗口句柄 __out LPDWORD lpdwProcessId //帶回進程ID ); //該函數返回線程ID, 參數帶回進程ID
四、上代碼
/************************************************************************ 四個視頻控件,假如分別放在四個父窗口上 則每個父窗口對應的vecor子窗口中只有一個元素 假如一個父窗口上放了多個視頻控件,則每個父窗口對應的vector子窗口中有多個元素 另:回調函數的主調線程 應該就是 鈎子被放置的線程 【放置鈎子的線程】很有可能不是【鈎子被放置到的線程】 因此要保存一個 線程--鈎子--父窗口(及其子窗口) 的對應關系 /************************************************************************/ typedef map<HWND, vector<HWND> > MYMAP; typedef struct _tagHOOKInfo { HHOOK hHook; MYMAP parentAndOcx; }HOOKInfo, *PHOOKInfo; map<DWORD, HOOKInfo> g_ThreadParentOcxMap2; LRESULT CALLBACK CallWndProc( __in int nCode, __in WPARAM wParam, __in LPARAM lParam ) { //當前線程ID
DWORD dwCurrentID = GetCurrentThreadId(); PCWPSTRUCT pMsg = (PCWPSTRUCT)lParam; map<DWORD, HOOKInfo>::iterator iter; MYMAP::const_iterator iter2; //取得其 鈎子句柄 父窗口 子窗口
PHOOKInfo pHookInfo = NULL; HHOOK hHook = NULL; MYMAP *pMyMap = NULL; //查找map中主鍵為當前線程ID的元素(認為 鈎子回調函數的主調線程 == 鈎子被設置的線程) //當前接受消息的窗口為某個控件的父窗口
//經過Andy指點:進入此回調函數時就已經說明該線程對應的此鈎子起作用了,那么就一定會找到當前線程對應的鈎子,
//那么,又因為控件的父窗口所在UI線程是不變的,其對應的窗口句柄也不會變,鈎子也一定能找到;
//下面if判斷永遠成立! else不會被執行到! 可以將此段if...else改造為單純取map中value的操作,
//而不必擔心主鍵線程ID 和 主鍵父窗口句柄 不存在的情況!
if ((iter = g_ThreadParentOcxMap2.find(dwCurrentID)) != g_ThreadParentOcxMap2.end() && (iter2 = (iter->second).parentAndOcx.find(pMsg->hwnd)) != iter->second.parentAndOcx.end() ) { pHookInfo = &(iter->second); hHook = pHookInfo->hHook; pMyMap = &(pHookInfo->parentAndOcx); } else {
//else永遠不會被執行到! return 0; } if (nCode == HC_ACTION ) { if (NULL != pMsg && pMsg->message == WM_SIZE) { #if defined _DEBUG OutputDebugString(TEXT("\r\n----------WM_SIZE happened: ")); OutputDebugString(TEXT("-----\r\n")); #endif
for (vector<HWND>::const_iterator iter3 = iter2->second.begin(); iter3 != iter2->second.end(); ++ iter3) { ::PostMessage(*iter3, WM_USER + 255, 0, 0); } } return ::CallNextHookEx(hHook, nCode, wParam, lParam); } else if (nCode < 0 && NULL != hHook) { return ::CallNextHookEx(hHook, nCode, wParam, lParam); } return 0; } extern "C" __declspec(dllexport) BOOL __stdcall StartHook(HWND hParentWnd, HWND hOCXWnd, DWORD dwThreadID) { DWORD dwCurrentID = GetCurrentThreadId(); BOOL bRet = FALSE; if(! ::IsWindow(hParentWnd) || ! ::IsWindow(hOCXWnd)) return FALSE; vector<HWND> hOCXWndVector; hOCXWndVector.push_back(hOCXWnd); HOOKInfo hookInfo; map<DWORD, HOOKInfo>::iterator iterBig; //不存在主鍵為dwThreadID的元素,則為該線程放置鈎子
if ((iterBig = g_ThreadParentOcxMap2.find(dwThreadID)) == g_ThreadParentOcxMap2.end()) { HHOOK hHook = SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)CallWndProc, NULL, dwThreadID ); if(NULL == hHook) return FALSE; hookInfo.parentAndOcx.insert(map<HWND, vector<HWND> >::value_type(hParentWnd, hOCXWndVector)); hookInfo.hHook = hHook; g_ThreadParentOcxMap2.insert(map<DWORD, HOOKInfo>::value_type(dwThreadID, hookInfo)); } else { //存在主鍵為dwThreadID的元素,則不必再設置鈎子,只需要在map值字段(HOOKInfo結構體)中的map插入或追加
PHOOKInfo pHookInfo = &(iterBig->second); //判斷HOOKInfo的map字段中是否存在主鍵為hParent的元素
MYMAP *pMap = &(pHookInfo->parentAndOcx); if (NULL == pMap) return FALSE; MYMAP::iterator iterSmall; //如果沒有 則將map<hParent, hOCXWndVector>插入
if((iterSmall = pMap->find(hParentWnd)) == pMap->end()) { pMap->insert(MYMAP::value_type(hParentWnd, hOCXWndVector)); } //如果有 則將hOCXWnd追加到其值vector中
else { (iterSmall->second).push_back(hOCXWnd); } } return TRUE; } extern "C" __declspec(dllexport) BOOL __stdcall StopHook() { for (map<DWORD, HOOKInfo>::const_iterator iter = g_ThreadParentOcxMap2.begin(); iter != g_ThreadParentOcxMap2.end(); ++ iter) { HHOOK hHook = (iter->second).hHook; if(NULL != hHook) UnhookWindowsHookEx(hHook); } return TRUE; }