Source Insight做个外挂系列之五--Insight “TabSiPlus”



“TabSiPlus 外挂插件”主要有两部分组成,分别是“外挂插件加载器”和“插件动态库”。“插件动态库”完成Source Insight窗口的Hook,显示Tab标签栏,截获Source Insight的窗口消息并根据消息调整Tab标签栏等功能,这是一个动态链接库,不能主动执行,所以还需要一个“外挂插件加载器”,“外挂插件加载器”是一个独立的可执行文件,它的主要功能就是发现Source Insight的进程,并把插件动态库“注入”到Source Insight的进程中。本文将简单介绍这两个组件是如何协同工作,最终实现插件的完整功能。

首先介绍“外挂插件加载器”,它就是TabSiHost.exe组件,它的功能就是监视Source Insight进程,发现Source Insight的主窗口,将插件主体动态库插入到Source Insight。关于如何发现Source Insight和代码注入的方法请查看本系列的第一篇《发现Source Insight》和第二篇《将本地代码注入到Source Insight》,此处就不再赘述。此处主要介绍TabSiHost.exe组件的工作流程以及一些实现细节问题。TabSiHost.exe组件的主函数是MainFunction(),这个函数首先获取参数,根据参数判断是加载还是卸载插件,然后是启动一个定时器,这个定时器负责监视Source Insight进程,最后进入一个消息循环,等待进程结束。定时器的主要任务就是检查是否有Source Insight进程创建,其实就是枚举当前全部程序的主窗口,查看是否有Source Insight的主窗口,如果有则要根据设置确定是否对其实施代码注入。这里需要注意的是不能重复注入,因为每次注入都要创建一个Tab标签栏,重复注入会导致很难看的事情发生。如何确定Source Insight已经被Hook过了呢?其实可以有很多方法,比如创建内核对象进行标识,这只内存标志等等,TabSiPlus使用了一种很巧妙的方式,就是在Source Insight主窗口的窗口标题字符串中插入一个标识字符串“with TabSiPlus”,这样定时器在枚举程序的主窗口的时候就可以根据窗口标题判断这个进程实例是否已经被Hook过了,同时,这个字符串标识还宣示了“TabSiPlus 插件”的存在,一举两得。简单了解了“外挂插件加载器”的工作过程之后,下面将重点介绍“插件动态库”的工作过程。

本系列的第三篇已经介绍过TabSiPlus.dll组件中的代码执行顺序,当LoadLibrary()调用发生时,CTabSiPlusApp类的构造函数和InitInstance()首先被调用,紧接着导出函数Initialize()被调用,Initialize()函数创建了CTabWndUIThread线程,CTabWndUIThread线程在初始化函数InitInstance()中创建并显示一个Tab标签栏,于是插件开始工作了。说起来简单,其实还是有很多细节,最重要的一点就是Hook(子类化) Source Insight的内部窗口。毫无疑问,要使外挂插件能够在没有对外接口的情况下无缝地介入到Source Insight当中,必需要Hook它的窗口消息,这样才能觉察它的变化,所以,在看是内部探索之前,先看看TabSiPlus.dll组件的内部Hook类关系图:


图 5.1 TabSiPlus.dll组件内部Hook类关系图

这个关系图基本上和Source Insight的实际窗口关系是一致的,只是多了一个CMdiChildManagment类,这个类的主要目的是维护CSiMDIWnd类的所有子窗口。

