• Download TabSiPlus_Src - 221.95 KB
  • Download the lastly executable binary file

tabsiplus.gif

Introduction

This article is about the TabSiPlus, a Plus program for Source Insight. Source Insight is a great tool, lot's of programmer, even some Visual Studio user, usingSource Insight as default coding tool.Source Insight has a lot of features to easy the programmers way for viewing and writingcode. I like those features, but I think it missing an important one, that is File Switch Tab. People may wondering and asking "what's the File Switch Tab going on"? Sorry, I really don't known how to name it, many software support this component but give it a very different name. One picture better than thousands of words, look the following pictures, stuffs which encircle by red line is what I called "File Switch Tab".

ueditor.gif
visualStudio.gif


People may argue that using "windows" menu item on menu bar can perform file switching function, but I want a fast and direct way, a small file switch table bar is the bulls eye. Unluckily,Source Insight does not support this bar, even the most recently 3.5.x version. I am a completist (but a fake completist), like many lazy programmers, I make tools myself to cater for my laziness, so I made a plus in tools named TabSiPlus. It insert a switch bar toSource Insight window, user can switch files just a single mouse click on it, that's what I want.

How it works

Adding a switch bar to a MDI(Multi-Document Interface) program is really not a hard work, there are many articles (with examplecodes) on this web site which help programmers add switch bar to their program, but unfortunately, I don't havesource code ofSource Insight. The only way is injecting mycode to main process of Source Insight, for 3.x version, generally, it is insignt3.exe. Since the emphases of this article is not about how to injectcode to remote process, so people who wilder aboutcode injecting can search the article "Three ways to inject yourcode to remote process" on this site for more detail information.

TabSiPlus for Source Insight has two components: "TabSiHost.exe" and "TabSiPlus.dll". "TabSiHost.exe" is a little stub program, it's duty is spy on the system and find new instance ofSource Insight program, than inject thecode contain in "TabSiPlus.dll" to insignt3.exe (forSource Insight 3.x). "TabSiPlus.dll" contain the whole functioncode, it can't run itself, it need TabSiHost.exe to load it to insignt3.exe process context.

There are several ways to find new instance of Source Insignt program on windows system, "TabSiHost.exe" using the simplest way: enumerating all windows on system and filtering them by certian class name or windows title.Source Insight(insignt3.exe) main windows has a fixed class name "si_Frame", so "TabSiHost.exe" put interest only in window that class name is "si_Frame". For safe reason, "TabSiHost.exe" also check the window's title and look for characters pattern: Source Insight. Here is the code, the duty of FindSourceInsightFrameWindow and EnumWindowsProc function is enumerating all windows and the duty of IsSourceInsightFrameWnd function is filtering the window with special class name and windows title.

//LPCTSTR lpszSourceInsight = _T("Source Insight");LPCTSTR lpszSiFrameWndClass = _T("si_Frame");LPCTSTR lpszTextMark = _T(" with TabSiPlus");BOOL IsSourceInsightFrameWnd(HWND hWnd){TCHAR szClassName[128],szTitle[256];int nRtn = GetClassName(hWnd,szClassName,128);if(nRtn == 0)return FALSE;nRtn = GetWindowText(hWnd,szTitle,256);if(nRtn == 0)return FALSE;//class name is si_Framem and windows title has "Source Insignt"if((lstrcmp(lpszSiFrameWndClass,szClassName) == 0) && (StrStr(szTitle,lpszSourceInsight) != NULL)){if(StrStr(szTitle,lpszTextMark) != NULL)//Is windows had been Hooked?return FALSE;return TRUE;}return FALSE;}BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam){BOOL bSuccess = TRUE;if(hwnd != NULL && IsSourceInsightFrameWnd(hwnd)){if(lParam){HWND *pHwnd = (HWND *)lParam;*pHwnd = hwnd;bSuccess = FALSE;//find Source Insight program window}}return bSuccess;}HWND FindSourceInsightFrameWindow(){HWND hSiFrmWnd = NULL;BOOL bRtn = ::EnumWindows(EnumWindowsProc,(LPARAM)&hSiFrmWnd);if(!bRtn && hSiFrmWnd != NULL)return hSiFrmWnd;elsereturn NULL;}

We should pay attention to avoid hooking a SourceInsight program windows twice or more, duplicate hooking a single window would result in unknown behavior. IsSourceInsightFrameWnd function using a little trick to filter the Source Insight program window which had been hooked, that is, Source Insight program window had been hooked only it's window title has no string mark: " with TabSiPlus". When TabSiPlus was inject into aSource Insight process, it modified theSource Insight program window title by adding a pad string " with TabSiPlus", I'll discover it later.

TabSiPlus program using CreateRemoteThread API to load and start "TabSiPlus.dll" in insignt3.exe process context, let's look how it works. "TabSiPlus.dll" is a normal windows DLL(Dynamic-Link Library) which using some MFC(Microsoft Foundation Class) features. When TabSiPlus.dll has been loaded into insignt3.exe process context, the constructor of class CTabSiPlusApp would be invoked first, and than is CTabSiPlusApp::InitInstance() function, that's is the mechanism of MFC, we can put some initializationcode in this two functions. The last is export function Initialize(), it invoked by remote thread, since that this remote thread will be end after this function call, so it is the last chance to create a local thread under insignt3.exe process context and start our UI. Here is the detail of Initialize():

