65.9K
CodeProject 正在变化。 阅读更多。
Home

关于服务和进程 - 第一部分

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.63/5 (7投票s)

2006 年 3 月 23 日

CPOL

13分钟阅读

viewsIcon

47871

downloadIcon

632

如何以编程方式控制 Windows NT 中的进程和服务

引言

本教程适用于使用 VC++ 6 以编程方式控制 Windows 2000 平台上的进程。本教程可能包含至少三部分。下一部分将在每篇教程的末尾提及。撰写本教程时,我使用的是 Windows 2000 Professional 和 VC++ 6,但移植到其他 Windows 或 VC 版本应该不需要太多更改,尽管我尚未对此进行测试。

本教程虽然详尽,但并非快速修补的去处。它旨在使您能够学习如何在与服务相关的进程中进行处理。本教程的出现源于互联网上对此类材料的缺乏,尤其是在进程方面。反馈和建议应通过电子邮件发送给我:r_thampi@hotmail.com。请使用相关的邮件主题行,以便将您的邮件与垃圾邮件区分开来。

您可以分发本教程,条件是您不得更改、编辑或删除教程中的任何内容,包括但不限于作者署名,并且不得将本教程出售以谋取个人利益(无论是金钱还是其他)。

本教程中的示例不遵循传统的 Windows 编程模型。我将项目保持为一个简单的控制台.exe。这样做是为了集中精力处理预期任务,而不是为了说明 Windows 编程模型,后者会包含更多的代码。

Outline

  1. 获取所有服务及其 PID。
    1. 你需要什么
    2. 涉及的函数,包括其工作原理的简要描述
    3. Process.cpp
    4. Process.cpp 剖析
  2. 第二部分。
  3. 附录

A. 获取所有服务及其 PID

1:- 您需要什么

为了更轻松地处理进程,有一个辅助库。在本教程中,我使用 Microsoft 提供的 PSAPI(进程状态应用程序编程接口)库来解释进程。相关文件是Psapi.hPsapi.libPsapi.dll

Psapi.hPsapi.lib:这些文件包含示例中将讨论的所有函数。这些文件必须包含在代码中并在项目中引用。您可以从 http://www.microsoft.com/downloads/details.aspx?familyid=A55B6B43-E24F-4EA3-A93E-40C0EC4F68E5&displaylang=en 下载 Windows 200 的安装程序。(如果文件位置已更改,您可能需要搜索该安装程序。)

运行安装程序文件时,它会提供安装项目所需之外的更多内容。要仅安装所需内容,请选择自定义并安装Windows Core SDK

Paspi.dll:编译后,Psapi.hPsapi.lib 文件将尝试引用Psapi.dll。因此,这是使您的exe正常运行的必要文件,因此请务必在分发您的项目之前将其包含在内。不幸的是,此 DLL 通常不随 Windows 一起提供,但随Windows Core SDK安装程序一起提供,但万一您只需要此 DLL,您可以使用以下链接下载它
http://www.microsoft.com/downloads/release.asp?releaseid=30337

读者应理解基础 C 并且熟悉 VC。如果您擅长 C 编程,但在 VC 中设置项目有问题,请参阅附录 B。

2:- 涉及的函数,包括其工作原理的简要描述

在开始项目之前,了解以下函数是有益的。这些函数是EnumProcessesOpenProcessGetModuleBaseName。如果您不能立即理解它们也没关系,因为它们在代码中也有详细的解释。

EnumProcesses:- EnumProcesses 函数检索系统中每个进程的进程标识符(PID)。
BOOL EnumProcesses(DWORD* pProcessIds, DWORD cb, DWORD* pBytesReturned);
参数

  • pProcessIds:[out] 这是我们定义的数组的指针。该函数将在此数组中填充进程标识符列表。
  • cb:[in] 这是一个 DWORD,包含 pProcessIDs 数组的大小(以字节为单位)。
  • pBytesReturned:[out] 这是 Windows 将用于存储返回到 pProcessIds 数组中的字节数的 DWORD 指针。

