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

API 函数调用统一:进程/模块枚举的案例

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.63/5 (16投票s)

2003 年 7 月 7 日

12分钟阅读

viewsIcon

115426

downloadIcon

1387

一篇展示如何编写代码来统一两个不同 API 集合的文章。

引言

本文及代码将向您展示如何编写一个类,该类封装了两个不同的 API,并使用一个接口呈现两者的合成。针对这个问题,我将特别展示如何编写一个类来解决在 Windows 操作系统中管理进程和模块的问题。

对于进程管理,Windows API 提供了两个不同的库:

  • PSAPI.DLL 函数(在 psapi.dll 中)- 在 Windows NT 和 2k/XP 中可用
  • ToolHelp 函数(在 kernel32.dll 中)- 在 Windows 9x/ME/2k/XP 中可用

正如您所见,如果您想支持 Windows 9x,则必须使用 ToolHelp API 进行编码;如果您想支持 WinNT,则必须使用 PSAPI 进行编码。另请注意,这些不同的库使用不同的数据结构来表示和管理进程。

在本文中,我将向您展示如何:

  • 动态加载 DLL 函数。
  • 合并两个数据结构,并基于不同数据结构的联合定义一个通用结构。
  • 使用标准模板库来支持您的代码。
  • 编写 C++ 类,设计数据结构和函数以满足您的需求。

到本文结束时,您将不仅学会如何使用此代码或管理进程和模块,还将学会如何编写自己的代码来实现通过两个或多个接口提供的任何其他功能。我将解释我使用的数据结构和编写的函数,以提供构建此类事物的经验和想法。我将解释我使用的数据结构和编写的函数,以提供构建此类事物的经验和想法。

背景信息

对于本文,建议您了解:
  • 什么是进程
  • 什么是进程模块
  • C/C++ 语言知识
  • 对枚举概念的初步了解。
  • STL 的 vector
为了方便您,我对这些概念中的每一个都做了一些解释。
术语 解释
进程 最简单的说,进程是一个正在执行的程序。进程由系统创建进程时为其分配的进程 ID 来表示。

我们主要使用 CreateProcess() 来创建进程。

您不能直接使用进程 ID (PID) 进行函数调用,而是需要进程句柄。可以通过 OpenProcess() 获取该句柄。

模块 模块是一个附加程序,它不能独立存在和运行。相反,它被正在运行的进程使用和调用。您可以将模块视为像 kernel32.dll 或 user32.dll 这样的库。

每个进程都有自己的模块依赖项。此依赖项可以在程序中静态定义(在其 import table 中)或通过使用 LoadLibrary() 动态建立。

模块(加载到内存时)主要由 HMODULE 表示。HMODULE 是模块的句柄。该值是模块的基地址。

枚举概念

许多 Windows API 提供枚举对象的服务,例如:

  • 通过 EnumWindows 进行窗口枚举

  • 通过 PSAPI / ToolHelp 函数进行进程枚举

  • 通过 FindFirst 系列进行文件和目录枚举。

通常,枚举过程与回调机制相结合,该机制允许开发者在枚举到新对象时附加自己的函数。

在此上下文中的回调允许开发者编写可扩展的代码,同时也能让他/她发布代码而不发布源代码,从而允许其他开发者以灵活的方式使用他的代码。

如果没有提供回调,则枚举过程将通过其他函数进行动画处理,例如:

  • 先枚举/后枚举

  • 可以遍历枚举列表(任意方向)的 Walk 函数。

更多关于枚举概念的内容将在本文中逐步阐述。

STL 的 vector 这可以简单地描述为可变大小的动态数组。我们可以使用 push_back() 方法向此数组添加元素。使用 size() 确定大小,我们还可以使用 at() 检索特定记录。

有关此主题的更多信息,请参阅 MSDN 库。

更具体地说,是这些部分:

  • Windows Base Services / Performance Monitoring / Process Status Helper(用于 PSAPI 参考)
  • Windows Base Services / Performance Monitoring / Tool Help Library(用于 ToolHelp 参考)

请注意,您将需要 Platform SDK 来编译此代码。或者至少需要 PSAPI.H 文件。如果您无法获得 PSAPI.H,请自行编写一个包含此代码中使用的两个或三个结构定义的 PSAPI.H 文件。(请查阅 SDK 文档以获取这些结构的定义)。

