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

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

starIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIconemptyStarIcon

1.69/5 (7投票s)

2006 年 4 月 21 日

10分钟阅读

viewsIcon

17166

系列第二部分,通过 PSAPI 解释进程。

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

(Windows 2000 服务教程)

作者:Rajesh Hrishikesh Thampi

2006 年 3 月。

本教程是“关于服务与进程”系列文章的延续。该系列文章使用 VC++ 6 编程控制 Windows 2000 平台上的服务和进程。我将在每一部分的结尾处提及下一部分。本部分专门讨论进程中的模块。

编写本教程时,我使用的是 Windows 2000 Professional 和 VC++ 6。但是,将其移植到其他 Windows 版本或 VC 版本应该不会有太大变化,尽管我尚未测试过。

如果您对本教程的易读性或理解性有任何建议和/或反馈,请发送电子邮件至 r_thampi@hotmail.com。请注明合适的邮件主题,因为我收到很多垃圾邮件。感谢所有反馈。

允许您自由分发本教程,只要不对其进行任何更改。


本教程中作为示例的项目不遵循传统的 Windows 编程模型。我将项目保持为简单的控制台可执行文件,以便专注于学习内容,而不是陷入 Windows 编程模型,后者会涉及更多代码。此外,这是对本教程第一部分的直接延续。第一部分中描述的内容在此不再重复。



  1. 深入研究进程。


1:- 我们需要什么。

2:- 进程查看器。

3:- 涉及的函数及其工作原理简介。

4:- Process.cpp 解析。


C. 第三部分。



D. 附录



B. 深入研究进程。


1:- 我们需要什么.

在开始之前,请下载名为“进程查看器”(Process Viewer) 的应用程序。它是免费提供的,可以从以下任一 URL 下载:

http://www.prcview.com

http://www.teamcti.com/pview/prcview.htm

此应用程序对于您理解进程中的模块至关重要。除了此应用程序以及您在系列第一部分已学到的内容之外,您不需要其他任何东西。

不要将进程查看器与 Windows 的任务管理器进行比较。进程查看器功能强大得多。

2:- 进程查看器。

安装此应用程序后,它会显示为系统托盘中的一副护目镜(眼罩)。只需双击即可打开。进程查看器应用程序启动并运行时,您会在窗口中找到以下列:名称(进程名称)、ID(进程 PID)、优先级(哪些进程应在共享硬件时获得更多优先级)、CPU(进程使用的 CPU)、内存使用量(进程使用的内存)、用户名(应用程序启动所用的用户)和完整路径(应用程序启动的完整路径)。

现在,我们以一个示例进程为例。例如,“Explorer.exe”进程。此进程存在于每台 Windows 计算机上。此外,即使您终止此进程,它也会自行重启。让我们试试。

打开“进程查看器”,在名称列中找到“Explorer.exe”。右键单击该进程,然后单击终止。将打开一个带有三个选项的新窗口 – 终止通知取消。单击终止,您会注意到 _explorer.exe_ 进程从列表中消失。同时,您的 Windows 任务栏也会消失,然后两者都会重新出现。

为了学习模块,在继续之前,我将解释如何使用进程查看器查看附加到进程的模块。像以前一样右键单击 _Explorer.exe_。从弹出的菜单中单击模块,您将看到一个名为模块的新窗口。此模块窗口列出了进程链接到的所有文件。这些文件称为模块。现在,如果您查看模块列表,您会发现 _Explorer.exe_ 进程中运行着一个 _explorer.exe_。很奇怪?想一想。

我们启动以使其成为进程的主要文件也是一个文件,对吧?在这方面,它是一个模块。也就是说,如果我执行一个名为 Thampi.exe 的文件,该文件将显示为名为 Thampi 的进程。但该文件本身也是一个模块。简而言之,进程可以看作是一个空管道,它包含并控制所有执行实际工作的模块。

进程在其执行过程中使用的每个文件(.dll、.exe、.drv)都是该进程的模块。明白了吗?

现在您已经理解了什么是模块,让我们看看如何通过编程从进程中列出它们。



3:- 涉及的函数及其工作原理。

我们将使用第一部分中的大部分代码。这样可以轻松地进行代码审查。但是,您还需要了解一些新函数。它们是

_EnumProcessModules_、_GetModuleFileNameEx_ 和 _GetModuleBaseName_。

EnumProcessModules:-

我们要看的第一个函数是 _EnumProcessesModules_ 函数。此函数在提供有关进程的必要详细信息后,为我们检索指定进程中每个模块的句柄。

BOOL EnumProcessModules (HANDLE hProcess, HMODULE* lphModule, DWORD cb, LPDWORD lpcbNeeded );

参数

hProcess: [in] 通过此变量,向函数提供您希望从中提取数据的进程句柄。

lphModule: [out] 函数会将一个指向接收模块句柄列表的数组的指针返回到此变量中。

cb: [in] 通过此变量,您可以告诉函数 _lphModule_ 数组的大小(以字节为单位)。

lpcbNeeded: [out] 通过此变量,函数将告诉我们存储 _lphModule_ 数组中所有模块句柄所需的字节数。

如果此函数成功,它将返回一个非零值。失败则返回零。您将使用此函数来提取模块句柄。



GetModuleFileNameEx :-

此函数将为您提供模块的完整路径,包括文件名或模块名。但为此,我们必须通过参数向函数提供三条信息。