正如您所见,一旦您调用此函数,您就获得了列出您计算机系统上运行的进程所需的所有数据!EnumProcesses 的返回值为零(表示没有错误)。

OpenProcess:- 您需要理解的下一个函数是OpenProcess,顾名思义,该函数为您提供进程句柄。它需要三个参数。
HANDLE OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId);
参数

  • dwDesiredAccess:[in] 此 DWORD 应包含您希望对该进程拥有的所需(一个或多个)访问权限,并会与进程的安全描述符进行交叉检查。如果调用者启用了SeDebugPrivilege特权,则无论安全描述符的内容如何,都会授予请求的访问权限。有关访问权限列表,请参阅附录 A。
  • bInheritHandle:[in] 如果此变量中的值为 TRUE,则创建的进程将继承句柄,否则进程将不继承此句柄。当我们将进行到本教程的第三部分时,您将理解这意味着什么。
  • dwProcessId:[in] 此变量应包含要打开的进程的标识符,换句话说,就是进程的PID

如果函数成功,则返回值是指定进程的打开句柄;如果失败,则返回值为 NULL

GetModuleBaseName:- GetModuleBaseName 函数检索指定模块的基础名称。本教程的第二部分将深入探讨“模块”。现在,您只需要知道许多模块构成了进程。
DWORD GetModuleBaseName(HANDLE hProcess, HMODULE hModule, LPTSTR lpBaseName, DWORD nSize);
参数

  • hProcess:[in] 包含模块的进程的句柄在此变量中可用。要使用此函数,句柄必须具有PROCESS_QUERY_INFORMATIONPROCESS_VM_READ访问权限。(有关它们代表的含义,请参阅附录 A。)
  • hModule:[in] 此参数应包含模块的句柄。如果此参数为 NULL,则此函数返回用于创建调用进程的文件名。
  • lpBaseName:[out] 此变量为您提供指向接收模块基础名称的缓冲区的指针。如果基础名称比 nSize 参数指定的字符数长,则基础名称将被截断。
  • nSize:[in] 此参数应包含 lpBaseName 缓冲区的大小(以字符为单位)。

如果函数成功,则返回值指定复制到缓冲区中的字符串的长度(以字符为单位);如果函数失败,则返回值为零。

3:- Process.cpp

include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <psapi.h>

void main()
{
  DWORD ProcessesIDs[50], cbNeeded, cProcesses;
  unsigned int i;

  // The default of <unknown> is given so that if GetModuleBaseName does not return
  // the base name of the module then <unknown> will be printed instead of the base name.
  TCHAR szProcessName[50] = TEXT("<unknown>");

  // if Enumprocess returns zero (fails) then quit the program.
  if ( !EnumProcesses( ProcessesIDs, sizeof(ProcessesIDs), &cbNeeded ) )
    return;

  // Calculate how many process identifiers were returned.
  cProcesses = cbNeeded / sizeof(DWORD);

  // This for loop will be enumerating each process.
  for ( i = 0; i < cProcesses; i++ )
  {
    // Get a handle to the process. The process to which the handle will be returned 
    // will depend on the variable i.
    HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
                                   FALSE, ProcessesIDs[i] );

    // Get the process name.
    if (NULL != hProcess )
    {
      GetModuleBaseName( hProcess, NULL, szProcessName,
                         sizeof(szProcessName)/sizeof(TCHAR) );
    }
    // Print the process name and identifier.
    _tprintf( TEXT("Processname = %s, PID = %u \n\n"), szProcessName, ProcessesIDs[i]);
    //Every handle is to be closed after its use is over.
    CloseHandle( hProcess );
    //End of for loop.
  }
}

4:- Process.cpp 剖析

上面的代码可以复制到 VC++ 6 中进行编译,但请记住链接包含文件Psapi.h和库Psapi.lib。有关详细信息,请参阅附录 B。

现在让我们尝试理解代码本身。首先,您声明了几个变量。前三个是ProcessesIDs[50]cbNeededcProcesses,它们的类型都是DWORD

