Introduction
The idea is to create a class that will kill a process given the process name, and work on every single Windows platform (except 3.1), without requiring additional redistributable components. So the tricky part here isn't actually killing the process (there's a great KB article on how to do this correctly.) The problem is that you need a PID to do that, and you don't have that intrinsically, given only a name. So the trick is now how to enumerate the processes, get the PID, and not require any additional components while we're at it.
This is actually easier than it seems, because all Windows except NT4 have ToolHelp32 API which accomplishes just that goal. NT4, however, is a pain in the behind, because it does have PSAPI to do this also, but it comes on a redistributable dll, which means additional installation hassles. So I looked on Google, and found Alex Fedotov's terrific article, which lists five different ways to skin a rabb.. er, I mean enumerate a process. Method 3 was the one I needed, so I filched it, and used it in my code. Method 3 involves ZwQuerySystemInformation
, an undocumented NT API that's available for NT4, 2000, and onward.
At this point, you might go over to Google (or to NT4 DDK), discover that function, and also discover that Win2K has different internal structures used with this function than NT4. That's ok, because we don't use this on Win2K, or anything else besides NT4. Basically the idea is we try to load ToolHelp32 functions first, and only if that fails (which automatically means we're on NT4) do we try to use the Zw function.
Anyway, without further ado, here's the code. By the way, I tested it on 98, ME, NT4, 2k, and XP, different language versions too, and all seemed to work just fine. Share and enjoy. :)
Code
I hope you'll forgive me for the laziness, but I didn't feel like making a sample project for such a small application. Really, I've written no new code here, just packaged Alex's, moliate's and Microsoft's code a little bit better. So here it is:
#include <tlhelp32.h>
typedef LONG NTSTATUS;
typedef LONG KPRIORITY;
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
#define SystemProcessesAndThreadsInformation 5
typedef struct _CLIENT_ID {
DWORD UniqueProcess;
DWORD UniqueThread;
} CLIENT_ID;
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
typedef struct _VM_COUNTERS {
SIZE_T PeakVirtualSize;
SIZE_T VirtualSize;
ULONG PageFaultCount;
SIZE_T PeakWorkingSetSize;
SIZE_T WorkingSetSize;
SIZE_T QuotaPeakPagedPoolUsage;
SIZE_T QuotaPagedPoolUsage;
SIZE_T QuotaPeakNonPagedPoolUsage;
SIZE_T QuotaNonPagedPoolUsage;
SIZE_T PagefileUsage;
SIZE_T PeakPagefileUsage;
} VM_COUNTERS;
typedef struct _SYSTEM_THREADS {
LARGE_INTEGER KernelTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER CreateTime;
ULONG WaitTime;
PVOID StartAddress;
CLIENT_ID ClientId;
KPRIORITY Priority;
KPRIORITY BasePriority;
ULONG ContextSwitchCount;
LONG State;
LONG WaitReason;
} SYSTEM_THREADS, * PSYSTEM_THREADS;
typedef struct _SYSTEM_PROCESSES {
ULONG NextEntryDelta;
ULONG ThreadCount;
ULONG Reserved1[6];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ProcessName;
KPRIORITY BasePriority;
ULONG ProcessId;
ULONG InheritedFromProcessId;
ULONG HandleCount;
ULONG Reserved2[2];
VM_COUNTERS VmCounters;
#if _WIN32_WINNT >= 0x500
IO_COUNTERS IoCounters;
#endif
SYSTEM_THREADS Threads[1];
} SYSTEM_PROCESSES, * PSYSTEM_PROCESSES;
class CKillProcess
{
private:
typedef HANDLE (WINAPI *PFCreateToolhelp32Snapshot)(
DWORD dwFlags,
DWORD th32ProcessID
);
typedef BOOL (WINAPI *PFProcess32First)(
HANDLE hSnapshot,
LPPROCESSENTRY32 lppe
);
typedef BOOL (WINAPI *PFProcess32Next)(
HANDLE hSnapshot,
LPPROCESSENTRY32 lppe
);
typedef NTSTATUS (WINAPI * PFZwQuerySystemInformation)
(UINT, PVOID, ULONG, PULONG);
typedef HANDLE (WINAPI* PFGetProcessHeap)(VOID);
typedef LPVOID (WINAPI* PFHeapAlloc)
(HANDLE,DWORD,SIZE_T);
typedef BOOL (WINAPI* PFHeapFree)(HANDLE,DWORD,LPVOID);
public:
CKillProcessHelper() : FCreateToolhelp32Snapshot(NULL),
FProcess32First(NULL), FProcess32Next(NULL),
m_hKernelLib(NULL),
m_hNTLib(NULL)
{
m_hKernelLib = ::LoadLibraryA("Kernel32");
if (m_hKernelLib)
{
FCreateToolhelp32Snapshot =
(PFCreateToolhelp32Snapshot)
::GetProcAddress(m_hKernelLib,
_TEXT("CreateToolhelp32Snapshot"));
FProcess32First = (PFProcess32First)
::GetProcAddress(m_hKernelLib,
_TEXT("Process32First"));
FProcess32Next = (PFProcess32Next)
::GetProcAddress(m_hKernelLib,
_TEXT("Process32Next"));
}
if(!FCreateToolhelp32Snapshot ||
!FProcess32First || !FProcess32Next)
{
if(!m_hKernelLib)
return;
m_hNTLib = ::LoadLibraryA("ntdll.dll");
if(m_hNTLib)
{
FQuerySysInfo =
(PFZwQuerySystemInformation)
::GetProcAddress(m_hNTLib,
_TEXT("ZwQuerySystemInformation"));
FGetProcessHeap = (PFGetProcessHeap)
::GetProcAddress(m_hKernelLib,
_TEXT("GetProcessHeap"));
FHeapAlloc = (PFHeapAlloc)
::GetProcAddress(m_hKernelLib,
_TEXT("HeapAlloc"));
FHeapFree = (PFHeapFree)
::GetProcAddress(m_hKernelLib,
_TEXT("HeapFree"));
}
}
}
~CKillProcessHelper()
{
if(m_hKernelLib)
FreeLibrary(m_hKernelLib);
if(m_hNTLib)
FreeLibrary(m_hNTLib);
}
bool KillProcess(IN const char* pstrProcessName)
{
DWORD dwId;
HANDLE hProcess = FindProcess(pstrProcessName,
dwId);
BOOL bResult;
if(!hProcess)
return false;
::EnumWindows((WNDENUMPROC)
CKillProcessHelper::TerminateAppEnum,
(LPARAM) dwId);
if(WaitForSingleObject(hProcess, 5000)
!=WAIT_OBJECT_0)
bResult = TerminateProcess(hProcess,0);
else
bResult = TRUE;
CloseHandle(hProcess);
return bResult == TRUE;
}
private:
HANDLE FindProcess(IN const char* pstrProcessName,
OUT DWORD& dwId)
{
if(!m_hKernelLib)
return NULL;
if(FCreateToolhelp32Snapshot && FProcess32First &&
FProcess32Next)
return THFindProcess(pstrProcessName, dwId);
if(FQuerySysInfo && FHeapAlloc &&
FGetProcessHeap && FHeapFree)
return NTFindProcess(pstrProcessName, dwId);
return NULL;
}
HANDLE THFindProcess(IN const char* pstrProcessName,
OUT DWORD& dwId)
{
HANDLE hSnapShot=NULL;
HANDLE hResult = NULL;
PROCESSENTRY32 processInfo;
char* pstrExeName;
bool bFirst = true;
::ZeroMemory(&processInfo, sizeof(PROCESSENTRY32));
processInfo.dwSize = sizeof(PROCESSENTRY32);
hSnapShot = FCreateToolhelp32Snapshot(
TH32CS_SNAPPROCESS, 0);
if(hSnapShot == INVALID_HANDLE_VALUE)
return NULL;
while((bFirst ? FProcess32First(hSnapShot,
&processInfo) : FProcess32Next(hSnapShot,
&processInfo)))
{
bFirst = false;
pstrExeName = strrchr(processInfo.szExeFile,
'\\');
if(!pstrExeName)
pstrExeName = processInfo.szExeFile;
else
pstrExeName++;
if(stricmp(pstrExeName, pstrProcessName) == 0)
{
hResult=OpenProcess(
SYNCHRONIZE|PROCESS_TERMINATE, TRUE,
processInfo.th32ProcessID);
dwId = processInfo.th32ProcessID;
break;
}
}
if(hSnapShot)
CloseHandle(hSnapShot);
return hResult;
}
HANDLE NTFindProcess(IN const char* pstrProcessName,
OUT DWORD& dwId)
{
HANDLE hHeap = FGetProcessHeap();
NTSTATUS Status;
ULONG cbBuffer = 0x8000;
PVOID pBuffer = NULL;
HANDLE hResult = NULL;
do
{
pBuffer = HeapAlloc(hHeap, 0, cbBuffer);
if (pBuffer == NULL)
return SetLastError(
ERROR_NOT_ENOUGH_MEMORY), NULL;
Status = FQuerySysInfo(
SystemProcessesAndThreadsInformation,
pBuffer, cbBuffer, NULL);
if (Status == STATUS_INFO_LENGTH_MISMATCH)
{
HeapFree(hHeap, 0, pBuffer);
cbBuffer *= 2;
}
else if (!NT_SUCCESS(Status))
{
HeapFree(hHeap, 0, pBuffer);
return SetLastError(Status), NULL;
}
}
while (Status == STATUS_INFO_LENGTH_MISMATCH);
PSYSTEM_PROCESSES pProcesses =
(PSYSTEM_PROCESSES)pBuffer;
for (;;)
{
PCWSTR pszProcessName =
pProcesses->ProcessName.Buffer;
if (pszProcessName == NULL)
pszProcessName = L"Idle";
CHAR szProcessName[MAX_PATH];
WideCharToMultiByte(CP_ACP, 0, pszProcessName,
-1,szProcessName, MAX_PATH, NULL, NULL);
if(stricmp(szProcessName, pstrProcessName)
== 0)
{
hResult=OpenProcess(
SYNCHRONIZE|PROCESS_TERMINATE, TRUE,
pProcesses->ProcessId);
dwId = pProcesses->ProcessId;
break;
}
if (pProcesses->NextEntryDelta == 0)
break;
pProcesses = (PSYSTEM_PROCESSES)(
((LPBYTE)pProcesses)
+ pProcesses->NextEntryDelta);
}
HeapFree(hHeap, 0, pBuffer);
return hResult;
}
static BOOL CALLBACK TerminateAppEnum( HWND hwnd,
LPARAM lParam )
{
DWORD dwID ;
GetWindowThreadProcessId(hwnd, &dwID) ;
if(dwID == (DWORD)lParam)
{
PostMessage(hwnd, WM_CLOSE, 0, 0) ;
}
return TRUE ;
}
HMODULE m_hNTLib;
HMODULE m_hKernelLib;
PFCreateToolhelp32Snapshot FCreateToolhelp32Snapshot;
PFProcess32First FProcess32First;
PFProcess32Next FProcess32Next;
PFZwQuerySystemInformation FQuerySysInfo;
PFGetProcessHeap FGetProcessHeap;
PFHeapAlloc FHeapAlloc;
PFHeapFree FHeapFree;
};
Credits
As I said before, this code is really a compilation of three different articles:
- moliate's article on how to enumerate processes using ToolHelp API and PSAPI
- Alex Fedotov's article on how to do the same, but with Zw-function
- KB's article on how to terminate a process cleanly
Copyright
(you knew it was coming...)
The code is completely, utterly, and absolutely free. Feel free to use it in private or commercial applications, modify it, sit on it, or print it out and use it for toilet paper. God knows I've ignored enough copyrights from CodeProject to have the nerve to make my own. :) One thing though -- if this code messes up your computer or puts your project seven months behind schedule, it's not my fault. None of it is my fault. Not even that "format c:" somewhere in there. Er, just kidding. :) Oh yeah, needless to say, you can't claim this code as your own. (I.E. I'd hate for someone to reprint this code, and copyright the hell out of it. Not sure if that's possible anyhow, but it would suck. Greatly.)
Conclusion
Probably a pointless thing to say, but if you're going to rate the article badly, at least take the time to write a comment saying how I could improve it, or what you find inadequate about it.