上一篇文章介紹了“TabSiPlus”是如何進行代碼注入的,本篇將介紹如何構建一個外掛軟體最重要的部分,也就是為其擴充功能的定製代碼。本文前面提到過,由於windows進程管理的限制,擴充代碼必須以動態連結程式庫的形式掛載到被掛程式的進程空間中,使用上一篇介紹的方法已經可以通過建立遠程線程的方式啟動一個線程,讓這個線程載入我們的定製動態連結程式庫,現在就看看這個動態連結程式庫是如何?的。
首先這是一個動態連結程式庫,因為考慮到擴充功能中有大量的介面操作,所以選擇支援MFC,同時,還要提供一個名為“InitFunc”的匯出函數,供在被掛程式中啟動的遠程線程調用,以初始化外掛動態連結程式庫,這個匯出函數的原型是這樣的:
typedef DWORD (WINAPI *PFN)();
沒有參數,但是有一個傳回值用於表示初始化是否成功。現在就用Visual C++的嚮導產生一個支援MFC的動態連結程式庫的架構,並手工添加一個名為“InitFunc”的匯出函數,如果你還不清楚怎麼做,那麼可以停止看本文了,因為本文可能對你毫無用處。
在產生的程式碼中,MFC對DllMain進行了封裝,所以有了一個CxxxApp的類,xxx與你的動態連結程式庫的名字一致,TabSiPlus使用的是CTabSiPlusApp,現在有三個地方需要特別注意,一個是CTabSiPlusApp::InitInstance(),一個是CTabSiPlusApp::ExitInstance(),另一個就是我們的匯出函數“InitFunc”。當遠程線程中LoadLibrary()調用我們的定製動態連結程式庫時,CTabSiPlusApp::InitInstance()被調用,當FreeLibrary()調用發生時,CTabSiPlusApp::ExitInstance()被調用,當然,伴隨而出現的還有兩個函數調用,那就是CTabSiPlusApp類的建構函式和解構函式,部分初始化代碼也可以放在建構函式中完成,不過並不推薦這樣做,因為如果因為建構函式觸發異常導致LoadLibrary()失敗,那麼隨後的解構函式也不會被調用,因為建構函式沒有完成對象的構造,同時,由於LoadLibrary()失敗,使得FreeLibrary()調用分支沒有執行,那麼導致CTabSiPlusApp::ExitInstance()也沒有被調用,這會引起資源釋放的異常。
很顯然,CTabSiPlusApp::InitInstance()的調用發生在InitFunc函數的調用之前,所以要控制好初始化代碼之前的先後關係。CTabSiPlusApp::InitInstance()中布置對資源初始化的代碼,而諸如建立檔案標籤欄視窗,Hook “Source Insight”視窗訊息,管理這些訊息的代碼則可以布置到InitFunc函數中實現。這裡需要注意的是由於我們的外掛代碼是以動態連結程式庫的形式掛載到“Source Insight”進程中的,所以它沒有訊息迴圈,所有的視窗UI系統無法正常工作,解決的辦法有兩個,一個是在InitFunc函數建立視窗之後人為地添加一個訊息迴圈,關於這一點如何?可以參考Windows SDK編程的方法;另一個方法就是不要把主要的工作放在InitFunc,而是在InitFunc函數中再建立一個本地線程,把視窗UI這些麻煩的東西放在這個線程中處理,這樣就可以利用這個線程的訊息迴圈使視窗UI系統工作起來,這樣做還有一個好處,就是InitFunc函數可以立即返回,載入外掛的宿主程式也可以及時得到外掛的載入情況,以便根據情況安排下一次載入動作(就是調用CreateRemoteThread()),同時還可以及時釋放在被掛程式中分配的記憶體。TabSiPlus就是採用的第二種方法,下面就是TabSiPlus的InitFunc函數實現,當然省去了一些代碼,主要核心就是一行:
DWORD WINAPI InitFunc()
{
//其它初始化操作
g_pTabWndUIThread = (CTabWndUIThread *)AfxBeginThread(RUNTIME_CLASS(CTabWndUIThread),THREAD_PRIORITY_NORMAL,0,0,NULL);
//其它操作
return (g_pTabWndUIThread != NULL);
}
在CTabWndUIThread類的InitInstance()函數中建立標籤欄視窗,hook “Source Insight”中相關視窗的訊息:
BOOL CTabWndUIThread::InitInstance()
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
g_pSiFrameWnd = new CSIFrameWnd(); //CWnd::FromHandle(hDevStudioWnd);
g_pSiFrameWnd->Attach(hWndSIFrame); //hook SI主視窗
HWND hMDIWnd = g_pSiFrameWnd->GetMDIClientWnd();
//UINT uThressID = GetCurrentThreadId();
// create the tabs window
m_pTabbarWnd = new CTabBarsWnd();
m_pTabbarWnd->Create(CWnd::FromHandle(g_pSiFrameWnd->GetSafeHwnd()),
RBS_BANDBORDERS | RBS_AUTOSIZE | RBS_FIXEDORDER | RBS_DBLCLKTOGGLE,
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | CBRS_TOP | CBRS_SIZE_FIXED, AFX_IDW_REBAR );
m_pMainWnd = m_pTabbarWnd;//這很重要,否則這個線程就無法退出
g_pSiFrameWnd->SetTabbarWnd(m_pTabbarWnd->GetSafeHwnd());
g_MdiChildMng.SetTabbarWnd(m_pTabbarWnd->GetSafeHwnd());
m_pTabbarWnd->SetWindowPos(CWnd::FromHandle(hMDIWnd)->GetWindow(GW_HWNDPREV), 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
g_pSiMDIClientWnd = new CSiMDIWnd();
g_pSiMDIClientWnd->SetTabbarWnd(m_pTabbarWnd->GetSafeHwnd());
DebugTracing(gnDbgLevelNormalDebug,_T("MDI Client Attach..."));
g_pSiMDIClientWnd->Attach(hMDIWnd);
DebugTracing(gnDbgLevelNormalDebug,_T("MDI Client Enum..."));
g_pSiMDIClientWnd->EnumMdiChildWnd(g_MdiChildMng,TRUE);
DebugTracing(gnDbgLevelNormalDebug,_T("MDI Client Enum end (%d)"),g_MdiChildMng.GetChildCount());
pGlobalActiveSIWindow = g_MdiChildMng.LookupMdiChild(g_pSiMDIClientWnd->MDIGetActive(NULL));
g_pSiMDIClientWnd->SetManaging(true);
return TRUE;
}
是不是很象標準單文檔結構的MFC程式中的CxxxApp::InitInstance()函數?特別是對m_pMainWnd的賦值?對m_pMainWnd賦值其實很重要,否則線程就會直接退出,對m_pMainWnd賦值還有一個好處,就是關閉m_pTabbarWnd視窗就會中止CTabWndUIThread線程,這和標準單文檔結構的MFC程式中的結果一樣。
CTabWndUIThread::InitInstance()函數中有很多是對“Source Insight”內部視窗進行hook的代碼,那麼TabSiPlus是如何得到這些視窗的控制代碼呢,又是如何關聯它們之間的訊息呢,請看下篇:給Source Insight做個外掛系列之四--分析“Source Insight”
Source Insignt檔案標籤外掛:TabSiPlus的:
http://blog.csdn.net/orbit/