设计

我在 MSDN 库中查找了用于管理进程的各种函数的描述和规范。这些函数如下所示:

任务/提供者 PSAPI ToolHelp
枚举进程 EnumProcesses() - 返回当前正在运行的所有进程 ID 的数组。
此函数不提供任何额外的进程信息。
CreateToolhelp32Snapshot(),参数为 TH32CS_SNAPPROCESS
然后结合 Process32First()Process32Next() 函数。
后两个函数除了进程 ID 外,还返回大量信息。它们将主要向我们提供进程镜像基址、父进程 ID 等。
枚举模块 EnumProcessModules() - 它将返回一个 HMODULE 数组,其中包含附加到进程的每个模块。同样,此函数不提供其他有意义的信息。 CreateToolhelp32Snapshot(),参数为 TH32CS_SNAPMODULE
然后结合 Module32First()Module32Next() 函数。
后两个函数也提供了有意义的信息,使我们满意,无需再查找关于给定模块的任何信息。

正如您从上面的表格中清楚看到的,PSAPI 函数并没有为我们提供关于进程或模块的太多信息,因此我们需要使用额外的 PSAPI 函数来获取与 ToolHelp API 几乎相同的信息。

我现在将介绍相关的数据结构。

我标记的关键字是我们关心的关键字,通过它们,我将构建我自己的数据结构,该结构不依赖于 PSAPI 和 ToolHelp。

上下文/提供者 PSAPI ToolHelp
进程 PSAPI 没有一个函数可以帮助我们构建一个满足我们需求的数据结构。因此,我将额外使用以下 PSAPI 函数:

GetModuleFileNameEx() - 给定进程句柄和模块基址,它将返回其完整路径。
我们知道 EnumProcess() 将返回进程 ID。现在,与 ToolHelp 部分关于进程的信息相比,我们已经得到了完整的信息。
typedef struct tagPROCESSENTRY32
{
DWORD dwSize;
DWORD cntUsage;
DWORD th32ProcessID;
ULONG_PTR th32DefaultHeapID;
DWORD th32ModuleID;
DWORD cntThreads;
DWORD th32ParentProcessID;
LONG pcPriClassBase;
DWORD dwFlags;
TCHAR szExeFile[MAX_PATH];
} PROCESSENTRY32, *PPROCESSENTRY32;
模块 我们只有 PSAPI()提供的 HMODULE 数组,因此我通过 PSAPI 的函数寻求其他信息:
GetModuleFileNameEx - 如上所述。
GetModuleInformation - 该函数将填充一个 MODULEINFO 结构:从中提取以下内容:

typedef struct _MODULEINFO {
LPVOID lpBaseOfDll;
DWORD SizeOfImage;
LPVOID EntryPoint;
} MODULEINFO, *LPMODULEINFO;
typedef struct tagMODULEENTRY32 {
DWORD dwSize;
DWORD th32ModuleID;
DWORD th32ProcessID;
DWORD GlblcntUsage;
DWORD ProccntUsage;
BYTE* modBaseAddr;
DWORD modBaseSize;
HMODULE hModule;
TCHAR szModule[MAX_MODULE_NAME32 + 1];
TCHAR szExePath[MAX_PATH];
} MODULEENTRY32, *PMODULEENTRY32;

从上表可以看出,我们已经成功地从 PSAPI 和 ToolHelp 提供的两个数据结构中标记了关于进程和模块的通用信息。现在,我将我的数据结构呈现如下:

  typedef struct tProcessInfo
  {
    DWORD pid;
    TCHAR FileName[MAX_PATH];
  };


  typedef struct tModuleInfo
  {
    LPVOID ImageBase;
    DWORD  ImageSize;
    TCHAR  FileName[MAX_PATH];
  };  
基本上,上面展示的结构是我们枚举进程或模块所需的一切。至于类的设计以及它应该导出哪些函数,我得出了以下结论:
函数 描述
Init 此函数用于检测并动态加载 PsApi 或 ToolHelp 函数。
ProcessesGetList() / ModulesGetList() 这些函数将获取所有模块或进程的快照。
ProcessesWalk() / ModulesWalk() 这些函数允许我们遍历枚举的进程或模块列表。
ProcessesCount() / ModulesCount() 这些函数将返回已枚举项的数量。
ProcessesFreeList() / ModulesFreeList() 释放由 xxxxGetList 函数先前创建的快照。

