四种方法实现VC枚举系统当前进程


    在Windows 2000以上的MS操作系统,通过Windows的任务管理器可以列出当前系统的所有活动进程(如图1所示),在Windows XP中,更是在控制台下增加了一条Tasklist命令,让系统下的所有进程无所遁行(如图2所示)。这一切是怎么实现的呢?


四种方法实现VC枚举系统当前进程
 
图 1

 四种方法实现VC枚举系统当前进程

图 2


引用侯捷大师在《深入浅出MFC》的一句话,“知其然而不知其所以然,真不是个好办法”。既然如此,我们干脆自己动手,自己通过编程来实现吧,这样很有成就感哦!以下所有代码均在Windows XP SP1+VC6.0 sp6编译环境下通过。Ok,Let’ go!

方法一

第一种方法是大家比较熟悉的通过ToolHelp Service提供的API函数来实现。这里用到了3个关键的函数:CreateToolhelp32Snapshot(),Process32First()和Process32Next()。下面给出了关于这三个函数的原形和参数说明;

HANDLE WINAPI CreateToolhelp32Snapshot(
  DWORD dwFlags, //系统快照要查看的信息类型
  DWORD th32ProcessID      //值0表示当前进程
);
BOOL WINAPI Process32First(
  HANDLE hSnapshot,        //CreateToolhelp32Snapshot()创建的快照句柄
  LPPROCESSENTRY32 lppe  //指向进程入口结构
);
BOOL WINAPI Process32Next(
  HANDLE hSnapshot,        //这里参数同Process32First
  LPPROCESSENTRY32 lppe  //同上
);

首先使用CreateToolhelp32Snapshot()创建系统快照句柄(hprocess是我们声明用来保存创建的快照句柄):

hProcess=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
然后调用Process32First()获得系统快照中的第一个进程信息(Report是BOOL型作为判断系统快照中下一条进程记录):

 report=Process32First(hProcess,pinfo);
接着用一个循环调用来遍历系统中所有运行的进程:

 while(report)
 {
 hModule=CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,pinfo->th32ProcessID);
  Module32First(hModule, minfo); 
  GetShortPathName(minfo->szExePath,shortpath,256);
  printf("%s --- %s\n",pinfo->szExeFile,shortpath);
  report=Process32Next(hProcess, pinfo);   
 }

笔者曾通过对Pstools工具包里的Pslist.exe反编译,发现该工具用的就是这种方法。如果你查询MSDN,可以找到一个比这个功能更加完善的源程序。


方法二

 第二种方法也很常见,通过MSDN就可以找到例子代码,它是通过Psapi.dll提供的API函数EnumProcesses和EnumProcessModules来实现。有一点要说明的是,Visual Studio提供的SDK包里没有提供相应的Psapi.h和与之相对应的导入库,笔者当时就很纳闷,MSDN里的例子居然编译不通,后来才发现,编译时根本找不到“psapi.h”。呵呵,还好,MSDN至少告诉我们他们都包含Psapi.dll里,用VC自带的Depend工具一查,果然。这样就好办了,我们可以自己找到这些函数入口地址。

小知识:C也好C++也好,一般的函数名本质上都是一个地址,在Win32的API里,它是指向函数所在Dll模块里函数实现的入口地址。

下面是第二种方法的实现过程:首先,先把Psapi.dll里要用到函数都定义好,方便后面显示调用。

//在psaipi.dll中的函数EnumProcesses用来枚举进程 
typedef BOOL (_stdcall *ENUMPROCESSES)(  //注意这里要指明调用约定为-stdcall
 DWORD* pProcessIds,  //指向进程ID数组链  
 DWORD cb,    //ID数组的大小,用字节计数
 DWORD* pBytesReturned);   //返回的字节
//在psapi.dll中的函数EnumProcessModules用来枚举进程模块
typedef BOOL (_stdcall *ENUMPROCESSMODULES)(
 HANDLE hProcess,   //进程句柄
 HMODULE* lphModule, //指向模块句柄数组链
 DWORD cb,    //模块句柄数组大小,字节计数
 LPDWORD lpcbNeeded);   //存储所有模块句柄所需的字节数
