“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

给Source Insight做个外挂系列之五--Insight “TabSiPlus”的更多相关文章

  1. 给Source Insight做个外挂系列之三--构建外挂软件的定制代码框架

    上一篇文章介绍了“TabSiPlus”是如何进行代码注入的,本篇将介绍如何构建一个外挂软件最重要的部分,也就是为其扩展功能的定制代码.本文前面提到过,由于windows进程管理的限制,扩展代码必须以动 ...

  2. 给Source Insight做个外挂系列之一--发现Source Insight

    一提到外挂程序,大家肯定都不陌生,QQ就有很多个版本的去广告外挂,很多游戏也有用于扩展功能或者作弊的工具,其中很多也是以外挂的形式提供的.外挂和插件的区别在于插件通常依赖于程序的支持,如果程序不支持插 ...

  3. 给Source Insight做个外挂系列之六--“TabSiPlus”的其它问题

    关于如何做一个Source Insight外挂插件的全过程都已经写完了,这么一点东西拖了一年的时间才写完,足以说明我是一个很懒的人,如果不是很多朋友的关心和督促,恐怕是难以完成了.许多朋友希望顺着本文 ...

  4. 给Source Insight做个外挂系列之四--分析“Source Insight”

    外挂的目的就是将代码注入到其它进程中,所以必须要有目标进程才能完成注入,而所谓的目标进程通常是某软件的一部分或者是全部,所以要对目标程序有深入地了解.一般外挂都是针对某个应用程序开发的,其装载.运行都 ...

  5. 给Source Insight做个外挂系列之二--将本地代码注入到Source Insight进程

    上一篇文章介绍了如何发现正在运行的“Source Insight”窗口,本篇将介绍“TabSiPlus”是如何进行代码注入的.Windows 9x以后的Windows操作系统都对进程空间进行了严格的保 ...

  6. Red Gate系列之五 .NET Reflector 7.6.1.824 Edition .NET程序反编译神器(附插件安装教程2012-10-13更新) 完全破解+使用教程

    原文:Red Gate系列之五 .NET Reflector 7.6.1.824 Edition .NET程序反编译神器(附插件安装教程2012-10-13更新) 完全破解+使用教程 Red Gate ...

  7. .NET 并行(多核)编程系列之五 Task执行和异常处理

    原文:.NET 并行(多核)编程系列之五 Task执行和异常处理 .NET 并行(多核)编程系列之五 Task执行和异常处理 前言:本篇主要讲述等待task执行完成. 本篇的议题如下: 1. 等待Ta ...

  8. Python猫荐书系列之五:Python高性能编程

    稍微关心编程语言的使用趋势的人都知道,最近几年,国内最火的两种语言非 Python 与 Go 莫属,于是,隔三差五就会有人问:这两种语言谁更厉害/好找工作/高工资…… 对于编程语言的争论,就是猿界的生 ...

  9. SQL Server 学习系列之五

    SQL Server 学习系列之五 SQL Server 学习系列之一(薪酬方案+基础) SQL Server 学习系列之二(日期格式问题) SQL Server 学习系列之三(SQL 关键字) SQ ...

随机推荐

  1. swift 001

    swift 001  = 赋值是没有返回值的 所以 int a=10; int b=20; if(a=b){ printf("这个是错误的"); } swift  中的模运算 是支 ...

  2. jquery之toggleClass应用

    今天记载一下常用的html + css + jquery效果应用 1.html内容 <div class="selBtn screen_btn"> <a id=& ...

  3. iOS 多个异步网络请求全部返回后再执行具体逻辑的方法

    对于dispatch多个异步操作后的同步方法,以前只看过dispatch_group_async,看看这个方法的说明: * @discussion * Submits a block to a dis ...

  4. Magento 新增字段的值读写丢失原因

    某实体新增字段handreturn_status,欲操作之: $order_info = Mage::getModel('sales/order')->load($order_id); //se ...

  5. mac下wifi无法连接的问题

    今天遇到了一个Wi-Fi打死连不上的问题,关闭重启电脑路由器都试了一下还是不行,最后把资源库/偏好设置/SystemConfiguration下的文件都删除,有一个是删不掉的,留着不影响,然后重启,O ...

  6. 【python】确保文件写入结束

    今天遇到了个问题: 我在执行如下代码时发现,文件只写了一半.也就是说,当文件量过大时,下面的代码是不能保证文件被正确写入的. fd = open('test.txt','w') fd.write(&q ...

  7. hibernate中HQL多对多的查询

    现有三张表 TLXPURCHASE.采购事项审批表,TLXPURCHASEACTIVITY.采购招标活动对应表,TLXACTIVITY.招标活动表,采购事项审批表和采购活动表是多对多关系.java中定 ...

  8. Swift中的一些关键字

    以下关键字关于引用传参.属性.修改成员变量.静态变量.索引和构造函数重载 读过The Swift Programming Language的人都能看得出,我上面的这几个说法全不是apple的习惯用语. ...

  9. 系统吞吐量(TPS)、用户并发量

    PS:下面是性能测试的主要概念和计算公式,记录下: 一.系统吞度量要素: 一个系统的吞度量(承压能力)与request对CPU的消耗.外部接口.IO等等紧密关联. 单个reqeust 对CPU消耗越高 ...

  10. 【CentOS】学习Bash

    一.特性 命令历史  history , !! , !$ , !字符 , !n (n为第几条命令) Tab可以补全文件路径或者命令 alias a="b",  unalias a ...