构建代码

代码已编写成一个名为 CProcessApi 的 C++ 类。
这是类的接口,简而言之,我们将探讨如何构建这些函数中的每一个。

class CProcessApi
{
public:
  typedef struct tProcessInfo
  {
    DWORD pid;
    TCHAR FileName[MAX_PATH];
  };

  typedef struct tModuleInfo
  {
    LPVOID ImageBase;
    DWORD  ImageSize;
    TCHAR  FileName[MAX_PATH];
  };

private:
  typedef vector<tProcessInfo> tProcessesList;
  typedef vector<tModuleInfo> tModulesList;

  typedef struct tProcessesData
  {
    DWORD Pos;
    tProcessesList *pl;
  };

  typedef struct tModulesData
  {
    DWORD Pos;
    tModulesList *ml;
  };

  // PSAPI.DLL functions prototype
  typedef BOOL (WINAPI *t_psapi_EnumProcesses)(
    DWORD *lpidProcess,  // array of process identifiers
    DWORD cb,            // size of array
    DWORD *cbNeeded      // number of bytes returned
  );

  typedef BOOL (WINAPI *t_psapi_EnumProcessModules)(
    HANDLE hProcess,      // handle to process
    HMODULE *lphModule,   // array of module handles
    DWORD cb,             // size of array
    LPDWORD lpcbNeeded    // number of bytes required
  );

  typedef DWORD (WINAPI *t_psapi_GetModuleFileNameEx)(
    HANDLE hProcess,    // handle to process
    HMODULE hModule,    // handle to module
    LPTSTR lpFilename,  // path buffer
    DWORD nSize         // maximum characters to retrieve
  );

  typedef BOOL (WINAPI *t_psapi_GetModuleInformation)(
    HANDLE hProcess,
    HMODULE hModule,
    LPMODULEINFO lpmodinfo,
    DWORD cb
  );
  
  // functions instances
  t_psapi_GetModuleFileNameEx       psapi_GetModuleFileNameEx;
  t_psapi_EnumProcessModules        psapi_EnumProcessModules;
  t_psapi_EnumProcesses             psapi_EnumProcesses;
  t_psapi_GetModuleInformation      psapi_GetModuleInformation;

  // TOOLHELP functions prototype
  typedef HANDLE (WINAPI *t_tlhlp_CreateToolhelp32Snapshot)(
    DWORD dwFlags,
    DWORD th32ProcessID
  );

  typedef BOOL (WINAPI *t_tlhlp_Process32First)(
    HANDLE hSnapshot,
    LPPROCESSENTRY32 lppe
  );
  
  typedef BOOL (WINAPI *t_tlhlp_Process32Next)(
    HANDLE hSnapshot,
    LPPROCESSENTRY32 lppe
  );

  typedef BOOL (WINAPI *t_tlhlp_Module32First)(
    HANDLE hSnapshot,
    LPMODULEENTRY32 lpme
  );

  typedef BOOL (WINAPI *t_tlhlp_Module32Next)(
    HANDLE hSnapshot,
    LPMODULEENTRY32 lpme
  );

  // functions instances
  t_tlhlp_CreateToolhelp32Snapshot tlhlp_CreateToolhelp32Snapshot;
  t_tlhlp_Process32First           tlhlp_Process32First;
  t_tlhlp_Process32Next            tlhlp_Process32Next; 
  t_tlhlp_Module32First            tlhlp_Module32First;
  t_tlhlp_Module32Next             tlhlp_Module32Next;

  // Private member variables
  HMODULE   m_hPsApi;
  HMODULE   m_hTlHlp;
  bool      m_bPsApi;
  bool      m_bToolHelp;

  bool Load_TlHlp();
  bool Load_PsApi();

  DWORD ProcessesPopulatePsApi(tProcessesData *pd);
  DWORD ProcessesPopulateToolHelp(tProcessesData *pd);

