MFC-通过psapi.dll中的函数枚举进程

这次通过一个实用的小例子讲解一下psapi.dll(头文件是Psapi.h)中几个函数的用法.
效果如图:

这个例子需要用到的知识有:
静态文本http://blog.csdn.net/qq_18297675/article/details/50978562
ListControl http://blog.csdn.net/qq_18297675/article/details/50983600
自定义消息http://blog.csdn.net/qq_18297675/article/details/50994167
多线程的使用(多线程以后会讲)
还有psapi.dll中下面三个函数的用法
EnumProcesses()

//成功返回非零,失败返回0
BOOL WINAPI EnumProcesses(
  _Out_  DWORD *pProcessIds,//存放进程ID的数组
  _In_   DWORD cb,          //数组大小
  _Out_  DWORD *pBytesReturned//获取到的进程ID总大小
);

EnumProcessesModules()

//枚举进程模块
BOOL WINAPI EnumProcessModules
( 
_In_ HANDLE hProcess,   //进程句柄
_Out_writes_bytes_(cb) HMODULE *lphModule,//存放进程模块句柄的数组
_In_ DWORD cb, //数组大小
_Out_ LPDWORD lpcbNeeded //返回进程模块个数
 );

GetModuleFileNameEx()

//获取进程模块的文件名
DWORD WINAPI GetModuleFileNameEx(
  _In_      HANDLE hProcess, //进程句柄
  _In_opt_  HMODULE hModule, //模块句柄
  _Out_     LPTSTR lpFilename,//模块名称(包括路径)
  _In_      DWORD nSize      //lpFilename 的大小
);

有了以上的知识后,基本问题就不大了,就剩下逻辑问题了.

1.在初始化对话框中添加表头

//显示进程信息的表头
    m_proc.InsertColumn(0, TEXT("进程名称"), 0, 200);
    m_proc.InsertColumn(1, TEXT("PID"), 0, 100);

    //显示模块信息的表头
    m_modul.InsertColumn(0, TEXT("进程模块路径及名称"), 0, 300);

2.创建两个线程
创建的线程必须是全局的,并且在MFC中,线程函数是不能使用类的成员变量的.具体原因自行百度.所以必须想另一个办法把获取到的进程模块显示到List中.(注:线程的创建和使用有多种方法,这里我只举个最简单的例子)
步骤如下:在头文件中添加如下线程函数声明

//枚举进程线程
UINT ThreadEnumProc(LPVOID lParam);

//枚举进程模块线程
UINT ThreadEnumProcModule(LPVOID lParam);

然后在cpp中定义函数体,等会再写实现

//枚举进程线程
UINT ThreadEnumProc(LPVOID lParam)
{
   return 0;
}

//枚举进程模块线程
UINT ThreadEnumProcModule(LPVOID lParam)
{
   return 0;
}

3.在枚举进程按钮中使用线程

g_ThreadEnumProc = AfxBeginThread(ThreadEnumProc, nullptr);
    if (!g_ThreadEnumProc)
    {
        AfxMessageBox(TEXT("创建线程失败"));
        return;
    }
//所有的全局变量如下
//全局变量
CWinThread* g_ThreadEnumProc = nullptr;
CWinThread* g_ThreadEnumProcModule = nullptr;

DWORD g_ProcessId[512] = { 0 };//存放进程ID的数组
DWORD g_BytesReturned = 0;//返回实际ProcessId的大小,就是所有进程ID的大小
DWORD g_cbProcessId = 0;//进程个数
HMODULE g_hModules[512] = { 0 };//存放进程模块

4.在ThreadEnumProc中实现进程的枚举

//提权函数声明
BOOL AdjustPrivilege();

//枚举进程线程
UINT ThreadEnumProc(LPVOID lParam)
{
    //提权
    AdjustPrivilege();
    //枚举进程,得到进程的所有ID
    if (!EnumProcesses(g_ProcessId, sizeof(g_ProcessId), &g_BytesReturned))
    {
        AfxMessageBox(TEXT("枚举进程失败"));
        return FALSE;
    }
    //计算进程ID数
    g_cbProcessId = g_BytesReturned / sizeof(DWORD);
    HANDLE hProcess = nullptr;
    for (DWORD i = 0; i < g_cbProcessId;i++)
    {
        //打开进程 //要以管理员身份运行程序才能打开系统进程
        hProcess = OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, FALSE, g_ProcessId[i]);
        if (hProcess)
        {
            DWORD dwNeed = 0;//进程的模块个数
            TCHAR lpProcessName[MAX_PATH];
            //枚举进程的模块
            if (EnumProcessModules(hProcess, g_hModules, sizeof(g_hModules), &dwNeed))
            {
                if (GetModuleFileNameEx(hProcess, g_hModules[0], lpProcessName, sizeof(lpProcessName)))
                {
                    //发送消息显示进程
                    SendMessage(AfxGetApp()->m_pMainWnd->m_hWnd, 
                        WM_DISPLAY_PROCESS, (WPARAM)lpProcessName, (LPARAM)g_ProcessId[i]);
                }

            }
        }
    }


    return TRUE;
}