CSIFrameWnd类主要是Hook Source Insight的主窗口消息,CSIFrameWnd类主要关心的消息有两个,一个是WM_SETTEXT消息,另一个是WM_DESTROY消息。处理WM_SETTEXT的目的是为了在窗口标题发生变化的时候,添加本插件的标识字符串:with TabSiPlus。当Windows需要改变标题文字时,WM_SETTEXT消息就会触发,其中第二个参数就是存放窗口标题的缓冲区,这是一个地址,将其替换成我们定制的字符串地址就能起到定制窗口标题的作用:

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什么时候被关闭了,主窗口是最后被销毁的窗口,拦截主窗口的WM_DESTROY消息使Tab标签栏能够有机会在主窗口销毁前销毁自己,然后等待CTabWndUIThread线程结束,既能够避免资源泄漏,也能防止Source Insight出现异常:

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主窗口后,就会调用CSIFrameWnd::GetMDIClientWnd()函数获取MDIClient窗口,MDIClient窗口是所有MDI Child窗口的直接父窗口,它处理MDI Child窗口的消息,比如创建、最大化、最小化以及销毁等等,所以非常重要。和工具栏,状态栏一样,MDIClient窗口是主窗口的一个子窗口,前文也讲过,它有固定的窗口类名:MDIClient,所以获取MDIClient窗口的方法就是遍历主窗口的所有子窗口,找到窗口类名是MDIClient的子窗口,幸运的是,Source Insight的主窗口有且只有一个MDIClient窗口,看代码:

/*这个函数总是返回一个有效的窗口句柄,即使不是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”》中已经分析过,当Source Insight打开一个新文件时,主窗口会创建一个窗口类名是si_Sw的窗口,由于Source Insight使用的是Windows标准MDI界面,所以截获MDIClient窗口的WM_MDICREATE消息就可以获取新打开的窗口,从窗口的标题中分析出文件名,然后在Tab标签栏创建相应的标签。同样当WM_MDIDESTROY发生时,表示关闭了一个文档,此时要从Tab标签栏中删除相应的标签,当WM_MDIACTIVATE发生时,要对Tab标签栏中对应的标签突出显示。要在Source Insight中显示一个标签窗口其实很简单,以主窗口为父窗口创建一个子窗口就可以了,但这样硬插进去的一个窗口会破坏整个Source Insight的界面,不是覆盖其它子窗口就是被其它子窗口覆盖,所以要处理好窗口之间的位置关系很重要。要正确处理窗口位置关系,还要Hook MDIClient窗口的WM_WINDOWPOSCHANGING消息,当MDIClient窗口的大小或位置或窗口Z-Order将要发生变化时,Windows就会发送WM_WINDOWPOSCHANGING消息给MDIClient窗口,获取窗口的位置,大小等等信息,这个消息的LParam参数是一个WINDOWPOS结构(指针),其中cx和cy两个参数就是窗口的宽度和高度,修改这两个值就可以欺骗Windows按照我们的要求调整MDIClient窗口的位置,通常我们要减少cy的值,给我们的Tab标签栏留出空间。

接下来是CSiWindow类,这个subclass主要是HookMDIClient窗口的子窗口,子窗口的窗口句柄和CSiWindow对象之间有一个映射关系,这个关系由CMdiChildManagment类维护。CSiWindow主要是Hook了WM_GETTEXT消息,目的是获取子窗口标题的变化,然后在标签栏中相应的有所表现,比如,对于具有只读属性的文件,Source Insight会在其文件名前面显示一个“!”,当只读属性被取消后,“!”也会消失,CSiWindow要捕获这个变化,然后通知标签栏相应地修改窗口对应的标签栏。再比如,当一个文件被修改过后,文件名后面会有一个“*”,当执行文件保存后,这个“*”号要消失,这些也要在标签栏同步变化。

最后是标签栏窗口CTabBarsWnd,这个类代码最多,其实功能很简单,就是维护一个TabCtrl控件,处理界面操作,根据主窗口的位置和大小调整自己的大小和位置。本来CTabBarsWnd就是一个普通的窗口类,但是在本插件中我让它继承自CReBar类,为的是偷一些懒,那个功能按钮其实就是只有一个图标的工具条,将它和TabCtrl控件作为两个Band添加到Rebar上,省了很多麻烦。

TabSiPlus的内部结构就是这样,很简单啊,不是吗?


Source Insight文件标签外挂:TabSiPlus的下载地址:
http://www.winmsg.com/download/tabsiplus.zip

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值