windows PSAPI.h 的一个BUG ?

来源:互联网 发布:瓦力抢红包软件 编辑:程序博客网 时间:2024/05/18 03:29

    想用windows PSAPI中的EnumPageFiles函数获取页面文件的列表及使用情况,查到在MSDN中的声明是这样的:

BOOL WINAPI EnumPageFiles(  __out         PENUM_PAGE_CALLBACK pCallbackRoutine,  __in          LPVOID lpContext);

看到第一个参数是__out,感觉非常奇怪,它要输出做什么用呢?想来应该是自己定义的一个函数,然后对每一个页面文件都调用该函数,因为在MSDN中PENUM_PAGE_CALLBACK 的定义是这样的:

BOOL CALLBACK EnumPageFilesProc(  [in]                 LPVOID pContext,  [in]                 PENUM_PAGE_FILE_INFORMATION pPageFileInfo,  [in]                 LPCTSTR lpFilename);
很容易想到在自定义的PENUM_PAGE_CALLBACK 中通过传入的参数PENUM_PAGE_FILE_INFORMATION 去获取页面文件的使用情况,但EnumPageFiles的第

一个参数无论如何不应该是输出,于是我试了一下,定义一个变量,该变量没有初始化的时候传给EnumPageFiles,

……
PENUM_PAGE_FILE_CALLBACK pf ; // = &OnEachPageFile;
if( EnumPageFiles(pf , NULL) ){
   return -1;
}
……

结果在VS中调试时会有异常抛出,是由于传入的变量没有初始化,显然该参数不应该是输出,否则没有理由要求参数必须进行初始化的,所以这个声明应该是有问题的,而事实上在psapi.h头文件的声明中并没有给EnumPageFiles的参数添加任何修饰词,例如__in或__out等,而其中绝大部分函数是有这些修饰词的,显然与MSDN文档不一致,而PENUM_PAGE_FILE_CALLBACK其实就是个宏,其定义及实际定义如下:

#define PENUM_PAGE_FILE_CALLBACK PENUM_PAGE_FILE_CALLBACKA
#define EnumPageFiles EnumPageFilesA
typedef BOOL (* PENUM_PAGE_FILE_CALLBACKA) (LPVOID pContext, PENUM_PAGE_FILE_INFORMATION pPageFileInfo, LPCSTR lpFilename);

这ASCII版的定义,本文只说ACII版的,UNICODE版的代码类似。可见EnumPageFiles 与MSDN文档中的定义也不一致。根据头文件中的定义,定义自己的EnumPageFilesProc,像这样:

BOOL OnEachPageFile(LPVOID pContext,
                    PENUM_PAGE_FILE_INFORMATION pPageFileInfo,
                    LPCTSTR lpFilename)
{
……
}

则在运行时会发生内存非法访问的异常,所以怀疑是头文件的声明与DLL中的实现不一致,关健就在于一个CALLBACK的修饰,因为CALLBACK其实是一个宏定义

#define CALLBACK    __stdcall

而VC中C/C++的默认函数调用方式是__cdecl的,DLL中的实现是__stdcall,这会导致调用函数的堆栈的维护方式不同,__cdecl是用函数调用者清理堆栈,而__stdcall是windows API的函数调用方式,它是由被调用函数清理堆栈,所以如果声明与实现不一致会导致堆栈的非访问。

于是试着去改psapi.h中的声明,将PENUM_PAGE_FILE_CALLBACKA的定义改为:

typedef BOOL (CALLBACK * PENUM_PAGE_FILE_CALLBACKA) (LPVOID pContext, PENUM_PAGE_FILE_INFORMATION pPageFileInfo, LPCSTR lpFilename); 

然后再测试,一切正常,当然自定义的函数的定义中也要有CALLBACK的修饰,完整代码如下:

#include
#include
#pragma comment(lib, "psapi")

 

#include
#include
#include

using std::string;
using std::vector;

 

struct SwapInfo{
    string name;
    string path;
    string type;
    long long free;
    long long size;
    long long used;
};

 

 

BOOL CALLBACK OnEachPageFile(LPVOID pContext,
                    PENUM_PAGE_FILE_INFORMATION pPageFileInfo,
                    LPCTSTR lpFilename)
{
    SwapInfo si;
    vector* ls = static_cast*>(pContext);

    si.free = pPageFileInfo->TotalSize - pPageFileInfo->TotalInUse;
    si.name = lpFilename;
    si.path = lpFilename;
    si.size = pPageFileInfo->TotalSize;
    si.type = "Pagefile";
    si.used =  pPageFileInfo->TotalInUse;
    ls->push_back(si);
    return TRUE;
}

 

int get_swap_info(vector & swapinfo)
{
    vector ls;
    SwapInfo info;
    PENUM_PAGE_FILE_CALLBACK pf = &OnEachPageFile;
    ls.clear();
    if( EnumPageFiles(pf, &ls) ){
        swapinfo.clear();
        swapinfo = ls;
        return 0;
    }
    return -1;

}

 

int main( int argc, char* argv[])
{
    vector pgfiles;

    if( get_swap_info(pgfiles) != 0){
        std::cerr<<"Failed !"<        return -1;
    }
    std::cout<<"Pagefiles:"<    for( vector::const_iterator it = pgfiles.begin(); it != pgfiles.end(); ++it){
        std::cout<name<<": "<used<<'/'<size<<" pages "<    }
    return 0;
}

输出结果:

F:/Projects/EnumPageFiles/Debug>EnumPageFiles.exe
Pagefiles:
C:/pagefile.sys: 14532/131072 pages
D:/pagefile.sys: 14579/32768 pages
E:/pagefile.sys: 14310/32768 pages

有一点值得说明,这里的EnumPageFiles函数会自动对每一个页面文件都调用一次OnEachPageFile,不论有多少个页面文件只需要调用一次EnumPageFiles。

结论: MSDN中绝大部分说明是正确的,除了EnumPageFiles第一个参数的修饰__out,应该使用__in,psapi.h中的声明错误,应该使用MSDN中的声明。

原创粉丝点击