//在psapi.dll中的函数GetModuleFileNameEx获得进程模块名
typedef DWORD (_stdcall *GETMODULEFILENAMEEX)(
 HANDLE hProcess,   //进程句柄
 HMODULE hModule,   //进程句柄
 LPTSTR lpFilename,   //存放模块全路径名
 DWORD nSize    //lpFilename缓冲区大小,字符计算
);

好,通过每个定义的函数前的注释你可以清楚看到这些原函数名,然后当然要加载包含这些函数模块Psapi.dll啦:
 hPsDll = LoadLibrary("PSAPI.DLL");
接着,通过Dll入口,我们查找每个函数地址:

pEnumProcesses  = 
(ENUMPROCESSES)GetProcAddress(hPsDll, "EnumProcesses");
pEnumProcessModules  =
(ENUMPROCESSMODULES)GetProcAddress(hPsDll, "EnumProcessModules");
pGetModuleFileNameEx  =
   (GETMODULEFILENAMEEX)GetProcAddress(hPsDll, "GetModuleFileNameExA");
注意第三个函数名GetModuleFileNameExA,在Dll里有以A和W结尾区分函数,A指采用的是ANSI字符串方式,W则是UNICODE方式。于是,我们可以用下面的语句枚举进程:
pEnumProcesses(processid, sizeof(processid), &needed);
 processcount=needed/sizeof(DWORD);
 
 for (i=0;i<processcount;i++)
 {
  //打开进程
  hProcess=OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ,
false, processid[i]);
  if (hProcess)
  {
   pEnumProcessModules(hProcess, &hModule, sizeof(hModule), &needed);
   pGetModuleFileNameEx(hProcess, hModule, path, sizeof(path));
   GetShortPathName(path,path,256);
   itoa(processid[i],temp,10);
   printf("%s --- %s\n",path,temp);
  }
  else
   printf("Failed!!!\n");
 }
 当然,在Google上一搜索,可以找到不少提供的Psapi.h头文件和对应的Psapi.lib库,这样,你就可以更方便用这种方法了。

方法三

 也许你会说前两种方法全世界都知道了,没什么大不了的!呵呵,那么笔者现在介绍的第三种方法,你就未必知道了。本方法利用了Windows NT/2000下终端服务API函数WTSOpenServer()和WTSEnumerateProcess()来实现,这两个函数都定义在Wtsapi32.dll里。具体的关于终端服务方面的知识,大家可以查询MSDN。
首先,我们来显示申明这两个函数原形:
typedef HANDLE (_stdcall *WTSOPENSERVER)(
LPTSTR pServerName //NetBios指定的终端服务名,如果我们查看本地终端所有进程信息我们可以通过在控制台命令行下用nbtstat –an来获取本机NetBios名。如图3所示。
);  

 四种方法实现VC枚举系统当前进程
 
图3


typedef BOOL (_stdcall *WTSENUMERATEPROCESSES)(
  HANDLE hServer, //WTSOpenServer返回的句柄 
  DWORD Reserved,  //保留值, 0
  DWORD Version,   //指定枚举要求的版本, 必须为1
  PWTS_PROCESS_INFO* ppProcessInfo, //这个参数是关键,存放我们要的进程名和进程id
  DWORD* pCount   //用来存放ppProcessInfo里WTS_PROCESS_INFO结构的数量指针
);

和前面一样,要先装载Wtsapi32.dll模块,获取关键函数地址:

hWtsApi32 = LoadLibrary("wtsapi32.dll");
pWtsOpenServer 
  = (WTSOPENSERVER)GetProcAddress(hWtsApi32, "WTSOpenServerA");
pWtsEnumerateProcesses 
= (WTSENUMERATEPROCESSES)GetProcAddress
(hWtsApi32,"WTSEnumerateProcessesA");

通过Argv[1]给终端服务名(这里我们赋本机NetBios名)赋一个值并打开这项服务:

char* szServerName = argv[1];
hWtsServer = pWtsOpenServer(szServerName);

