这次通过一个实用的小例子讲解一下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;
}
这个程序还可以扩展,就是选中后右键单击结束进程.相关知识,前面几篇文章都有讲解.
再注意的一点是,这个程序如果不是以管理员身份运行,则系统进程是显示不出来的.