DWORD GetModuleFileNameEx (HANDLE hProcess, HMODULE hModule, LPTSTR lpFilename, DWORD nSize);

参数

hProcess: [in] 通过此参数,您传递要获取信息的进程句柄。

hModule: [in] 通过此参数,您传递进程中特定模块的句柄。

lpFilename: [out] 函数将一个指向空终止缓冲区的指针返回到此参数中,该缓冲区接收模块的完全限定路径。如果文件名大小大于 _nSize_ 参数的值,则函数成功,但文件名将被截断并以空终止。

nSize: [in] 此参数应包含 _lpFilename_ 的大小(以字符为单位)。

_GetModuleBaseName_,我们将讨论在逐步审查代码时需要定义的一个参数。


3:- Module.cpp


<U> </U>

#include <windows.h>

#include <stdio.h>

#include <tchar.h>

#include <psapi.h>

void main( )

{

DWORD ProcessesIDs[50], cbNeeded, cProcesses, cModules, cbNeededM;

HMODULE hMod[1024];

unsigned int i, n;

TCHAR szProcessName[50] = TEXT("<unknown>");

TCHAR szModName[200];

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

return;

cProcesses = cbNeeded / sizeof(DWORD);

for ( i = 0; i < cProcesses; i++ )

{

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

if (NULL != hProcess )

{

if ( EnumProcessModules( hProcess, hMod, sizeof(hMod), &cbNeededM) )

{

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

cModules=cbNeededM/sizeof(HMODULE);

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

for (n=0;n<cModules;n++)

{

GetModuleFileNameEx(hProcess, hMod[n], szModName, sizeof(szModName)/sizeof(TCHAR));

_tprintf(TEXT("ModuleName= %s,\n"), szModName);

}

_tprintf("\n");

}

}

CloseHandle( hProcess );

//End of for loop.

}

}


4:- Module.cpp 解析。


由于上面看到的绝大部分代码与第一部分几乎相同,因此我们只关注更改的部分。

在“for”循环内部,您使用“OpenProcess”来获取进程句柄,直到循环进入下一个周期。下一个“if”语句检查您是否拥有进程句柄,然后我们遇到“EnumProcessModules”。现在,此函数做什么?或者此函数可用于什么?

此函数不仅用于获取进程中每个模块的所有句柄,还用于了解与进程关联的模块数量。哪个进程?您在第一个参数中提供句柄的那个进程。

第一个参数接受您希望探索的进程的句柄。

在第二个参数中,您向函数提供一个长度为 1024 个元素的数组。该函数将此数组填入进程中模块的句柄。这就是为什么该数组的类型为“HMODULE”。您需要自行决定数组的长度。此数组需要足够长以存储进程中的所有模块。因此,如果一个进程有超过 1024 个模块,则其余模块的信息将丢失。

第三个参数需要包含第二个参数的大小(以字节为单位)数据。与其将字节大小计算到另一个变量中,然后再将其传递到函数中,不如直接在函数中进行计算,在第三个位置提供“sizeof(hMod)”。其中 hMod 是包含 1024 个元素的句柄数组。

最后,通过最后一个参数,函数会告诉您函数在“hMod”中使用了多少字节。这样,您就知道“hMod”中有多少数据以及有多少是空的。

是的,当此函数成功时,它会返回一个非零值。失败则返回零。因此,我们可以将其安全地放在“if”条件中。

此函数通过后,我们就拥有了进程中所有模块的句柄。

下一个函数是 _GetModuleBaseName_。除了第二个参数外,该函数在第一部分中已解释。如果您注意到,这里也有一个 _NULL_ 值。此参数在本部分末尾进行解释。使用此函数,您可以打印您正在探索的进程的名称。打印完成后,您将进入另一个“for”循环。您可以使用此循环遍历 _hMod_ 数组中的每个模块句柄。

在此循环中,只有两个函数,其中一个是打印模块名称。另一个是 _GetModuleFileNameEx_。在进入此函数之前,请注意您已经派生出 _hMod_ 中模块句柄的数量,该数量存储在 _cModules_ 中。

现在 _GetModuleFileNameEx_ 中的第一个参数是“进程”的句柄。

第二个参数应包含特定模块的句柄。为此,请使用 hMod[n],其中 n 的范围仅限于 cModules – 1。

第三个参数是函数返回的内容。函数会将模块名称及其在系统中的路径推送到此变量中。

最后,通过最后一个参数,您需要向函数提供第三个参数在字符中的实际大小。计算是在函数本身中完成的,您获取了“szModName”的大小并除以 TCHAR(即一个字符)的大小。

现在您拥有了所需的信息,即模块名称,您可以继续在下一行打印它。这会一直持续到循环遍历所有模块并退出。控制权传递到外部循环,处理下一个进程,然后进入模块循环。通过这种方式,您将能够获取系统中运行的每一个进程及其所有模块。

回到 _GetModuleBaseName_… 第二个参数为 NULL。为什么?因为此参数包含我们提供给函数的模块句柄。您可以使用此函数获取进程的名称。但是您已经通过第一个参数传递了进程句柄。好的一点是,如果您将其保留为 _NULL_,该函数将返回用于创建调用进程的文件的名称。这样您也能获得进程的名称。

这是不为此参数提供进程中模块句柄的唯一原因。


C. 第三部分

下一部分(第三部分)包含有关获取进程状态、控制进程、终止进程等信息。

© . All rights reserved.