然后开始遍历终端服务器上的所有进程,这里我们是指本机的所有进程
if(!pWtsEnumerateProcesses(hWtsServer,
       0,
       1,
       &pWtspi,
       &dwCount))
 {
  printf("enum processes error: %d\n", GetLastError());
  return;
 };
 
 for(int i=0; i<dwCount; i++)
 {
  printf("ps_Id: %d\t\tps_name: %s\n", pWtspi[i].ProcessId, pWtspi[i].pProcessName);
 }

怎么样,酷吧,跟前面两种方法效果一样!


方法四

 最后笔者介绍一种最少人用的方法,本方法是从“幻影旅团”论坛上看到的。后面给出的源代码也是从他们那拷贝过来的。呵呵,这种方法也提供给我们一种很好的思路。

 这第四种方法利用了Native Api的NtQuerySystemInformation函数来实现。同样没有该函数的导入库,也要自己定义原形。整个实现不难,可是有点烦,因为在该函数参数结构上,笔者通查MSDN查了很久才找到这些相关的结构,下面我们来看看这个方法的实现吧。
先是自定义函数原形:

typedef NTSTATUS (__stdcall *PZWQUERYSYSTEMINFORMATION) 
                 (IN SYSTEM_INFORMATION_CLASS SystemInformationClass,  
                  IN OUT PVOID SystemInformation,  
                  IN ULONG SystemInformationLength,  
                  OUT PULONG ReturnLength);
然后,还是和前面一样,要找到ZwQuerySystemInformation在Ntdll.dll模块里的入口地址:

hModule = GetModuleHandle((LPCTSTR)"ntdll.dll");
pZwQuerySystemInformation =(PZWQUERYSYSTEMINFORMATION)
GetProcAddress(hModule, (LPCTSTR)"ZwQuerySystemInformation");
获得指向进程信息数组链的第一条进程信息:
pSystemProcessInformation = (SYSTEM_PROCESS_INFORMATION *)pvProcessList;

和方法一方法二一样,在获得第一条进程信息后,开始循环遍历,列出其余的进程:
while (TRUE) 
    { 
        if (pSystemProcessInformation->NextEntryDelta == 0) //如果是最后一条,则终止循环。
            break;                                   

        pSystemProcessInformation=(SYSTEM_PROCESS_INFORMATION*)((PCHAR)pSystemProcessInformation+ pSystemProcessInformation->NextEntryDelta); 
        pProcessName = (char *)malloc(pSystemProcessInformation->ProcessName.Length + 2); 
}

特别注意的是,这里SYSTEM_PROCESS_INFORMATION结构里进程名的类型为UNICODE_STRING, 而UNICODE_STRING里的成员Buffer定义的是Unicode类型。
typedef struct _SYSTEM_PROCESS_INFORMATION  
{  
    DWORD NextEntryDelta;  
    DWORD dThreadCount;  
    DWORD dReserved01;  
    DWORD dReserved02;  
    DWORD dReserved03;  
    DWORD dReserved04;  
    DWORD dReserved05;  
    DWORD dReserved06;  
    FILETIME ftCreateTime; /* relative to 01-01-1601 */  
    FILETIME ftUserTime; /* 100 nsec units */  
    FILETIME ftKernelTime; /* 100 nsec units */  
    UNICODE_STRING ProcessName;      //这就是进程名
    DWORD BasePriority;  
    DWORD dUniqueProcessId;            //进程ID
    DWORD dParentProcessID;  
    DWORD dHandleCount;  
    DWORD dReserved07;  
    DWORD dReserved08;  
    DWORD VmCounters;  
    DWORD dCommitCharge;  
    PVOID ThreadInfos[1]; 
} SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;

typedef struct _UNICODE_STRING { 
  USHORT Length; 
  USHORT MaximumLength; 
  PWSTR  Buffer;                 //注意,这里为Unicode类型
} UNICODE_STRING, *PUNICODE_STRING;

所以,最后我们要调用WideCharToMultiByte()来还原成ANSI字符串以便输出:
WideCharToMultiByte(CP_ACP,  
                      0, 
                    pSystemProcessInformation->ProcessName.Buffer, 
                    pSystemProcessInformation->ProcessName.Length + 1, 
                    pProcessName, 
                    pSystemProcessInformation->ProcessName.Length + 1, 
                    NULL, 
                    NULL);
