给Source Insight做个外挂系列之六--“TabSiPlus”的其它问题
生活随笔
收集整理的這篇文章主要介紹了
给Source Insight做个外挂系列之六--“TabSiPlus”的其它问题
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
關于如何做一個Source Insight外掛插件的全過程都已經寫完了,這么一點東西拖了一年的時間才寫完,足以說明我是一個很懶的人,如果不是很多朋友的關心和督促,恐怕是難以完成了。許多朋友希望順著本文的思路也作一個類似于TabSiPlus功能的Source Insight外掛插件,抱歉讓他們等了這么長時間,看了本文或許能讓大家消消氣(大頭在后面)。其實即使不是為了給Source Insight做外掛插件,本文的很多方法都可以用于給其它軟件做外掛。
??? 盡管本文介紹了做TabSiPlus外掛插件的完整過程,但是要做一個有使用價值的外掛插件還有很多細節要注意,首先是穩定,插入到Source Insight進程中的代碼一定要考慮周到,仔細地測試所有分支,確保不能頻繁地掛死Source Insight;其次,附加的功能一定不能干擾Source Insight的正常工作,比如窗口消息的處理;最后就是在界面上要能和Source Insight融為一體,插件創建的窗口一定不能覆蓋Source Insight的窗口。
????
??? 以上都是空話,現在用具體的例子來說幾個細節,比如TabsiPlus提供了直接根據標簽關閉文檔窗口的功能,由于文檔窗口創建的時候已經獲取到窗口的句柄,所以TabsiPlus的第一個版本就使用DestroyWindow() API直接關閉了文檔窗口,從外表看確實大到了效果,但是卻隱藏了一個BUG,那就是雖然窗口被關閉了,但是Source Insight并不知道文檔窗口被關閉了,相應的文件依然處于打開狀態,如果文檔修改過,這樣關閉窗口甚至不會提醒用戶保存文檔。在自己的程序中關閉窗口當然直接DestroyWindow()就行了,但是既然你的代碼是“寄人籬下”,就要按照“別人”的規矩來。通過Spy++觀察Source Insight窗口的消息,發現Source Insight窗口只對WM_CLOSE消息會有正常的反映,也就是說Source Insight可能在OnClose()中處理了關閉文件和提示保存修改的操作(很奇怪DestroyWindow()后為什么沒有觸發Source Insight的OnClose()處理被調用,看來遠程注入的代碼確實有很多需要注意的地方),后來的版本使用SendMessage將一個WM_CLOSE消息發給文檔窗口,這樣就很好地解決了這個問題。
????
??? 上面的問題還沒完,讓窗口消失就算是關閉了嗎,有沒有考慮窗口的Focus? 如果關閉某個擁有“焦點(Focus)”的子窗口,Windows會激活此焦點窗口的一個兄弟窗口,通常是上一個擁有焦點的窗口,這個相信使用Windows的人都知道,我也是這么認為的,但是,這一點在外掛中失靈了,在TabSiPlus的線程中,關閉當前擁有焦點的文檔窗口后,其它的文檔窗口標題欄竟然都是灰的,也就是Source Insight的MDIClient窗口沒有選擇上一個焦點窗口激活,怎么辦?看看TabSiPlus中關閉文檔窗口的代碼:
void CTabBarsWnd::CloseSIWindow(CSiWindow*& pWindow)
{
? ASSERT(pWindow);
? HWND hPrevActive = NULL;
? if(pGlobalActiveSIWindow != NULL && pWindow != pGlobalActiveSIWindow)
? {
??? DebugTracing(gnDbgLevelNormalDebug,_T("CTabBarsWnd::CloseSIWindow() pGlobalActiveSIWindow = %x"),pGlobalActiveSIWindow);
??? hPrevActive = pGlobalActiveSIWindow->GetSafeHwnd();
??? m_iLockUpdates++;
? }
? pWindow->SendMessage(WM_CLOSE, 0, 0);//now close it!
? if(hPrevActive != NULL)
? {
??? ::PostMessage(::GetParent(hPrevActive), WM_MDIACTIVATE,(WPARAM)hPrevActive, 0);
? }
}
核心只有一句:
pWindow->SendMessage(WM_CLOSE, 0, 0);//now close it!
卻要圍繞它做很多事情。
??? 再來看一個問題,有沒有考慮過Tab標簽欄上的標簽與實際打開的文檔窗口個數不一致的情況?雖然我們Hook可MDI_CREATE消息,但是依然有一些窗口創建是TabsiPlus插件無法感知的,比如Source Insight支持內置宏語言,通過宏進行窗口操作TabsiPlus插件無法感知,還有一種情況是Source Insight對于一些不激活的文檔通常不是立即創建窗口,而是在激活的時候才創建窗口顯示文檔,當用戶通過Windows菜單看到的已經打開的文件與你的Tab標簽欄不一致會怎么想?沒有好的辦法,TabSiPlus使用一個定時器處理這種不一致,具體代碼在CTabBarsWnd::OnTimer()中。
??? 還有一個問題,如何安全地關閉外掛插件?有一種方法先關閉Source Insight,然后關閉加載器TabSiHost.exe,然后再打開Source Insight。讓自己接受這種方案都很難,更何況別人,如果能夠在插件中提供一個界面,通過用戶選擇可以直接退出插件就好了,實現這一點關鍵是Source Insight內部關閉Tab標簽窗口后如何中止加載器TabSiHost.exe,如果不中止TabSiHost.exe,TabSiHost.exe會再次加載TabsiPlus.dll插件。TabsiPlus通過內核對象完成與TabSiHost.exe的同步:
void CTabBarsWnd::ShutDownTabSiPlus()
{
? HANDLE hAnotherTabSiHostEvent = NULL;
? LPCTSTR szGlobalKernelName = _T("Local//TabSiHostIsAlreadyRunning");
? hAnotherTabSiHostEvent = CreateEvent( NULL, TRUE, FALSE, szGlobalKernelName );??
? DWORD dwer = GetLastError();??
? if(dwer == ERROR_ALREADY_EXISTS)
? {
??? ResetEvent( hAnotherTabSiHostEvent );
? }
? ::CloseHandle(hAnotherTabSiHostEvent);
? g_pSiMDIClientWnd->SetManaging(false);
? DestroyWindow();
}
??? 還有,TabSiPlus內部窗口之前傳遞數據都是通過自定義消息進行的,原因就是Tab標簽窗口與Source Insight的窗口是工作在不同的線程中的,在線程之間只有句柄是安全的,向窗口句柄發送消息要比直接操縱數據要安全。還有,當使用了Source Insight的查找字符串功能時,Source Insight會打開一個窗口顯示搜索的結果,這個窗口的窗口類名和代碼窗口一樣,都是si_Sw,但是其窗口標題卻和代碼窗口的標題不一樣,這個要區分。其它的細節還有很多,就不一樣列舉了,具體看代碼吧。
??? 羅嗦了半天,代碼在哪里?本來想隨本文一起上傳的,但是這個Blog上傳附件太麻煩,只好放到我的CSDN資源里了,大家可以到我的空間下載源代碼。本文附帶的代碼是一份精簡的TabSiPlus插件代碼,為了大家理解代碼,我去掉了全部裝飾性的代碼和附加功能代碼,包括很多預防性代碼,這樣做地目的就是為了大家在學習源代碼時能夠將注意力集中在框架上而不是枝節瑣事。盡管如此,這是一個完整的可工作的Tab標簽欄,演示了本文寫的全部內容。除了我的代碼之外,源代碼中還使用了一些自由代碼,使用時請注意相關作者的權利要求。
??? 代碼的編譯很簡單,用VC 6.0打開直接Build就行了,為什么不升級到VC7 or VC8?其實我很懶。調試的時候注意相關的資源文件要在同一個目錄中,TaiSiHost.exe的調試比較簡單,直接加載就行了,調試TabSiPlus.dll比較麻煩,首先關閉已經打開的Source Insight程序,然后在Project Setting/Debug 窗口中設置“Executable for debug session:”為Source Insight的主程序,通常是Insight3.exe,再然后運行TabSiHost.exe,最后就可以按F5開始調試了。整個過程就是:按下F5后,VC根據調試設置啟動insight3.exe,已經運行的TabSiHost.exe發現啟動了Source Insight,就會遠程代碼注入到insight3.exe,于是insight3.exe就會加載TabSiPlus.dll,這樣就可以調試了。
????
????
Source Insignt文件標簽外掛:TabSiPlus的下載地址:
http://www.winmsg.com/download/tabsiplus.zip
??? 盡管本文介紹了做TabSiPlus外掛插件的完整過程,但是要做一個有使用價值的外掛插件還有很多細節要注意,首先是穩定,插入到Source Insight進程中的代碼一定要考慮周到,仔細地測試所有分支,確保不能頻繁地掛死Source Insight;其次,附加的功能一定不能干擾Source Insight的正常工作,比如窗口消息的處理;最后就是在界面上要能和Source Insight融為一體,插件創建的窗口一定不能覆蓋Source Insight的窗口。
????
??? 以上都是空話,現在用具體的例子來說幾個細節,比如TabsiPlus提供了直接根據標簽關閉文檔窗口的功能,由于文檔窗口創建的時候已經獲取到窗口的句柄,所以TabsiPlus的第一個版本就使用DestroyWindow() API直接關閉了文檔窗口,從外表看確實大到了效果,但是卻隱藏了一個BUG,那就是雖然窗口被關閉了,但是Source Insight并不知道文檔窗口被關閉了,相應的文件依然處于打開狀態,如果文檔修改過,這樣關閉窗口甚至不會提醒用戶保存文檔。在自己的程序中關閉窗口當然直接DestroyWindow()就行了,但是既然你的代碼是“寄人籬下”,就要按照“別人”的規矩來。通過Spy++觀察Source Insight窗口的消息,發現Source Insight窗口只對WM_CLOSE消息會有正常的反映,也就是說Source Insight可能在OnClose()中處理了關閉文件和提示保存修改的操作(很奇怪DestroyWindow()后為什么沒有觸發Source Insight的OnClose()處理被調用,看來遠程注入的代碼確實有很多需要注意的地方),后來的版本使用SendMessage將一個WM_CLOSE消息發給文檔窗口,這樣就很好地解決了這個問題。
????
??? 上面的問題還沒完,讓窗口消失就算是關閉了嗎,有沒有考慮窗口的Focus? 如果關閉某個擁有“焦點(Focus)”的子窗口,Windows會激活此焦點窗口的一個兄弟窗口,通常是上一個擁有焦點的窗口,這個相信使用Windows的人都知道,我也是這么認為的,但是,這一點在外掛中失靈了,在TabSiPlus的線程中,關閉當前擁有焦點的文檔窗口后,其它的文檔窗口標題欄竟然都是灰的,也就是Source Insight的MDIClient窗口沒有選擇上一個焦點窗口激活,怎么辦?看看TabSiPlus中關閉文檔窗口的代碼:
void CTabBarsWnd::CloseSIWindow(CSiWindow*& pWindow)
{
? ASSERT(pWindow);
? HWND hPrevActive = NULL;
? if(pGlobalActiveSIWindow != NULL && pWindow != pGlobalActiveSIWindow)
? {
??? DebugTracing(gnDbgLevelNormalDebug,_T("CTabBarsWnd::CloseSIWindow() pGlobalActiveSIWindow = %x"),pGlobalActiveSIWindow);
??? hPrevActive = pGlobalActiveSIWindow->GetSafeHwnd();
??? m_iLockUpdates++;
? }
? pWindow->SendMessage(WM_CLOSE, 0, 0);//now close it!
? if(hPrevActive != NULL)
? {
??? ::PostMessage(::GetParent(hPrevActive), WM_MDIACTIVATE,(WPARAM)hPrevActive, 0);
? }
}
核心只有一句:
pWindow->SendMessage(WM_CLOSE, 0, 0);//now close it!
卻要圍繞它做很多事情。
??? 再來看一個問題,有沒有考慮過Tab標簽欄上的標簽與實際打開的文檔窗口個數不一致的情況?雖然我們Hook可MDI_CREATE消息,但是依然有一些窗口創建是TabsiPlus插件無法感知的,比如Source Insight支持內置宏語言,通過宏進行窗口操作TabsiPlus插件無法感知,還有一種情況是Source Insight對于一些不激活的文檔通常不是立即創建窗口,而是在激活的時候才創建窗口顯示文檔,當用戶通過Windows菜單看到的已經打開的文件與你的Tab標簽欄不一致會怎么想?沒有好的辦法,TabSiPlus使用一個定時器處理這種不一致,具體代碼在CTabBarsWnd::OnTimer()中。
??? 還有一個問題,如何安全地關閉外掛插件?有一種方法先關閉Source Insight,然后關閉加載器TabSiHost.exe,然后再打開Source Insight。讓自己接受這種方案都很難,更何況別人,如果能夠在插件中提供一個界面,通過用戶選擇可以直接退出插件就好了,實現這一點關鍵是Source Insight內部關閉Tab標簽窗口后如何中止加載器TabSiHost.exe,如果不中止TabSiHost.exe,TabSiHost.exe會再次加載TabsiPlus.dll插件。TabsiPlus通過內核對象完成與TabSiHost.exe的同步:
void CTabBarsWnd::ShutDownTabSiPlus()
{
? HANDLE hAnotherTabSiHostEvent = NULL;
? LPCTSTR szGlobalKernelName = _T("Local//TabSiHostIsAlreadyRunning");
? hAnotherTabSiHostEvent = CreateEvent( NULL, TRUE, FALSE, szGlobalKernelName );??
? DWORD dwer = GetLastError();??
? if(dwer == ERROR_ALREADY_EXISTS)
? {
??? ResetEvent( hAnotherTabSiHostEvent );
? }
? ::CloseHandle(hAnotherTabSiHostEvent);
? g_pSiMDIClientWnd->SetManaging(false);
? DestroyWindow();
}
??? 還有,TabSiPlus內部窗口之前傳遞數據都是通過自定義消息進行的,原因就是Tab標簽窗口與Source Insight的窗口是工作在不同的線程中的,在線程之間只有句柄是安全的,向窗口句柄發送消息要比直接操縱數據要安全。還有,當使用了Source Insight的查找字符串功能時,Source Insight會打開一個窗口顯示搜索的結果,這個窗口的窗口類名和代碼窗口一樣,都是si_Sw,但是其窗口標題卻和代碼窗口的標題不一樣,這個要區分。其它的細節還有很多,就不一樣列舉了,具體看代碼吧。
??? 羅嗦了半天,代碼在哪里?本來想隨本文一起上傳的,但是這個Blog上傳附件太麻煩,只好放到我的CSDN資源里了,大家可以到我的空間下載源代碼。本文附帶的代碼是一份精簡的TabSiPlus插件代碼,為了大家理解代碼,我去掉了全部裝飾性的代碼和附加功能代碼,包括很多預防性代碼,這樣做地目的就是為了大家在學習源代碼時能夠將注意力集中在框架上而不是枝節瑣事。盡管如此,這是一個完整的可工作的Tab標簽欄,演示了本文寫的全部內容。除了我的代碼之外,源代碼中還使用了一些自由代碼,使用時請注意相關作者的權利要求。
??? 代碼的編譯很簡單,用VC 6.0打開直接Build就行了,為什么不升級到VC7 or VC8?其實我很懶。調試的時候注意相關的資源文件要在同一個目錄中,TaiSiHost.exe的調試比較簡單,直接加載就行了,調試TabSiPlus.dll比較麻煩,首先關閉已經打開的Source Insight程序,然后在Project Setting/Debug 窗口中設置“Executable for debug session:”為Source Insight的主程序,通常是Insight3.exe,再然后運行TabSiHost.exe,最后就可以按F5開始調試了。整個過程就是:按下F5后,VC根據調試設置啟動insight3.exe,已經運行的TabSiHost.exe發現啟動了Source Insight,就會遠程代碼注入到insight3.exe,于是insight3.exe就會加載TabSiPlus.dll,這樣就可以調試了。
????
????
Source Insignt文件標簽外掛:TabSiPlus的下載地址:
http://www.winmsg.com/download/tabsiplus.zip
版權聲明:本文為博主原創文章,未經博主允許不得轉載。
總結
以上是生活随笔為你收集整理的给Source Insight做个外挂系列之六--“TabSiPlus”的其它问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 给Source Insight做个外挂系
- 下一篇: Eclipse CDT Hello Wo