第一个ProcessIDsDWORD类型的数组。它用于存储 PID。如果您系统的进程超过 50 个,则需要增加此数组的大小。

第二个是cbNeeded,用于存储返回到ProcessIDs数组中的字节数。

第三个是cProcesses。它用于获取系统中运行的进程数。

最后一个变量是szProcessName[50]i变量仅用于for循环)。此变量用于存储模块名称。如果您运行的进程名称超过 50 个 TCHAR,则可以相应地增加数组的大小。

现在,在声明之后,让我们继续进行语句。

if ( !EnumProcesses( ProcessesIDs, sizeof(ProcessesIDs), &cbNeeded ) )
  return; 

EnumProcesses 函数在失败时返回零,在成功时返回非零值,因此您可以安全地使用 if 语句进行检查。如果函数失败,您可以停止进一步执行。

您可以看到 EnumProcess 有三个参数。在这三个参数中,唯一为函数提供值的参数是第二个参数,它表示第一个参数变量的大小。在这种情况下,第一个参数变量(ProcessIDs)是一个包含 50 个元素的 DWORD 数组。由于 DWORD 是 4 个字节,因此 sizeof(ProcessesIDs) 的返回值将是 4*50 = 200 字节。

ProcessIDs 参数变量中包含什么并不重要。函数返回时,ProcessesIDs 是一个指向数组的指针,该数组将包含 PID 列表。函数执行后,您可以遍历 ProcessesIDs 数组并从每个元素中读取每个 PID

第三个参数是指向 DWORD 的指针。函数返回函数返回到 ProcessIDs 数组的实际字节数,因此函数运行后,您将在 cbNeeded 中获得 ProcessIDs 中包含所需数据的字节数。

例如,假设有 15 个 PID。当 EnumProcesses 运行时,它将返回 15 个 PIDProcessIDs 数组中,并且 cbNeeded 表明只有 15 个元素被填充。它通过返回 15 * 4 = 60 字节来实现,其中 4 是一个元素(以字节为单位的 DWORD)的大小。

现在,我们有了系统中运行的 PID 数组(存储在 ProcessesIDs 中),我们可以进入下一步。但在继续之前,如果您存储返回的 PID 数量,可以让事情变得更容易。因此,添加一行

cProcesses = cbNeeded / sizeof(DWORD); 

上面的代码获取 DWORD 的大小(4 字节),并除以 cbNeeded 的值(其中包含返回到数组 ProcessIDs 的总字节数)。这将给出存储在 ProcessIDs 中的 PID 总数,并存储在 cProcesses 中。

现在您需要一个循环来遍历数组 ProcessesIDs 中的每个 PID。为此,您循环 cProcesses 次。进入循环后,您需要 ProcessesIDs[i] 元素中 PID 的句柄。这正是 OpenProcess 函数的用武之地。

HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 
                               FALSE, ProcessesIDs[i] );

OpenProcess 函数也需要三个参数。第一个参数指示您希望对该进程拥有的访问类型。有关访问权限及其含义的列表,请参阅附录 A。第二个参数是 BOOL 值。如果此值为 TRUE,则此进程创建的进程将继承句柄。由于您不需要创建其他进程,因此可以将其保留为 FALSE。最后一个参数是要获取句柄的 PID

一旦您输入所有这些输入并调用函数,该函数将返回进程的句柄,该句柄存储在名为hProcess的变量中,并进行检查以确保hProcess不为NULL

现在来看获取进程名称的最后一个函数!

GetModuleBaseName( hProcess, NULL, szProcessName, sizeof(szProcessName)/sizeof(TCHAR) );

此函数有四个参数,其中三个您需要提供数据,它返回一个参数。第一个参数提供进程的句柄,这是 OpenProcess 的返回值。您只需要在此处使用 hProcess

对于第二个参数(与模块有关,在本教程的第二部分中将详细介绍),目前只需输入 NULL

对于第四个参数,请提供 szProcessName 的大小,该大小与它可以容纳的字符数有关。请记住,返回的内容超过数组大小的部分将被截断。为此,请获取数组的总大小(以字节为单位),然后除以 TCHAR 的大小。