具体每个参数大家可以参照MSDN,很简单的。以上四种方法的完整源代码见光盘。

 从这里看出,查看系统当前活动进程的实现也不过如此,况且,笔者还没有给出第五种方法(可以通过WMI的COM接口函数CoCreateInstance(),ConnectServer(),ExecQuery()等函数来实现,留给读者自己练习),呵呵,好玩吧?居然有那么多种方法。

    写这篇文章的目的是想告诉大家,有时候通过自己动手,会发现以前认为很NB的东西原来也是很简单的,只要擅于总结,常动手,我们自己也可以自己打造一些方便好用的小工具来,你说呢?!
(文中涉及代码已收录到杂志配套光盘“杂志相关”栏目,按文章名查找即可)


CancelWaitableTimer 这个函数用于取消一个可以等待下去的计时器操作
CallNamedPipe 这个函数由一个希望通过管道通信的一个客户进程调用
ConnectNamedPipe 指示一台服务器等待下去,直至客户机同一个命名管道连接
CreateEvent 创建一个事件对象
CreateMailslot 创建一个邮路。返回的句柄由邮路服务器使用(收件人)
CreateMutex 创建一个互斥体(MUTEX)
CreateNamedPipe 创建一个命名管道。返回的句柄由管道的服务器端使用
CreatePipe 创建一个匿名管道
CreateProcess 创建一个新进程(比如执行一个程序)
CreateSemaphore 创建一个新的信号机
CreateWaitableTimer 创建一个可等待的计时器对象
DisconnectNamedPipe 断开一个客户与一个命名管道的连接
DuplicateHandle 在指出一个现有系统对象当前句柄的情况下,为那个对象创建一个新句柄
ExitProcess 中止一个进程
FindCloseChangeNotification 关闭一个改动通知对象
FindExecutable 查找与一个指定文件关联在一起的程序的文件名
FindFirstChangeNotification 创建一个文件通知对象。该对象用于监视文件系统发生的变化
FindNextChangeNotification 重设一个文件改变通知对象,令其继续监视下一次变化
FreeLibrary 释放指定的动态链接库
GetCurrentProcess 获取当前进程的一个伪句柄
GetCurrentProcessId 获取当前进程一个唯一的标识符
GetCurrentThread 获取当前线程的一个伪句柄
GetCurrentThreadId 获取当前线程一个唯一的线程标识符
GetExitCodeProces 获取一个已中断进程的退出代码
GetExitCodeThread 获取一个已中止线程的退出代码
GetHandleInformation 获取与一个系统对象句柄有关的信息
GetMailslotInfo 获取与一个邮路有关的信息
GetModuleFileName 获取一个已装载模板的完整路径名称
GetModuleHandle 获取一个应用程序或动态链接库的模块句柄
GetPriorityClass 获取特定进程的优先级别
GetProcessShutdownParameters 调查系统关闭时一个指定的进程相对于其它进程的关闭早迟情况
GetProcessTimes 获取与一个进程的经过时间有关的信息
GetProcessWorkingSetSize 了解一个应用程序在运行过程中实际向它交付了多大容量的内存
GetSartupInfo 获取一个进程的启动信息
GetThreadPriority 获取特定线程的优先级别
GetTheardTimes 获取与一个线程的经过时间有关的信息
GetWindowThreadProcessId 获取与指定窗口关联在一起的一个进程和线程标识符
LoadLibrary 载入指定的动态链接库,并将它映射到当前进程使用的地址空间
LoadLibraryEx 装载指定的动态链接库,并为当前进程把它映射到地址空间
LoadModule 载入一个Windows应用程序,并在指定的环境中运行
MsgWaitForMultipleObjects 等侯单个对象或一系列对象发出信号。如返回条件已经满足,则立即返回
SetPriorityClass 设置一个进程的优先级别
SetProcessShutdownParameters 在系统关闭期间,为指定进程设置他相对于其它程序的关闭顺序
SetProcessWorkingSetSize 设置操作系统实际划分给进程使用的内存容量
SetThreadPriority 设定线程的优先级别
ShellExecute 查找与指定文件关联在一起的程序的文件名
TerminateProcess 结束一个进程
WinExec 运行指定的程序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值