《Undocumented Windows 2000 Secrets》翻译 --- 3( 二 )


*lpcbNeeded = i*sizeof(DWORD);
fOk = TRUE;
}
LocalFree(psmi);
if ( !fOk ); SetLastError( RtlNtStatusToDosError(ns) );
}
}
else
SetLastError( RtlNtStatusToDosError(ns) );
return fOk;
}
列表1-4; EnumDeviceDrivers函数的示列
列表1-4列出了EnumDeviceDrivers()一种可能的实现方式 。注意这并不是来自psapi.dll的原始代码 。但通过C编译器它可以变成等效的二进制代码 。为了保持简单干净,我省略了源代码中易分散注意力的细节,比如结构化异常等 。在列表1-4的中间,你会看到NtQuerySystemInformation()函数作了很多工作 。这是我非常喜欢的Windows 2000函数之一,因为该函数可以访问多种重要的数据结构,如驱动、进程、线程、句柄(handle)和LPC端口列表等等 。我的文章“Inside Windows NT Sytem Data”(出版于1999年11月的Dr.Dobb’s Journal)在第一时间提供了有关该函数的内部信息及其搭档函数NtSetSystemInformation()的文档化资料 。另外的全面讲述这两个函数的文档可以在Gary Nebbett的《Indispendsable Windows NT/2000 Native API Reference》中找到 。
不要过于担心列表1-4列出的EnumDeviceDrivers()函数的实现细节 。我增加这些代码片断只是为了例举该函数有趣的一面,这像一根红线贯穿于psapi.dll 。在使用SystemModuleInformation标志第二次调用NtQuerySystemInformation()获取了完整的驱动列表后,代码遍历驱动模块数组并将其pImageBase成员复制到调用者提供的指针数组(名为lpImageBase[])中 。这似乎很正确,但除非你不知道NtQuerySystemInformation提供的模块数组所包含的其他信息 。这些数据结构都是没有文档化的,但是我现在可以告诉你,这些信息同样是有关模块在内存中的大小、它们的路径和名称、引用计数(load counts)和其他一些标志信息的 。甚至文件名在路径中的偏移量也是很容易就能得到的!,EnumDeviceDrivers()残忍的丢掉了所有这些有用的信息,仅仅保留了映像基址(Image Base address) 。
所以如果你试图通过返回的指针来获取有关模块的更多信息,则肯定会失败 。当你调用GetDeviceDriverFileName()来获取指定映像基址对应的文件路径时,猜猜psapi.dll会怎样做?它会运行与列表1-4类似的代码来获取完整的驱动列表,并遍历该列表来寻找指定的映像基址 。如果它找到一个匹配项,就将其路径复制到调用者的缓冲区中 。这难道很高效吗?为什么EnumDeviceDrivers不在它首次遍历驱动列表时就复制路径呢?按这样的方式实现此函数并没有多么困难 。除去性能问题,这种设计还有另一个潜在的问题:如果在GetDeviceDriverFileName()执行之前指定的模块就已经被卸载了会怎么样呢?该模块的地址将不会出现在第二次获取的驱动列表中,GetDeviceDriverFileName()将会失败 。我真不明白微软为什么会发布这样的DLL 。
枚举活动进程
psapi.dll的另一个典型工作就是枚举当前系统中运行的进程 。为此目的,该DLL提供了EnumProcesses()函数 。该函数的工作与EnumDeviceDrivers()十分类似,不过返回的是进程ID而不是虚拟地址了 。再次提示,该函数并不会提示缓冲区大小不足,因此我们还需再次使用trial-and-error循环,如列表1-5所示,这些代码和列表1-3很相似,除了有些不同的符号和类型名称 。
一个进程ID是一个全局数字标签可在整个系统中唯一标识一个进程 。进程和线程ID都取自同一个数字池(pool of numbers),从以0开始的Idle进程,在同一时间,所有运行的进程和线程都不会有相同的ID 。但是,当一个进程结束后,另一个进程可能会再次使用该结束进程或线程的ID 。因此,在X时间获取的一个进程ID在Y时间可能会代表另一个完全不同的进程 。也有可能在其使用的那一刻还没有定义或者指定给了某个线程 。所以,EnumProcesses()返回一个简单的进程ID列表并不能可靠的代表当前系统活动进程的快照 。如果考虑该函数的实现方式,这个设计缺陷真是无法原谅 。列表1-6是psapi.dll另一个函数的克隆,大致勾勒出了EnumProcessees()的基本动作 。和EnumDeviceDrivers()类似,它也依赖NtQuerySystemInformation()函数,不过在调用时,用SystemProcessInformation代替了SystemModuleInformation 。注意列表1-6中间的循环,在哪儿lpidProcess[]数组被来自SYSTEM_PROCESS_INFORMATION结构中的数据填充 。没什么好惊奇的,该结构也没有文档化 。

推荐阅读