给Source Insight做个外挂系列之五--Insight “TabSiPlus”
生活随笔
收集整理的這篇文章主要介紹了
给Source Insight做个外挂系列之五--Insight “TabSiPlus”
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
“TabSiPlus 外掛插件”主要有兩部分組成,分別是“外掛插件加載器”和“插件動態(tài)庫”。“插件動態(tài)庫”完成Source Insight窗口的Hook,顯示Tab標簽欄,截獲Source Insight的窗口消息并根據(jù)消息調(diào)整Tab標簽欄等功能,這是一個動態(tài)鏈接庫,不能主動執(zhí)行,所以還需要一個“外掛插件加載器”,“外掛插件加載器”是一個獨立的可執(zhí)行文件,它的主要功能就是發(fā)現(xiàn)Source Insight的進程,并把插件動態(tài)庫“注入”到Source Insight的進程中。本文將簡單介紹這兩個組件是如何協(xié)同工作,最終實現(xiàn)插件的完整功能。
????
??? 首先介紹“外掛插件加載器”,它就是TabSiHost.exe組件,它的功能就是監(jiān)視Source Insight進程,發(fā)現(xiàn)Source Insight的主窗口,將插件主體動態(tài)庫插入到Source Insight。關(guān)于如何發(fā)現(xiàn)Source Insight和代碼注入的方法請查看本系列的第一篇《發(fā)現(xiàn)Source Insight》和第二篇《將本地代碼注入到Source Insight》,此處就不再贅述。此處主要介紹TabSiHost.exe組件的工作流程以及一些實現(xiàn)細節(jié)問題。TabSiHost.exe組件的主函數(shù)是MainFunction(),這個函數(shù)首先獲取參數(shù),根據(jù)參數(shù)判斷是加載還是卸載插件,然后是啟動一個定時器,這個定時器負責監(jiān)視Source Insight進程,最后進入一個消息循環(huán),等待進程結(jié)束。定時器的主要任務(wù)就是檢查是否有Source Insight進程創(chuàng)建,其實就是枚舉當前全部程序的主窗口,查看是否有Source Insight的主窗口,如果有則要根據(jù)設(shè)置確定是否對其實施代碼注入。這里需要注意的是不能重復(fù)注入,因為每次注入都要創(chuàng)建一個Tab標簽欄,重復(fù)注入會導(dǎo)致很難看的事情發(fā)生。如何確定Source Insight已經(jīng)被Hook過了呢?其實可以有很多方法,比如創(chuàng)建內(nèi)核對象進行標識,這只內(nèi)存標志等等,TabSiPlus使用了一種很巧妙的方式,就是在Source Insight主窗口的窗口標題字符串中插入一個標識字符串“with TabSiPlus”,這樣定時器在枚舉程序的主窗口的時候就可以根據(jù)窗口標題判斷這個進程實例是否已經(jīng)被Hook過了,同時,這個字符串標識還宣示了“TabSiPlus 插件”的存在,一舉兩得。簡單了解了“外掛插件加載器”的工作過程之后,下面將重點介紹“插件動態(tài)庫”的工作過程。
??? 本系列的第三篇已經(jīng)介紹過TabSiPlus.dll組件中的代碼執(zhí)行順序,當LoadLibrary()調(diào)用發(fā)生時,CTabSiPlusApp類的構(gòu)造函數(shù)和InitInstance()首先被調(diào)用,緊接著導(dǎo)出函數(shù)Initialize()被調(diào)用,Initialize()函數(shù)創(chuàng)建了CTabWndUIThread線程,CTabWndUIThread線程在初始化函數(shù)InitInstance()中創(chuàng)建并顯示一個Tab標簽欄,于是插件開始工作了。說起來簡單,其實還是有很多細節(jié),最重要的一點就是Hook(子類化) Source Insight的內(nèi)部窗口。毫無疑問,要使外掛插件能夠在沒有對外接口的情況下無縫地介入到Source Insight當中,必需要Hook它的窗口消息,這樣才能覺察它的變化,所以,在看是內(nèi)部探索之前,先看看TabSiPlus.dll組件的內(nèi)部Hook類關(guān)系圖:
圖 5.1 TabSiPlus.dll組件內(nèi)部Hook類關(guān)系圖
這個關(guān)系圖基本上和Source Insight的實際窗口關(guān)系是一致的,只是多了一個CMdiChildManagment類,這個類的主要目的是維護CSiMDIWnd類的所有子窗口。
??? CSIFrameWnd類主要是Hook Source Insight的主窗口消息,CSIFrameWnd類主要關(guān)心的消息有兩個,一個是WM_SETTEXT消息,另一個是WM_DESTROY消息。處理WM_SETTEXT的目的是為了在窗口標題發(fā)生變化的時候,添加本插件的標識字符串:with TabSiPlus。當Windows需要改變標題文字時,WM_SETTEXT消息就會觸發(fā),其中第二個參數(shù)就是存放窗口標題的緩沖區(qū),這是一個地址,將其替換成我們定制的字符串地址就能起到定制窗口標題的作用:
??? ??? case WM_SETTEXT:
??? ??? {
??? ??? ??? static TCHAR szBuffer[512];
??? ??? ??? if(lParam)
??? ??? ??? {
??? ??? ??? ??? lstrcpy(szBuffer,(LPCTSTR)lParam);
??? ??? ??? ??? lstrcat(szBuffer,lpszTextMark);
??? ??? ??? ??? lParam = (LPARAM)szBuffer;
??? ??? ??? }
??? ??? ??? DebugTracing(gnDbgLevelNormalDebug,_T("SIFrameWnd reach WM_SETTEXT, %s"),g_szCurDircetory);
??? ??? ??? break;
??? ??? }
處理WM_DESTROY消息的目的是要知道Source Insight什么時候被關(guān)閉了,主窗口是最后被銷毀的窗口,攔截主窗口的WM_DESTROY消息使Tab標簽欄能夠有機會在主窗口銷毀前銷毀自己,然后等待CTabWndUIThread線程結(jié)束,既能夠避免資源泄漏,也能防止Source Insight出現(xiàn)異常:
??? if(uMsg == WM_DESTROY)
??? {
??? ??? DebugTracing(gnDbgLevelNormalDebug,_T("SIFrameWnd reach WM_DESTROY, Destroy Tabbar"));
??? ??? bExitPadding = TRUE;
??? ??? g_pSiFrameWnd->DestroyTabbarWnd();
??? ??? if(g_pTabWndUIThread != NULL)
??? ??? {
??? ??? ??? ::WaitForSingleObject(g_pTabWndUIThread->m_hThread,INFINITE);
??? ??? ??? g_pTabWndUIThread = NULL;
??? ??? }
??? ??? DebugTracing(gnDbgLevelNormalDebug,_T("SIFrameWnd reach WM_DESTROY, Waiting Tabbar thread end"));
??? }
??? 接下來是CSiMDIWnd類。CSIFrameWnd類的實例在Hook主窗口后,就會調(diào)用CSIFrameWnd::GetMDIClientWnd()函數(shù)獲取MDIClient窗口,MDIClient窗口是所有MDI Child窗口的直接父窗口,它處理MDI Child窗口的消息,比如創(chuàng)建、最大化、最小化以及銷毀等等,所以非常重要。和工具欄,狀態(tài)欄一樣,MDIClient窗口是主窗口的一個子窗口,前文也講過,它有固定的窗口類名:MDIClient,所以獲取MDIClient窗口的方法就是遍歷主窗口的所有子窗口,找到窗口類名是MDIClient的子窗口,幸運的是,Source Insight的主窗口有且只有一個MDIClient窗口,看代碼:
/*這個函數(shù)總是返回一個有效的窗口句柄,即使不是MDIClient*/
HWND CSIFrameWnd::GetMDIClientWnd()
{
??? TCHAR cClassName[256];
? HWND hMDIWnd = g_pSiFrameWnd->GetTopWindow();
? ::GetClassName(hMDIWnd, cClassName, sizeof(cClassName));
? while(lstrcmp(cClassName, lpszMdiClientWndClass) != 0)
? {
??? hMDIWnd = ::GetNextWindow(hMDIWnd, GW_HWNDNEXT);
??? ASSERT(hMDIWnd);
??? GetClassName(hMDIWnd, (LPTSTR)cClassName, sizeof(cClassName));
? }
??? return hMDIWnd;
}
找到MDIClient的窗口句柄后,就用CSiMDIWnd類Hook它,Hook之后的第一件事就是遍歷MDIClient窗口的所有子窗口(就是Source Insight的視圖窗口,窗口類名是si_Sw),分別用CSiWindow類Hook全部子窗口,并將它們納入到CMdiChildManagment類的管理中。CSiMDIWnd類是整個插件中最重要的部分,它主要處理幾個重要的Windows消息,分別是WM_MDICREATE、WM_MDIDESTROY、WM_MDIACTIVATE和WM_WINDOWPOSCHANGING。在前文《分析“Source Insight”》中已經(jīng)分析過,當Source Insight打開一個新文件時,主窗口會創(chuàng)建一個窗口類名是si_Sw的窗口,由于Source Insight使用的是Windows標準MDI界面,所以截獲MDIClient窗口的WM_MDICREATE消息就可以獲取新打開的窗口,從窗口的標題中分析出文件名,然后在Tab標簽欄創(chuàng)建相應(yīng)的標簽。同樣當WM_MDIDESTROY發(fā)生時,表示關(guān)閉了一個文檔,此時要從Tab標簽欄中刪除相應(yīng)的標簽,當WM_MDIACTIVATE發(fā)生時,要對Tab標簽欄中對應(yīng)的標簽突出顯示。要在Source Insight中顯示一個標簽窗口其實很簡單,以主窗口為父窗口創(chuàng)建一個子窗口就可以了,但這樣硬插進去的一個窗口會破壞整個Source Insight的界面,不是覆蓋其它子窗口就是被其它子窗口覆蓋,所以要處理好窗口之間的位置關(guān)系很重要。要正確處理窗口位置關(guān)系,還要Hook MDIClient窗口的WM_WINDOWPOSCHANGING消息,當MDIClient窗口的大小或位置或窗口Z-Order將要發(fā)生變化時,Windows就會發(fā)送WM_WINDOWPOSCHANGING消息給MDIClient窗口,獲取窗口的位置,大小等等信息,這個消息的LParam參數(shù)是一個WINDOWPOS結(jié)構(gòu)(指針),其中cx和cy兩個參數(shù)就是窗口的寬度和高度,修改這兩個值就可以欺騙Windows按照我們的要求調(diào)整MDIClient窗口的位置,通常我們要減少cy的值,給我們的Tab標簽欄留出空間。
??? 接下來是CSiWindow類,這個subclass主要是HookMDIClient窗口的子窗口,子窗口的窗口句柄和CSiWindow對象之間有一個映射關(guān)系,這個關(guān)系由CMdiChildManagment類維護。CSiWindow主要是Hook了WM_GETTEXT消息,目的是獲取子窗口標題的變化,然后在標簽欄中相應(yīng)的有所表現(xiàn),比如,對于具有只讀屬性的文件,Source Insight會在其文件名前面顯示一個“!”,當只讀屬性被取消后,“!”也會消失,CSiWindow要捕獲這個變化,然后通知標簽欄相應(yīng)地修改窗口對應(yīng)的標簽欄。再比如,當一個文件被修改過后,文件名后面會有一個“*”,當執(zhí)行文件保存后,這個“*”號要消失,這些也要在標簽欄同步變化。
??? 最后是標簽欄窗口CTabBarsWnd,這個類代碼最多,其實功能很簡單,就是維護一個TabCtrl控件,處理界面操作,根據(jù)主窗口的位置和大小調(diào)整自己的大小和位置。本來CTabBarsWnd就是一個普通的窗口類,但是在本插件中我讓它繼承自CReBar類,為的是偷一些懶,那個功能按鈕其實就是只有一個圖標的工具條,將它和TabCtrl控件作為兩個Band添加到Rebar上,省了很多麻煩。
??? TabSiPlus的內(nèi)部結(jié)構(gòu)就是這樣,很簡單啊,不是嗎?
Source Insight文件標簽外掛:TabSiPlus的下載地址:
http://www.winmsg.com/download/tabsiplus.zip?
????
??? 首先介紹“外掛插件加載器”,它就是TabSiHost.exe組件,它的功能就是監(jiān)視Source Insight進程,發(fā)現(xiàn)Source Insight的主窗口,將插件主體動態(tài)庫插入到Source Insight。關(guān)于如何發(fā)現(xiàn)Source Insight和代碼注入的方法請查看本系列的第一篇《發(fā)現(xiàn)Source Insight》和第二篇《將本地代碼注入到Source Insight》,此處就不再贅述。此處主要介紹TabSiHost.exe組件的工作流程以及一些實現(xiàn)細節(jié)問題。TabSiHost.exe組件的主函數(shù)是MainFunction(),這個函數(shù)首先獲取參數(shù),根據(jù)參數(shù)判斷是加載還是卸載插件,然后是啟動一個定時器,這個定時器負責監(jiān)視Source Insight進程,最后進入一個消息循環(huán),等待進程結(jié)束。定時器的主要任務(wù)就是檢查是否有Source Insight進程創(chuàng)建,其實就是枚舉當前全部程序的主窗口,查看是否有Source Insight的主窗口,如果有則要根據(jù)設(shè)置確定是否對其實施代碼注入。這里需要注意的是不能重復(fù)注入,因為每次注入都要創(chuàng)建一個Tab標簽欄,重復(fù)注入會導(dǎo)致很難看的事情發(fā)生。如何確定Source Insight已經(jīng)被Hook過了呢?其實可以有很多方法,比如創(chuàng)建內(nèi)核對象進行標識,這只內(nèi)存標志等等,TabSiPlus使用了一種很巧妙的方式,就是在Source Insight主窗口的窗口標題字符串中插入一個標識字符串“with TabSiPlus”,這樣定時器在枚舉程序的主窗口的時候就可以根據(jù)窗口標題判斷這個進程實例是否已經(jīng)被Hook過了,同時,這個字符串標識還宣示了“TabSiPlus 插件”的存在,一舉兩得。簡單了解了“外掛插件加載器”的工作過程之后,下面將重點介紹“插件動態(tài)庫”的工作過程。
??? 本系列的第三篇已經(jīng)介紹過TabSiPlus.dll組件中的代碼執(zhí)行順序,當LoadLibrary()調(diào)用發(fā)生時,CTabSiPlusApp類的構(gòu)造函數(shù)和InitInstance()首先被調(diào)用,緊接著導(dǎo)出函數(shù)Initialize()被調(diào)用,Initialize()函數(shù)創(chuàng)建了CTabWndUIThread線程,CTabWndUIThread線程在初始化函數(shù)InitInstance()中創(chuàng)建并顯示一個Tab標簽欄,于是插件開始工作了。說起來簡單,其實還是有很多細節(jié),最重要的一點就是Hook(子類化) Source Insight的內(nèi)部窗口。毫無疑問,要使外掛插件能夠在沒有對外接口的情況下無縫地介入到Source Insight當中,必需要Hook它的窗口消息,這樣才能覺察它的變化,所以,在看是內(nèi)部探索之前,先看看TabSiPlus.dll組件的內(nèi)部Hook類關(guān)系圖:
圖 5.1 TabSiPlus.dll組件內(nèi)部Hook類關(guān)系圖
這個關(guān)系圖基本上和Source Insight的實際窗口關(guān)系是一致的,只是多了一個CMdiChildManagment類,這個類的主要目的是維護CSiMDIWnd類的所有子窗口。
??? CSIFrameWnd類主要是Hook Source Insight的主窗口消息,CSIFrameWnd類主要關(guān)心的消息有兩個,一個是WM_SETTEXT消息,另一個是WM_DESTROY消息。處理WM_SETTEXT的目的是為了在窗口標題發(fā)生變化的時候,添加本插件的標識字符串:with TabSiPlus。當Windows需要改變標題文字時,WM_SETTEXT消息就會觸發(fā),其中第二個參數(shù)就是存放窗口標題的緩沖區(qū),這是一個地址,將其替換成我們定制的字符串地址就能起到定制窗口標題的作用:
??? ??? case WM_SETTEXT:
??? ??? {
??? ??? ??? static TCHAR szBuffer[512];
??? ??? ??? if(lParam)
??? ??? ??? {
??? ??? ??? ??? lstrcpy(szBuffer,(LPCTSTR)lParam);
??? ??? ??? ??? lstrcat(szBuffer,lpszTextMark);
??? ??? ??? ??? lParam = (LPARAM)szBuffer;
??? ??? ??? }
??? ??? ??? DebugTracing(gnDbgLevelNormalDebug,_T("SIFrameWnd reach WM_SETTEXT, %s"),g_szCurDircetory);
??? ??? ??? break;
??? ??? }
處理WM_DESTROY消息的目的是要知道Source Insight什么時候被關(guān)閉了,主窗口是最后被銷毀的窗口,攔截主窗口的WM_DESTROY消息使Tab標簽欄能夠有機會在主窗口銷毀前銷毀自己,然后等待CTabWndUIThread線程結(jié)束,既能夠避免資源泄漏,也能防止Source Insight出現(xiàn)異常:
??? if(uMsg == WM_DESTROY)
??? {
??? ??? DebugTracing(gnDbgLevelNormalDebug,_T("SIFrameWnd reach WM_DESTROY, Destroy Tabbar"));
??? ??? bExitPadding = TRUE;
??? ??? g_pSiFrameWnd->DestroyTabbarWnd();
??? ??? if(g_pTabWndUIThread != NULL)
??? ??? {
??? ??? ??? ::WaitForSingleObject(g_pTabWndUIThread->m_hThread,INFINITE);
??? ??? ??? g_pTabWndUIThread = NULL;
??? ??? }
??? ??? DebugTracing(gnDbgLevelNormalDebug,_T("SIFrameWnd reach WM_DESTROY, Waiting Tabbar thread end"));
??? }
??? 接下來是CSiMDIWnd類。CSIFrameWnd類的實例在Hook主窗口后,就會調(diào)用CSIFrameWnd::GetMDIClientWnd()函數(shù)獲取MDIClient窗口,MDIClient窗口是所有MDI Child窗口的直接父窗口,它處理MDI Child窗口的消息,比如創(chuàng)建、最大化、最小化以及銷毀等等,所以非常重要。和工具欄,狀態(tài)欄一樣,MDIClient窗口是主窗口的一個子窗口,前文也講過,它有固定的窗口類名:MDIClient,所以獲取MDIClient窗口的方法就是遍歷主窗口的所有子窗口,找到窗口類名是MDIClient的子窗口,幸運的是,Source Insight的主窗口有且只有一個MDIClient窗口,看代碼:
/*這個函數(shù)總是返回一個有效的窗口句柄,即使不是MDIClient*/
HWND CSIFrameWnd::GetMDIClientWnd()
{
??? TCHAR cClassName[256];
? HWND hMDIWnd = g_pSiFrameWnd->GetTopWindow();
? ::GetClassName(hMDIWnd, cClassName, sizeof(cClassName));
? while(lstrcmp(cClassName, lpszMdiClientWndClass) != 0)
? {
??? hMDIWnd = ::GetNextWindow(hMDIWnd, GW_HWNDNEXT);
??? ASSERT(hMDIWnd);
??? GetClassName(hMDIWnd, (LPTSTR)cClassName, sizeof(cClassName));
? }
??? return hMDIWnd;
}
找到MDIClient的窗口句柄后,就用CSiMDIWnd類Hook它,Hook之后的第一件事就是遍歷MDIClient窗口的所有子窗口(就是Source Insight的視圖窗口,窗口類名是si_Sw),分別用CSiWindow類Hook全部子窗口,并將它們納入到CMdiChildManagment類的管理中。CSiMDIWnd類是整個插件中最重要的部分,它主要處理幾個重要的Windows消息,分別是WM_MDICREATE、WM_MDIDESTROY、WM_MDIACTIVATE和WM_WINDOWPOSCHANGING。在前文《分析“Source Insight”》中已經(jīng)分析過,當Source Insight打開一個新文件時,主窗口會創(chuàng)建一個窗口類名是si_Sw的窗口,由于Source Insight使用的是Windows標準MDI界面,所以截獲MDIClient窗口的WM_MDICREATE消息就可以獲取新打開的窗口,從窗口的標題中分析出文件名,然后在Tab標簽欄創(chuàng)建相應(yīng)的標簽。同樣當WM_MDIDESTROY發(fā)生時,表示關(guān)閉了一個文檔,此時要從Tab標簽欄中刪除相應(yīng)的標簽,當WM_MDIACTIVATE發(fā)生時,要對Tab標簽欄中對應(yīng)的標簽突出顯示。要在Source Insight中顯示一個標簽窗口其實很簡單,以主窗口為父窗口創(chuàng)建一個子窗口就可以了,但這樣硬插進去的一個窗口會破壞整個Source Insight的界面,不是覆蓋其它子窗口就是被其它子窗口覆蓋,所以要處理好窗口之間的位置關(guān)系很重要。要正確處理窗口位置關(guān)系,還要Hook MDIClient窗口的WM_WINDOWPOSCHANGING消息,當MDIClient窗口的大小或位置或窗口Z-Order將要發(fā)生變化時,Windows就會發(fā)送WM_WINDOWPOSCHANGING消息給MDIClient窗口,獲取窗口的位置,大小等等信息,這個消息的LParam參數(shù)是一個WINDOWPOS結(jié)構(gòu)(指針),其中cx和cy兩個參數(shù)就是窗口的寬度和高度,修改這兩個值就可以欺騙Windows按照我們的要求調(diào)整MDIClient窗口的位置,通常我們要減少cy的值,給我們的Tab標簽欄留出空間。
??? 接下來是CSiWindow類,這個subclass主要是HookMDIClient窗口的子窗口,子窗口的窗口句柄和CSiWindow對象之間有一個映射關(guān)系,這個關(guān)系由CMdiChildManagment類維護。CSiWindow主要是Hook了WM_GETTEXT消息,目的是獲取子窗口標題的變化,然后在標簽欄中相應(yīng)的有所表現(xiàn),比如,對于具有只讀屬性的文件,Source Insight會在其文件名前面顯示一個“!”,當只讀屬性被取消后,“!”也會消失,CSiWindow要捕獲這個變化,然后通知標簽欄相應(yīng)地修改窗口對應(yīng)的標簽欄。再比如,當一個文件被修改過后,文件名后面會有一個“*”,當執(zhí)行文件保存后,這個“*”號要消失,這些也要在標簽欄同步變化。
??? 最后是標簽欄窗口CTabBarsWnd,這個類代碼最多,其實功能很簡單,就是維護一個TabCtrl控件,處理界面操作,根據(jù)主窗口的位置和大小調(diào)整自己的大小和位置。本來CTabBarsWnd就是一個普通的窗口類,但是在本插件中我讓它繼承自CReBar類,為的是偷一些懶,那個功能按鈕其實就是只有一個圖標的工具條,將它和TabCtrl控件作為兩個Band添加到Rebar上,省了很多麻煩。
??? TabSiPlus的內(nèi)部結(jié)構(gòu)就是這樣,很簡單啊,不是嗎?
Source Insight文件標簽外掛:TabSiPlus的下載地址:
http://www.winmsg.com/download/tabsiplus.zip?
版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的给Source Insight做个外挂系列之五--Insight “TabSiPlus”的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 给Source Insight做个外挂系
- 下一篇: 给Source Insight做个外挂系