TABSIPLUSDLL_API BOOL WINAPI Initialize(){  AFX_MANAGE_STATE(AfxGetStaticModuleState());DebugTracing(gnDbgLevelNormalDebug, _T("TabSiPlus.dll> Initialize() enter") );BOOL res = theApp.Initialize();DebugTracing(gnDbgLevelNormalDebug, _T("TabSiPlus.dll> Initialize() returned %d"), res );return res;}

Initialize() is a stub function, it call a same name function in class CTabSiPlusApp.

BOOL CTabSiPlusApp::Initialize(){DebugTracing(gnDbgLevelNormalDebug,_T("CTabSiPlusApp::Initialize() Start"));if(IsAnotherTabSiPlusDll())  return FALSE;InitGlobalVar();::GetCurrentDirectory(MAX_PATH,g_szCurDircetory);GetCurrentSiProjectPath(g_szCurProjectPath, MAX_PATH);//get current Project path from registryDebugTracing(gnDbgLevelNormalDebug,_T("CTabSiPlusApp::Initialize, %s"),g_szCurDircetory);//Create main UI Threadg_pTabWndUIThread = (CTabWndUIThread *)AfxBeginThread(RUNTIME_CLASS(CTabWndUIThread),THREAD_PRIORITY_NORMAL,0,0,NULL);bInitialized = TRUE;DebugTracing(gnDbgLevelNormalDebug,_T("CTabSiPlusApp::Initialize() end"));return TRUE;}

CTabSiPlusApp::Initialize() create a local thread, this is important, becauseCTabSiPlusApp::Initialize() is running in the context of remote thread, it would be end after this function call. TabSiPlus program need hook internal windows message ofSource Insight window and create a file switch table bar as child window ofSource Insight main window, so it need create another local thread.Source Insight is a standard MDI(Multi-Document Interface) program,insight3.exe hold a main frame window (class name is si_Frame), this main frame window hold a standard MDI(Multi-Document Interface) client window (class name is MDIClient), and this client window is a container of child frame window (class name is si_Sw), all these window's class name is fixed, there is the window inheriting relation:

wnd_class.gif

The InitInstance() function of local thread would do some windows hook and create a file switch table bar window. Firstly, TabSiPlus program find and hook main frame window, for three purpose: enumerating all child window to find MDI Client window, hook WM_SETTEXT message to modified window's title (add " with TabSiPlus") and hook WM_DESTROY message to get a chance to destroy tabbar window before main frame window destroyed. Secondly, TabSiPlus program hook MDI client window. Four messages of this window should be hooked, there are WM_WINDOWPOSCHANGING, WM_MDICREATE, WM_MDIDESTROY and WM_MDIACTIVATE. hook WM_WINDOWPOSCHANGING is important, TabSiPlus program modify the window coordinate to steal some space to display file switch table bar window. Hook WM_MDICREATE, WM_MDIDESTROY and WM_MDIACTIVATE let TabSiPlus program have a chance to know the status of childcode view window (class name is si_Sw) and change table bar status, add, delete or highlight a file switch table. Lastly, TabSiPlus program hook all childcode view window. For each code view window, TabSiPlus program hook WM_GETTEXT and WM_WINDOWPOSCHANGING message. Here is the detail ofCTabWndUIThread::InitInstance() :

BOOL CTabWndUIThread::InitInstance(){AFX_MANAGE_STATE(AfxGetStaticModuleState());BOOL bSuccess = FALSE;SetThreadNativeLanguage();HWND hWndSIFrame = FindSourceInsightFrameWindow();if(hWndSIFrame == NULL)return FALSE;DWORD dwSIPID = 0;GetWindowThreadProcessId(hWndSIFrame,&dwSIPID);if(dwSIPID != GetCurrentProcessId())return FALSE;    g_pSiFrameWnd = new CSIFrameWnd(); //CWnd::FromHandle(hSiFrameWnd);g_pSiFrameWnd->Attach(hWndSIFrame);HWND hMDIWnd = g_pSiFrameWnd->GetMDIClientWnd(); //get MDI client window  // create the tabs windowm_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;//this is importantg_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));return TRUE;}

TabSiPlus program hook WM_WINDOWPOSCHANGING message to steal some space from Source Insight window, now it's time to display table bar window and fill this space. Table bar window is a normal rebar window contain two bands, one is a toolbar button and the other is a SysTabControl.

Here is all, check out the code for more detail.

About The Code

Code is written in C++, using VC6 as compile tool. To compile thesource code, make sure you have SP6 for Visual Studio 6 and Platform SDK installed. TheCode just tested under Windows XP with SP2 and SP3, it should be work fine with Windows 2000 and 2003, Vista? maybe.