经常碰到很多程序只能运行一个实例或是限制实例数量, 特别是像有些游戏,用到了这方面的东西,如中国游戏中心……

自己也分析过很多这方面的程序,所以写下这篇文章,欢迎大家交流。

主要方法有以下几种:


1、FindWindow
   这里面又可分为两种:

    A、 任务栏按纽标题

        通过查找任务栏按纽标题来确定上一实例是否正在运行,不适合按纽标题动态变化的程序(如Winamp)。通常情况下,该方法还是优先考虑,因为按纽标题是一般是固定的。


    B、窗口标题

       通过查找窗口标题来确定上一实例是否正在运行,不适合窗口标题动态变化的程序。

2、Window Property
    将某个数据(可以是字符串或句柄)通过SetProp加入到指定窗口的property list,程序运行时枚举窗口并检查该数据是否存在来确定上一实例是否正在运行。

3、全局Atom
    将某个特定字符串通过GlobalAddAtom加入全局原子表(Global Atom Table),程序运行时检查该串是否存在来确定上一实例是否正在运行。该方法有个局限,就是程序终止前必须显式调用GlobalDeleteAtom来释放atom,否则该atom不会自动释放,如果程序运行时意外终结了,那么下一个实例就无法正常执行。早期版本的realplayer就存在这个现象,不知道是不是采用了该方法。

4、Mutex/Event/Semaphore
    通过互斥对象/信号量/事件等线程同步对象来确定实例是否存在,在NT下要注意权限问题(SID)。

5、DLL全局共享区域
    VC下的DLL工程可以通过下面代码来建立一个进程间共享数据段:
    #pragma data_seg(".share")
    //shared for all processes that attach to the dll
    DWORD dllgs_dwRunCount = 1; //一定要在这里对变量进行初始化,否则工夫白做!
    #pragma data_seg()
    #pragma comment(linker,"/section:.share,rws")
    
    导出3个函数,分别为:
    DWORD IncRunCount(void); //运行计数器加1,返回计数器结果
    DWORD DecRunCount(void); //运行计数器减1,返回计数器结果
    DWORD GetRunCount(void); //取当前运行计数器
    
    由于DLL全局共享段在映射到各个进程地址空间时仅会被初始化一次,并且是在首次被windows加载时,所以利用该共享段数据就能对程序实例进行可靠计数。

6、内存映射文件(File Mapping)
    通过把程序实例信息(如窗口句柄、计数器等等)放置到跨进程的内存映射文件,同样可以控制程序实例运行的数量,道理与DLL全局共享区域类似。

7、其它
如注册表、文件之类,这里就不写了,不过用这些方法的还是比较少。
   


 总结:
      第一种方法和第二种是最常见的,但是程序要有窗体,而后面的没有这个限制,但是实现比较复杂,不管采用哪种方法,参考对象均必须具有可共享、跨进程、易失性、重启自复位等必要性质。


最后附一段某外挂的代码吧:

00404860    .  3BC5                   cmp eax,ebp
00404862    .  8983 00030000          mov dword ptr ds:[ebx+300],eax
00404868    .  74 11                  je short TJMan.0040487B
0040486A    .  FF15 1C604100          call dword ptr ds:[<&KERNEL32.GetLastError>]    ; [GetLastError
00404870    .  3D B7000000            cmp eax,0B7
00404875    .  0F85 C9000000          jnz TJMan.00404944
0040487B    >  55                     push ebp
0040487C    .  55                     push ebp
0040487D    .  68 70A24100            push TJMan.0041A270                             ;  ASCII "系统错误1,外挂不能运行!"
00404882    .  E8 3FF00000            call <jmp.&MFC42.#1200>
00404887    .  8B13                   mov edx,dword ptr ds:[ebx]
00404889    .  8BCB                   mov ecx,ebx
0040488B    .  FF92 CC000000          call dword ptr ds:[edx+CC]
00404891    .  E9 AE000000            jmp TJMan.00404944

这里中是简单的做了一些介绍,以后会专门写具体实现的程序出来,今天就写到这吧.