我的程序需要枚举Windows中当前正在运行的进程,我是用CreateToolhelp32Snapshot实现的,在32位windows下运行一切正常。但当我在64位windows下测试程序的时候,发现该功能失效,枚举时只发现一个进程,就是我自己那个正在运行的程序。求教高手:如何在win32程序中,枚举64位系统下的进程?

解决方案 »

  1.   

    由于没有VISTA所以无法测试,不过CreateToolhelp32Snapshot中带32可能真是无法处理64位的进程数据,那就使用以下二种方法吧:
    第一种方法也很常见,通过MSDN就可以找到例子代码,它是通过Psapi.dll提供的API函数EnumProcesses和EnumProcessModules来实现。有一点要说明的是,Visual Studio提供的SDK包里没有提供相应的Psapi.h和与之相对应的导入库,笔者当时就很纳闷,MSDN里的例子居然编译不通,后来才发现,编译时根本找不到“psapi.h”。呵呵,还好,MSDN至少告诉我们他们都包含Psapi.dll里,用VC自带的Depend工具一查,果然。这样就好办了,我们可以自己找到这些函数入口地址。小知识:C也好C++也好,一般的函数名本质上都是一个地址,在Win32的API里,它是指向函数所在Dll模块里函数实现的入口地址。下面是第二种方法的实现过程:首先,先把Psapi.dll里要用到函数都定义好,方便后面显示调用。//在psaipi.dll中的函数EnumProcesses用来枚举进程 
    typedef BOOL (_stdcall *ENUMPROCESSES)(  //注意这里要指明调用约定为-stdcall
     DWORD* pProcessIds,  //指向进程ID数组链  
     DWORD cb,    //ID数组的大小,用字节计数
     DWORD* pBytesReturned);   //返回的字节
    //在psapi.dll中的函数EnumProcessModules用来枚举进程模块
    typedef BOOL (_stdcall *ENUMPROCESSMODULES)(
     HANDLE hProcess,   //进程句柄
     HMODULE* lphModule, //指向模块句柄数组链
     DWORD cb,    //模块句柄数组大小,字节计数
     LPDWORD lpcbNeeded);   //存储所有模块句柄所需的字节数
    //在psapi.dll中的函数GetModuleFileNameEx获得进程模块名
    typedef DWORD (_stdcall *GETMODULEFILENAMEEX)(
     HANDLE hProcess,   //进程句柄
     HMODULE hModule,   //进程句柄
     LPTSTR lpFilename,   //存放模块全路径名
     DWORD nSize    //lpFilename缓冲区大小,字符计算
    );好,通过每个定义的函数前的注释你可以清楚看到这些原函数名,然后当然要加载包含这些函数模块Psapi.dll啦:
     hPsDll = LoadLibrary("PSAPI.DLL");
    接着,通过Dll入口,我们查找每个函数地址:pEnumProcesses  = 
    (ENUMPROCESSES)GetProcAddress(hPsDll, "EnumProcesses");
    pEnumProcessModules  =
    (ENUMPROCESSMODULES)GetProcAddress(hPsDll, "EnumProcessModules");
    pGetModuleFileNameEx  =
       (GETMODULEFILENAMEEX)GetProcAddress(hPsDll, "GetModuleFileNameExA");
    注意第三个函数名GetModuleFileNameExA,在Dll里有以A和W结尾区分函数,A指采用的是ANSI字符串方式,W则是UNICODE方式。于是,我们可以用下面的语句枚举进程:
    pEnumProcesses(processid, sizeof(processid), &needed);
     processcount=needed/sizeof(DWORD); for (i=0;i<processcount;i++)
     {
      //打开进程
      hProcess=OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ,
    false, processid[i]);
      if (hProcess)
      {
       pEnumProcessModules(hProcess, &hModule, sizeof(hModule), &needed);
       pGetModuleFileNameEx(hProcess, hModule, path, sizeof(path));
       GetShortPathName(path,path,256);
       itoa(processid[i],temp,10);
       printf("%s --- %s\n",path,temp);
      }
      else
       printf("Failed!!!\n");
     }
     当然,在Google上一搜索,可以找到不少提供的Psapi.h头文件和对应的Psapi.lib库,这样,你就可以更方便用这种方法了。
      

  2.   

    另一种方法:这是一种最少人用的方法,这种方法利用了Native Api的NtQuerySystemInformation函数来实现。同样没有该函数的导入库,也要自己定义原形。整个实现不难,可是有点烦,下面我们来看看这个方法的实现吧。
    先是自定义函数原形:typedef NTSTATUS (__stdcall *PZWQUERYSYSTEMINFORMATION) 
                     (IN SYSTEM_INFORMATION_CLASS SystemInformationClass,  
                      IN OUT PVOID SystemInformation,  
                      IN ULONG SystemInformationLength,  
                      OUT PULONG ReturnLength);
    然后,还是和前面一样,要找到ZwQuerySystemInformation在Ntdll.dll模块里的入口地址:hModule = GetModuleHandle((LPCTSTR)"ntdll.dll");
    pZwQuerySystemInformation =(PZWQUERYSYSTEMINFORMATION)
    GetProcAddress(hModule, (LPCTSTR)"ZwQuerySystemInformation");
    获得指向进程信息数组链的第一条进程信息:
    pSystemProcessInformation = (SYSTEM_PROCESS_INFORMATION *)pvProcessList;和方法一方法二一样,在获得第一条进程信息后,开始循环遍历,列出其余的进程:
    while (TRUE) 
        { 
            if (pSystemProcessInformation->NextEntryDelta == 0) //如果是最后一条,则终止循环。
                break;                                            pSystemProcessInformation=(SYSTEM_PROCESS_INFORMATION*)((PCHAR)pSystemProcessInformation+ pSystemProcessInformation->NextEntryDelta); 
            pProcessName = (char *)malloc(pSystemProcessInformation->ProcessName.Length + 2); 
    }特别注意的是,这里SYSTEM_PROCESS_INFORMATION结构里进程名的类型为UNICODE_STRING, 而UNICODE_STRING里的成员Buffer定义的是Unicode类型。
    typedef struct _SYSTEM_PROCESS_INFORMATION  
    {  
        DWORD NextEntryDelta;  
        DWORD dThreadCount;  
        DWORD dReserved01;  
        DWORD dReserved02;  
        DWORD dReserved03;  
        DWORD dReserved04;  
        DWORD dReserved05;  
        DWORD dReserved06;  
        FILETIME ftCreateTime; /* relative to 01-01-1601 */  
        FILETIME ftUserTime; /* 100 nsec units */  
        FILETIME ftKernelTime; /* 100 nsec units */  
        UNICODE_STRING ProcessName;      //这就是进程名
        DWORD BasePriority;  
        DWORD dUniqueProcessId;            //进程ID
        DWORD dParentProcessID;  
        DWORD dHandleCount;  
        DWORD dReserved07;  
        DWORD dReserved08;  
        DWORD VmCounters;  
        DWORD dCommitCharge;  
        PVOID ThreadInfos[1]; 
    } SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;typedef struct _UNICODE_STRING { 
      USHORT Length; 
      USHORT MaximumLength; 
      PWSTR  Buffer;                 //注意,这里为Unicode类型
    } UNICODE_STRING, *PUNICODE_STRING;所以,最后我们要调用WideCharToMultiByte()来还原成ANSI字符串以便输出:
    WideCharToMultiByte(CP_ACP,  
                          0, 
                        pSystemProcessInformation->ProcessName.Buffer, 
                        pSystemProcessInformation->ProcessName.Length + 1, 
                        pProcessName, 
                        pSystemProcessInformation->ProcessName.Length + 1, 
                        NULL, 
                        NULL);
    具体每个参数可以参照MSDN.
      

  3.   

    以上方法都没有指定32位,所以在64位上应该可以被兼容运行的,其实还有一种方法:
    就是通过WMI的COM接口函数CoCreateInstance(),ConnectServer(),ExecQuery()等函数来实现.总之方法还是不少的.楼主再试试看吧.
      

  4.   

    使用PSAPI遍历,编译程序为64位
      

  5.   

    我的要求是编译程序为32位,不是64位!已经试过psapi,不能符合我的要求!
      

  6.   


    你32位程序在64系统上是兼容模式下运行,当然不可以获取的系统级的东东了。具体可参考procexp的实现,它启动先判断操作系统是否为64位,如果是它会从资源里释放出一个64位版本的程序并运行起来。
      

  7.   

    在高版本的VS(2008,2010)中,EnumProcess其实是可用的,返回的PID也都正确。问题出在枚举具体进程名称时,需要先OpenProcess,这个函数无法获取64-bit的进程的名称,返回的进程句柄都是0x00000000,从而造成整个枚举失败。