第三个参数是要从函数返回的数组。它将包含进程的名称。搞定……

for 循环结束之前,您将使用此行打印进程名称和 PID

_tprintf( TEXT("Processname = %s, PID = %u \n\n"), szProcessName, ProcessesIDs[i]);

使用 OpenProcess 关闭句柄,如下所示

CloseHandle( hProcess ); 

B.第二部分

本教程的第二部分将详细介绍进程,并深入探讨进程中的模块。

C.附录

附录 A

访问权限可以是

进程名称 描述
PROCESS_ALL_ACCESS 进程对象的所有可能访问权限
PROCESS_CREATE_PROCESS 创建进程所需
PROCESS_CREATE_THREAD 创建线程所需
PROCESS_DUP_HANDLE 使用 DuplicateHandle 复制句柄所需
PROCESS_QUERY_INFORMATION 检索进程的某些信息(例如其令牌、退出代码和优先级类)所需
PROCESS_QUERY_LIMITED_INFORMATION 检索进程的某些信息所需
PROCESS_SET_QUOTA 使用 SetProcessWorkingSetSize 设置内存限制所需
PROCESS_SET_INFORMATION 设置进程的某些信息(例如其优先级类)所需
PROCESS_SUSPEND_RESUME 挂起或恢复进程所需
PROCESS_TERMINATE 使用 TerminateProcess 终止进程所需
PROCESS_VM_OPERATION 对进程的地址空间执行操作所需
PROCESS_VM_READ 使用 ReadProcessMemory 读取进程内存所需
PROCESS_VM_WRITE 使用 WriteProcessMemory 向进程内存写入所需
SYNCHRONIZE 使用 wait 函数等待进程终止所需

附录B

设置项目所用的 VC。

首先确定您的系统是否已包含以下文件:Psapi.hPsapi.libPsapi.dll。如果已有,请转到本附录的“设置 VC”部分。

如果没有,请从以下链接下载 NT 的 PSAPI SDK 可再发行组件:http://www.microsoft.com/downloads/details.aspx?familyid=A55B6B43-E24F-4EA3-A93E-40C0EC4F68E5&displaylang=en。如果网站已移动或暂时不可用,您可能需要搜索 SDK。备用网站是 MSDN 或 Microsoft 主页。

安装此软件包,并记下其安装目录。例如,如果它安装在C:\Program Files\,则在Program Files目录中,您会找到另一个名为Microsoft Platform SDK的目录。此文件夹又会有两个名为LibInclude的子文件夹,分别包含Psapi.libpsapi.hpsapi.dll将在C:\WINNT\system32中找到。

设置 VC

打开 VC,然后转到文件 | 新建 | 项目 | Win32 控制台应用程序。输入项目名称,然后单击确定

然后单击文件 | 新建 | 文件 | C++ 源文件。输入文件名,然后单击确定。C++ 源文件将打开,您可以在其中编写代码。

  1. 将本文档中的代码粘贴到您刚刚创建的 C++ 源文件中。
  2. 单击工具 | 选项 | 目录。在“显示目录用于”下选择“包含文件”。
  3. 双击“目录”标签下的空路径。(此空间看起来像一个文本区域)。双击后,您将看到一个省略号。
  4. 单击省略号,然后浏览到C:\Program Files\Microsoft Platform SDK\Include(或 Microsoft Platform SDK&Include文件夹的路径)。
  5. 点击**确定**。
  6. 在“显示目录用于”下选择“库文件”,然后按照前面的步骤指向C:\Program Files\Microsoft Platform SDK\Lib
  7. 单击确定
  8. 在 VC 主窗口的左侧,“文件视图”选项卡下,右键单击“资源文件”,然后单击“将文件添加到文件夹”。
  9. 在打开的浏览器中,将其指向Psapi.lib
  10. 单击“确定”
  11. 按 F5 进行构建
  12. 转到命令提示符,然后导航到项目位置。
  13. 执行exe文件,您将看到系统中所有进程的PID和名称。

© . All rights reserved.