  DWORD ModulesPopulatePsApi(DWORD pid, tModulesData *md);
  DWORD ModulesPopulateToolHelp(DWORD pid, tModulesData *md);

public:
  // CProcessApi error enum
  enum
  {
    paeSuccess = 0,                     // No error
    paeNoApi,                           // No process API helper dll found
    paeNoEntryPoint,                    // One needed entrypoint not found  
                                        // in helper dll
    paeNoMem,                           // Not enough memory
    paeNoSnap,                          // Could not get a snapshot
    paeNoMore,                          // List contains no more items
    paeOutOfBounds,                     // Tried to access list w/ an 
                                        // invalid  index
    paeYYY
  };

  DWORD LastError; // Holds the last error

  CProcessApi();
  ~CProcessApi();

  bool Init(bool bPsApiFirst = true);

  DWORD ProcessesGetList();
  bool  ProcessesWalk(DWORD lid, tProcessInfo *pi, DWORD Pos = -1);
  DWORD ProcessesCount(DWORD lid) const;
  void  ProcessesFreeList(DWORD lid);

  DWORD ModulesGetList(DWORD ProcessID);
  bool  ModulesWalk(DWORD lid, tModuleInfo *mi, DWORD Pos = -1);
  DWORD ModulesCount(DWORD lid) const;
  void  ModulesFreeList(DWORD lid);
};

错误代码

大多数函数都返回一个布尔值来表示成功或失败。可以通过 LastError 成员变量提取有关发生错误的额外信息。错误代码描述为 CProcessApi::paeXXXX

使用哪个库以及如何动态加载它?

我们已经定义了我们的问题:使用哪个库取决于系统中有什么可用。
我没有静态链接任何 PSAPI 或 Toolhelp API 函数,因此我定义了它们的函数原型,然后在运行时根据辅助库的可用性来填充这些函数的指针。

  // TOOLHELP functions prototype
  typedef HANDLE (WINAPI *t_tlhlp_CreateToolhelp32Snapshot)(
    DWORD dwFlags,
    DWORD th32ProcessID
  );
此语法允许我定义或描述 CreateToolhelp32Snapshot 函数的工作原理。而这个
  t_tlhlp_CreateToolhelp32Snapshot tlhlp_CreateToolhelp32Snapshot;
是 t_tlhlp_CreateToolhelp32Snapshot 函数类型的一个实例。我们(传统上)按如下方式初始化此实例:
HMODULE hMod = LoadLibrary("kernel32.dll");
if (!hMod)
  return FALSE; // report error here!

t_tlhlp_CreateToolhelp32Snapshot tlhlp_CreateToolhelp32Snapshot = 
   (t_tlhlp_CreateToolhelp32Snapshot)GetProcAddress(hMod, 
                                                 "CreateToolhelp32Snapshot");