BOOL AdjustPrivilege()
{
    BOOL bRet = FALSE;
    TOKEN_PRIVILEGES tp = { 0 };//令牌权限结构
    HANDLE hToken = nullptr;//令牌句柄

    do
    {
        //打开当前进程令牌,并且获取它 //令牌权限修改和查询
        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES |
            TOKEN_QUERY, &hToken))
            break;
        //获取关机注销重启的LUID(Locally Unique Identifier),局部唯一标识
        if (!LookupPrivilegeValue(nullptr, SE_DEBUG_NAME, &tp.Privileges[0].Luid))
            break;
        tp.PrivilegeCount = 1;//修改权限的个数
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;//激活SE_SHUTDOWN_NAME这个权限
                                                           //提升权限//FALSE表示可以修改权限//把需要修改的权限传进来
        if (!AdjustTokenPrivileges(hToken, FALSE, &tp, 0, (PTOKEN_PRIVILEGES)nullptr, 0))
            break;
        bRet = TRUE;
    } while (FALSE);
    if (hToken)
        CloseHandle(hToken);
    return bRet;
}

5.自定义消息
消息函数如下:

LRESULT CMFCTESTDlg::OnDisPlayProcess(WPARAM wParam, LPARAM lParam)
{
    CString s;
    int iCount = m_proc.GetItemCount();
    m_proc.InsertItem(iCount, GetExeName((LPCTSTR)wParam));
    s.Format(TEXT("%d"), (DWORD)lParam);
    m_proc.SetItemText(iCount, 1, s);

    return 0;
}

//获取exe名字,把路径去掉
CString GetExeName(CString PathName)
{
    //先找到.exe的位置,从后往前截取
    int iPos = PathName.Find(".exe");
    char* szBuffer = PathName.GetBuffer();
    while (iPos--)
    {//每次往前一个字符,直到找到\跳出循环
        char ch = *(szBuffer + iPos);   
        if (ch == '\\')
             break;
    }
    //因为iPos指向\,所以要前进一个字符
    return (szBuffer + iPos + 1);
}

到这里进程就可以枚举出来了.
总结一下枚举进程的思路:
创建枚举进程函数->获取所有进程ID->通过ID枚举所有模块->获取每个进程的第一个模块(通常这就是exe模块)->发送消息到自定义的消息函数->显示发送过来的进程名->循环反复->完成

6.枚举进程的模块

//枚举进程模块线程
UINT ThreadEnumProcModule(LPVOID lParam)
{
    HANDLE hProcess = nullptr;
    //打开进程 //要以管理员身份运行程序才能打开系统进程
    hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE,(DWORD)lParam);
    if (hProcess)
    {
        DWORD dwNeed = 0;//进程的模块个数
        TCHAR lpProcessName[MAX_PATH];
        //枚举进程的模块
        if (EnumProcessModules(hProcess, g_hModules, sizeof(g_hModules), &dwNeed))
        {
            for (int j = 0; j < dwNeed; j++)
            {
                if (GetModuleFileNameEx(hProcess, g_hModules[j], lpProcessName, sizeof(lpProcessName)))
                {
                    //发送消息显示模块
                    SendMessage(AfxGetApp()->m_pMainWnd->m_hWnd,
                        WM_DISPLAY_MODULE, (WPARAM)lpProcessName, 0);
                }
            }
        }
    }


    return TRUE;
}

传进去的是一个进程ID参数,所以我们得获取进程的ID
7.当选中某个进程时,显示它的所有模块
双击进程信息List控件,代码如下:

void CMFCTESTDlg::OnLvnItemchangedListProc(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
    // TODO: 在此添加控件通知处理程序代码

    //先清除原有模块,再显示新的进程模块
    m_modul.DeleteAllItems();
    //获取选中的进程位置
    POSITION pos = m_proc.GetFirstSelectedItemPosition();
    if (pos)
    {   //得到选中进程的Index
        int iItem = m_proc.GetNextSelectedItem(pos);
        CString s;
        s = m_proc.GetItemText(iItem, 1);//获取PID
        DWORD dwProcessId = atoi(s.GetBuffer()); //转型
        //开启枚举模块进程,参数为进程ID
        g_ThreadEnumProcModule = AfxBeginThread(ThreadEnumProcModule,(LPVOID)dwProcessId);
        if (!g_ThreadEnumProcModule)
        {
            AfxMessageBox(TEXT("创建线程失败!"));
            return;
        }
    }

    *pResult = 0;
}

8.自定义消息函数
每获得一个模块名称,就触发这个消息显示出来

LRESULT CMFCTESTDlg::OnDisPlayModule(WPARAM wParam, LPARAM lParam)
{
    CString s;
    int iCount = m_modul.GetItemCount();
    m_modul.InsertItem(iCount, (LPCTSTR)wParam);

    return 0;
}

这个程序还可以扩展,就是选中后右键单击结束进程.相关知识,前面几篇文章都有讲解.
再注意的一点是,这个程序如果不是以管理员身份运行,则系统进程是显示不出来的.

你可能感兴趣的:(mfc,模块,枚举进程,Psapi-h,Psapi-dll)