if (tlhlp_CreateToolhelp32Snapshot == NULL)
{ FreeLibrary(hMod); return FALSE; // report error here }
正如您所见,最后一个代码片段需要动态加载每个函数。因此,我通过创建一个宏(支持 UNICODE)来简化此任务,例如:
  #ifdef _UNICODE
    #define Modifier "W"
  #else
    #define Modifier "A"
  #endif
   ;
  PVOID p;
  
  // custom macro to allow me to load functions dynamically
  #define DynamicGetProcAddress(modname, Mod) \
    p = GetProcAddress(m_hTlHlp, #modname Mod); \
    if (!p) { FreeLibrary(m_hTlHlp); \
              return (LastError = paeNoEntryPoint, false);  } \
    tlhlp_##modname = (t_tlhlp_##modname)p;
您可以看到,这个宏只是节省了我编写相同代码一遍又一遍。这个宏还检查失败并返回给调用者,报告适当的错误代码。因此,动态加载函数的过程简化为:
  DynamicGetProcAddress(CreateToolhelp32Snapshot, _T(""));
  DynamicGetProcAddress(Process32First, _T(""));
  DynamicGetProcAddress(Process32Next, _T(""));
  DynamicGetProcAddress(Module32First, _T(""));
  DynamicGetProcAddress(Module32Next, _T(""));
  // in the case of PSAPI:
  DynamicGetProcAddress(GetModuleFileNameEx, Modifier);
  DynamicGetProcAddress(EnumProcessModules, _T(""));
  DynamicGetProcAddress(EnumProcesses, _T(""));
  DynamicGetProcAddress(GetModuleInformation, _T(""));
动态加载函数是传播在两个函数中:Load_PsApi()Load_TlHlp()Init(bool bUsePsApiFirst) 将调用 Load_PsApiLoad_TlHlp,然后它将拥有自己的一组就绪函数。内部布尔标志 m_bPsApi / m_bTlHlp 在代码中用于确定内部使用哪些函数。
bool CProcessApi::Init(bool bPsApiFirst)
{
  bool loaded;
  
  if (bPsApiFirst)
  {
    loaded = Load_PsApi();
    if (!loaded)
      loaded = Load_TlHlp();
  }
  else
  {
    loaded = Load_TlHlp();
    if (!loaded)
      loaded = Load_PsApi();
  }
  return (loaded ? (LastError = paeSuccess, true) : (LastError = paeNoApi,
                                                                  false));
}

用于描述/管理/枚举进程的内部数据结构

  typedef vector<tProcessInfo> tProcessesList;
  typedef vector<tModuleInfo> tModulesList;

  typedef struct tProcessesData
  {
    DWORD Pos;
    tProcessesList *pl;
  };

  typedef struct tModulesData
  {
    DWORD Pos;
    tModulesList *ml;
  };
我使用 STL 的 vector 库来存储所有枚举的信息。因此,您看到 tProcessesList/tModulesList 是前面解释的结构 tProcessInfotModulesInfo 的 vector。

tModulesDatatProcessesData 是用于命令/驱动枚举函数 xxxFreeList/xxxWalkList 的内部数据结构。它们由元素列表(进程或模块)和一个 DWORD Pos 组成,该 Pos 用作内部当前列表索引指示器。

ProcessesGetList 将返回一个名为 'lid'(或 list id)的 DWORD。此列表 ID 实际上是指向 tProcessesData 的指针,它被强制转换为 DWORD。对于 ModulesGetList 也一样。当我返回列表 ID 给用户时,这个 LID(列表 ID)将与 xxxxWalk 等函数一起使用以获取所需信息。在 xxxWalk 内部,我将列表 ID(即 DWORD)强制转换回 tProcessesDatatModulesData,然后访问该结构内的 vector 以便为用户服务。

DWORD CProcessApi::ProcessesGetList()
{
  tProcessesData *pd = new tProcessesData;
  if (!pd)
    return (LastError = paeNoMem, NULL);

  // create the list
  pd->pl = new tProcessesList;
  if (!pd->pl)
  {
    delete pd;
    return (LastError = paeNoMem, NULL);
  }

  // decide what to use
  if (m_bPsApi)
    LastError = ProcessesPopulatePsApi(pd);
  else if (m_bToolHelp)
    LastError = ProcessesPopulateToolHelp(pd);

  return (DWORD) pd;
}
首先,我们分配 tProcessData 的新内存,然后分配一个名为 tProcessList 的新列表,然后调用相应的函数来填充列表。我们返回一个强制转换的 tProcessData 指针,该指针对他来说是一个列表 ID(但对我们来说是指向 tProcessData struct 的有意义的指针)。

遍历列表的代码如下:

bool CProcessApi::ProcessesWalk(DWORD lid, tProcessInfo *pi, DWORD Pos)
{
  tProcessesData *pd = reinterpret_cast<tProcessesData *>(lid);

  // auto position ?
  if (Pos == -1)
    Pos = pd->Pos;

  // out of bounds?
  if (Pos < 0 || Pos > pd->pl->size())
    return (LastError = paeOutOfBounds, false);
  // end reached ?
  else if (Pos == pd->pl->size())
    return (LastError = paeNoMore, false);
    
  // copy information to user buffer
  *pi = pd->pl->at(Pos);

  // advance position to next item
  pd->Pos++;
  return (LastError = paeSuccess, true);
}
正如您所注意到的,它不依赖于任何其他调用,它只是从已填充的列表中返回数据。我首先检查边界并检查用户所需的起始位置。如果没有指定位置,我将使用最后一个位置,这个机制允许我们以后将代码用作:
while (ProcessApi::ProcessesWalk(lid, &procinf))
{
  // do stuff with tProcessInfo 'procinf'
}
xxxxFreeList 只是删除已分配的数据结构。
void CProcessApi::ProcessesFreeList(DWORD lid)
{
  tProcessesData *pd = reinterpret_cast<tProcessesData *>(lid);
  delete pd->pl;
  delete pd;
}

使用 PSAPI 进行枚举

我将通过 PSAPI 来演示进程枚举。有关更多信息,请参阅源代码和 SDK 手册,您还可以在那里找到许多示例。

// Populates the list using PsApi functions
DWORD CProcessApi::ProcessesPopulatePsApi(tProcessesData *pd)
{
  DWORD nProcess, // number of processes returned
        nCount(4096); // maximum number of processes (defined by me)

  // Dynamic array for storing returned processes IDs
  DWORD *processes = new DWORD[nCount];

  // enum all processes
  if (!psapi_EnumProcesses(processes, nCount * sizeof(DWORD), &nProcess))
  {
    delete processes;
    return paeNoSnap;
  }

  // convert fron bytes count to items count
  nProcess /= 4;

  tProcessInfo pi = {0};

  // walk in process list
  for (DWORD i=0; 
       (i < nProcess);
       i++)
  {
    HANDLE hProcess;

    // open process for querying only
    hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 
                           FALSE, processes[i]);
    
    if (!hProcess)
      continue;

    // get the process's image name by getting first module
    DWORD nmod;
    HMODULE mod1;
    
    if (!psapi_EnumProcessModules(hProcess, &mod1, sizeof(mod1), &nmod))
      _tcscpy(pi.FileName, _T("-"));
    else
      psapi_GetModuleFileNameEx(hProcess, mod1, pi.FileName, 
                                sizeof(pi.FileName));

    // get the process ID
    pi.pid = processes[i];

    // store in the list
    pd->pl->push_back(pi);
     
    CloseHandle(hProcess);
  }

  // reposition list to 0
  pd->Pos = 0;
  delete processes;
  return paeSuccess;
}
  1. 使用 EnumProcesses(),它接受一个大的 DWORD 数组。此数组将被填充为相应的进程 ID。
  2. 循环遍历返回的进程 ID。
  3. 获取此进程的第一个模块,通常第一个模块是进程本身。
  4. 检索进程名称。
  5. 将进程 ID 和进程名称都存储到 tProcessInfo 中并将其添加到列表中。
  6. 重置列表位置,例如 pd->Pos = 0。我们可以将位置设置为 3,例如以跳过系统进程。

使用 ToolHelp 进行枚举

我将通过 ToolHelp API 来演示进程枚举。

// Populates a tProcessesList with the help of ToolHelp API

DWORD CProcessApi::ProcessesPopulateToolHelp(tProcessesData *pd)
{
  // create a process snapshot
  HANDLE hSnap = tlhlp_CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if (hSnap == INVALID_HANDLE_VALUE)
    return paeNoSnap;

  BOOL bMore;
  tProcessInfo pi = {0};

  PROCESSENTRY32 pe32 = {sizeof(PROCESSENTRY32), 0};

  // clear the list
  pd->pl->clear();

  // initialize position
  pd->Pos = 0;

  bMore = tlhlp_Process32First(hSnap, &pe32);
  while (bMore)
  {
    // convert from PROCESSENTRY32 to my unified tProcessInfo struct
    pi.pid = pe32.th32ProcessID;
    _tcscpy(pi.FileName, pe32.szExeFile);

    pd->pl->push_back(pi);
    bMore = tlhlp_Process32Next(hSnap, &pe32);
  }

  CloseHandle(hSnap);
  return paeSuccess;
}
  1. 创建进程快照。
  2. 开始遍历列表。
  3. PROCESSENTRY32 获取相关信息,并将其存储到 tProcessInfo 结构中。
  4. 使用 Process32Next() 获取下一个进程。

在这些函数中,您会注意到我没有直接调用 ToolHelp 或 PsApi 函数,例如:EnumProcessesProcess32Next,而是通过动态设置的函数 tlhlp_XXXXpsapi_XXXXX 调用它们。

计数功能

我只是返回项目 vector 的大小,如下所示:

// Returns the count in the processes list
DWORD CProcessApi::ProcessesCount(DWORD lid) const
{
  return (reinterpret_cast<tProcessesData *>(lid))->pl->size();
}
  1. 将列表 ID 强制转换为 tXXXXData。
  2. 返回 vector 的大小。

使用代码

所有繁琐而冗长的工作都完成了,以便实现者可以轻松访问进程和模块。下面是一个简单的控制台应用程序 (proc1.cpp),它列出了进程及其相应的模块。

#include <stdio.h>
#include <stdlib.h>
#include "processapi.h"

int main(void)
{
  CProcessApi papi;

  if (!papi.Init(false))
  {
    printf("Failed to load either of process api libraries!");
    return 1;
  }

  DWORD pl = papi.ProcessesGetList();
  if (pl)
  {
    CProcessApi::tProcessInfo pi;
    while (papi.ProcessesWalk(pl, &pi))
    {
      printf("Process[%d]: %s\n", pi.pid, pi.FileName);
      CProcessApi::tModuleInfo mi = {0};
      DWORD ml = papi.ModulesGetList(pi.pid);
      while (papi.ModulesWalk(ml, &mi))
      {
        printf(" Module(%08X/%08X): %s\n", mi.ImageBase, mi.ImageSize,
                                           mi.FileName);
      }
      papi.ModulesFreeList(ml);
    }
  }
  papi.ProcessesFreeList(pl);
  return 0;
}
  1. 定义一个 CProcessApi 实例(例如 papi)。
  2. 将 papi 实例初始化为 papi.Init(false)。'false' 表示尝试先加载 ToolHelp。
  3. 以如下方式获取进程列表:DWORD pl = papi.ProcessesGetList()。
  4. 您应该在继续之前检查 papi.LastError。
  5. 定义一个 tProcessInfo 结构,例如:CProcessApi::tProcessInfo pi。
  6. 开始遍历进程列表。
  7. 为每个进程显示一些信息,然后构建一个模块列表。
    1. 以如下方式获取模块列表:DWORD ml = papi.ModulesGetList(processID);
    2. 定义一个模块信息结构,例如:CProcessApi::tModuleInfo mi。
    3. 遍历模块列表并显示信息。
    4. 完成后释放模块列表。
  8. 完成后释放进程列表。
这是另一个示例代码 (proc2.cpp),用于向您展示如何以向后方式遍历列表(使用 PsApi)。
#include <stdio.h>
#include <stdlib.h>
#include "processapi.h"

int main(void)
{
  CProcessApi papi;

  if (!papi.Init(true))
  {
    printf("Failed to load either of process api libraries!");
    return 1;
  }

  DWORD pl = papi.ProcessesGetList();
  int i, j;
  if (pl)
  {
    CProcessApi::tProcessInfo pi;
    for (i=papi.ProcessesCount(pl)-1;i>=0;i--)
    {
      if (!papi.ProcessesWalk(pl, &pi, i))
      {
        printf("failed to process walk @ %d\n", i);
        continue;
      }
      printf("Process[%d]: %s\n", pi.pid, pi.FileName);
      CProcessApi::tModuleInfo mi = {0};
      DWORD ml = papi.ModulesGetList(pi.pid);
      for (j=papi.ModulesCount(ml)-1;j>=0;j--)
      {
        if (!papi.ModulesWalk(ml, &mi, j))
        {
          printf("failed to module walk @ %d\n", j);
          continue;
        }
        printf(" Module(%08X/%08X): %s\n", mi.ImageBase, mi.ImageSize, 
                                           mi.FileName);
      }
      papi.ModulesFreeList(ml);
    }
  }
  papi.ProcessesFreeList(pl);
  return 0;
}

扩展代码

正如您所看到的,CProcessApi 类的设计允许我们添加任何最初在 PsApi 或 ToolHelp 中可用的新函数。
例如,尝试添加以下函数:

  • DWORD FindProcessID(LPTSTR ImagePath):给定一个镜像路径(或 exe 的路径),此函数将返回该当前正在运行进程的 ID(或 (DWORD)-1,如果进程未运行)。
  • bool GetProcessNameByPID(DWORD pid) :将返回给定 PID 的进程镜像名称。

我将额外的实用程序函数留给您的想象。祝您好运!

关注点

在编写此代码的过程中,我学会并再次练习了如何使用我定义的结构和可爱的 STL 容器来设计这样的统一系统。

我很高兴能收到您的评论或错误修正建议。希望您也能从本文中有所收获。

© . All rights reserved.