C++,如何确定Windows进程是否正在运行?

75

这涉及到Windows XP进程的问题。

我有一个正在运行的进程,我们称之为Process1。Process1创建了一个新进程,Process2,并保存了它的id。

现在,在某个时刻,Process1想要让Process2做一些事情,因此首先需要确保Process2仍然存活,并且用户没有将其终止。

我该如何检查这个进程是否仍在运行? 由于我创建了这个进程,我拥有它的进程ID,我认为应该有一些类似于IsProcessIDValid( id )的库函数,但我在MSDN上找不到它。


DCOM已经免费完成了所有这些工作,你为什么要重新发明轮子呢? - Ana Betts
3
由于固有的竞态条件,这个计划似乎存在缺陷。在你检查Process2是否仍然存活之后,在你要求它完成你需要的工作(或者在它完成你需要的工作之前),用户可能会杀死Process2。最好是先发出Process2执行工作的命令,然后等待确认它已经完成。在等待过程中,你可以注意到Process2是否消失了。 - Adrian McCarthy
13个回答

85

您可以使用GetExitCodeProcess方法,如果进程仍在运行它会返回STILL_ACTIVE259),如果进程已退出并且返回该退出码,则同样返回STILL_ACTIVE


2
如果OP的Process1在调用CreateProcess后可以保存Process2的句柄(其他回复中被忽略的一点),那么使用GetExitCodeProcess会更好。可以将该句柄输入到GetExitCodeProcess函数中。 - vladr
哇,这比使用OpenProcess(PROCESS_QUERY_INFORMATION)好多了,谢谢! - rogerdpack
14
GetExitCodeProcess的相关文档中有一条重要的说明:如果该进程以STILL_ACTIVE作为退出代码返回,你的应用程序可能会将其解释为仍处于活动状态,尽管它已经终止。 - Niklas R
7
是的,这个设计问题非常严重,让人难以置信。 - Elmue
7
你可以检查退出代码是否为“STILL_ACTIVE”,如果是,则可以调用WaitForSingleObject(processHandle,0)并检查返回值是否为“WAIT_TIMEOUT”。 如果是,则进程仍处于活动状态,否则进程已将259作为退出代码返回。 注意:你应该使用SYNCHRONIZE期望访问标志调用 OpenProcess(),否则你将会得到一个权限错误。 - Netherwire
显示剩余2条评论

43
进程句柄在退出时会被发送信号。
因此以下代码可以正常工作(为了简洁起见已删除错误处理部分):
BOOL IsProcessRunning(DWORD pid)
{
    HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, pid);
    DWORD ret = WaitForSingleObject(process, 0);
    CloseHandle(process);
    return ret == WAIT_TIMEOUT;
}
请注意,进程ID可以被回收 - 更好的方法是缓存从CreateProcess调用返回的句柄。 您还可以使用线程池API(在Vista+上使用SetThreadpoolWait,在旧平台上使用RegisterWaitForSingleObject)来在进程退出时接收回调。
编辑:我错过了原始问题中“要做一些处理”部分。 如果在某个小窗口期间可以具有潜在过时数据或者想要在不尝试操作的情况下失败,则可以使用此技术。 您仍然必须处理由于进程已退出而导致的操作失败的情况。

4
应将其标题更改为WasProcessRunning,称其为IsProcessRunning是一个用词不当。 - JaredPar

21
#include <cstdio>
#include <windows.h>
#include <tlhelp32.h>

/*!
\brief Check if a process is running
\param [in] processName Name of process to check if is running
\returns \c True if the process is running, or \c False if the process is not running
*/
bool IsProcessRunning(const wchar_t *processName)
{
    bool exists = false;
    PROCESSENTRY32 entry;
    entry.dwSize = sizeof(PROCESSENTRY32);

    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

    if (Process32First(snapshot, &entry))
        while (Process32Next(snapshot, &entry))
            if (!wcsicmp(entry.szExeFile, processName))
                exists = true;

    CloseHandle(snapshot);
    return exists;
}

3
当exists被设置为true时,您可以考虑添加一个break来加快速度。 - wonko realtime
2
这里最好使用do...while()循环,因为Process32First找到的条目可能是正确的。 - Marco Veglio
正如@MarcoVeglio所指出的那样,这个解决方案跳过了第一个进程。然而,正如Michael评论的那样,第一个进程很可能是“SYSTEM”。 - PolarBear
这里有一个解决方案,考虑了Wonko和Marco的评论。链接 - PolarBear

12

@user152949提供的解决方案, 如评论中所述,跳过了第一个进程并且在"exists"为true时没有停止。让我提供一个修复后的版本:

#include <windows.h>
#include <tlhelp32.h>
#include <tchar.h>

bool IsProcessRunning(const TCHAR* const executableName) {
    PROCESSENTRY32 entry;
    entry.dwSize = sizeof(PROCESSENTRY32);

    const auto snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

    if (!Process32First(snapshot, &entry)) {
        CloseHandle(snapshot);
        return false;
    }

    do {
        if (!_tcsicmp(entry.szExeFile, executableName)) {
            CloseHandle(snapshot);
            return true;
        }
    } while (Process32Next(snapshot, &entry));

    CloseHandle(snapshot);
    return false;
}

2
两个小补充:1)此代码假定您的项目是Unicode。如果不是,请将arg更改为const char * const processName。2)参数的名称令人困惑。它不是进程名称,而是.exe文件的名称。 - Woody20
1
@Woody20,感谢您的评论。您是正确的。我已经更新了我的答案,并修复了版本。现在它适用于Unicode和非Unicode构建配置。 - PolarBear
我添加了一个 PID 检查,使其成为单实例应用程序的“IsProcessAlreadyRunning”检查 :) - Kyudos

8

我今天发现了这个内容,它来自于2003年。它可以通过进程名字来找到一个进程,甚至不需要进程ID。

\#include windows.h

\#include tlhelp32.h

\#include iostream.h

int FIND_PROC_BY_NAME(const char *);

int main(int argc, char *argv[])

{

//  Check whether a process is currently running, or not

char szName[100]="notepad.exe";   // Name of process to find

int isRunning;

    isRunning=FIND_PROC_BY_NAME(szName);

    // Note: isRunning=0 means process not found, =1 means yes, it is found in memor
    return isRunning;
}

int FIND_PROC_BY_NAME(const char *szToFind)

// Created: 12/29/2000  (RK)

// Last modified: 6/16/2003  (RK)

// Please report any problems or bugs to kochhar@physiology.wisc.edu

// The latest version of this routine can be found at:

//     http://www.neurophys.wisc.edu/ravi/software/killproc/

// Check whether the process "szToFind" is currently running in memory

// This works for Win/95/98/ME and also Win/NT/2000/XP

// The process name is case-insensitive, i.e. "notepad.exe" and "NOTEPAD.EXE"

// will both work (for szToFind)

// Return codes are as follows:

//   0   = Process was not found

//   1   = Process was found

//   605 = Unable to search for process

//   606 = Unable to identify system type

//   607 = Unsupported OS

//   632 = Process name is invalid

// Change history:

//  3/10/2002   - Fixed memory leak in some cases (hSnapShot and

//                and hSnapShotm were not being closed sometimes)

//  6/13/2003   - Removed iFound (was not being used, as pointed out

//                by John Emmas)

{

    BOOL bResult,bResultm;
    DWORD aiPID[1000],iCb=1000,iNumProc,iV2000=0;
    DWORD iCbneeded,i;
    char szName[MAX_PATH],szToFindUpper[MAX_PATH];
    HANDLE hProc,hSnapShot,hSnapShotm;
    OSVERSIONINFO osvi;
    HINSTANCE hInstLib;
    int iLen,iLenP,indx;
    HMODULE hMod;
    PROCESSENTRY32 procentry;      
    MODULEENTRY32 modentry;

    // PSAPI Function Pointers.
     BOOL (WINAPI *lpfEnumProcesses)( DWORD *, DWORD cb, DWORD * );
     BOOL (WINAPI *lpfEnumProcessModules)( HANDLE, HMODULE *,
        DWORD, LPDWORD );
     DWORD (WINAPI *lpfGetModuleBaseName)( HANDLE, HMODULE,
        LPTSTR, DWORD );

      // ToolHelp Function Pointers.
      HANDLE (WINAPI *lpfCreateToolhelp32Snapshot)(DWORD,DWORD) ;
      BOOL (WINAPI *lpfProcess32First)(HANDLE,LPPROCESSENTRY32) ;
      BOOL (WINAPI *lpfProcess32Next)(HANDLE,LPPROCESSENTRY32) ;
      BOOL (WINAPI *lpfModule32First)(HANDLE,LPMODULEENTRY32) ;
      BOOL (WINAPI *lpfModule32Next)(HANDLE,LPMODULEENTRY32) ;

    // Transfer Process name into "szToFindUpper" and
    // convert it to upper case
    iLenP=strlen(szToFind);
    if(iLenP<1 || iLenP>MAX_PATH) return 632;
    for(indx=0;indx<iLenP;indx++)
        szToFindUpper[indx]=toupper(szToFind[indx]);
    szToFindUpper[iLenP]=0;

    // First check what version of Windows we're in
    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    bResult=GetVersionEx(&osvi);
    if(!bResult)     // Unable to identify system version
        return 606;

    // At Present we only support Win/NT/2000 or Win/9x/ME
    if((osvi.dwPlatformId != VER_PLATFORM_WIN32_NT) &&
        (osvi.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS))
        return 607;

    if(osvi.dwPlatformId==VER_PLATFORM_WIN32_NT)
    {
        // Win/NT or 2000 or XP

         // Load library and get the procedures explicitly. We do
         // this so that we don't have to worry about modules using
         // this code failing to load under Windows 95, because
         // it can't resolve references to the PSAPI.DLL.
         hInstLib = LoadLibraryA("PSAPI.DLL");
         if(hInstLib == NULL)
            return 605;

         // Get procedure addresses.
         lpfEnumProcesses = (BOOL(WINAPI *)(DWORD *,DWORD,DWORD*))
            GetProcAddress( hInstLib, "EnumProcesses" ) ;
         lpfEnumProcessModules = (BOOL(WINAPI *)(HANDLE, HMODULE *,
            DWORD, LPDWORD)) GetProcAddress( hInstLib,
            "EnumProcessModules" ) ;
         lpfGetModuleBaseName =(DWORD (WINAPI *)(HANDLE, HMODULE,
            LPTSTR, DWORD )) GetProcAddress( hInstLib,
            "GetModuleBaseNameA" ) ;

         if( lpfEnumProcesses == NULL ||
            lpfEnumProcessModules == NULL ||
            lpfGetModuleBaseName == NULL)
            {
               FreeLibrary(hInstLib);
               return 605;
            }

        bResult=lpfEnumProcesses(aiPID,iCb,&iCbneeded);
        if(!bResult)
        {
            // Unable to get process list, EnumProcesses failed
            FreeLibrary(hInstLib);
            return 605;
        }

        // How many processes are there?
        iNumProc=iCbneeded/sizeof(DWORD);

        // Get and match the name of each process
        for(i=0;i<iNumProc;i++)
        {
            // Get the (module) name for this process

            strcpy(szName,"Unknown");
            // First, get a handle to the process
            hProc=OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ,FALSE,
                aiPID[i]);
            // Now, get the process name
            if(hProc)
            {
               if(lpfEnumProcessModules(hProc,&hMod,sizeof(hMod),&iCbneeded) )
               {
                  iLen=lpfGetModuleBaseName(hProc,hMod,szName,MAX_PATH);
               }
            }
            CloseHandle(hProc);
            // Match regardless of lower or upper case
            if(strcmp(_strupr(szName),szToFindUpper)==0)
            {
                // Process found
                FreeLibrary(hInstLib);
                return 1;
            }
        }
    }

    if(osvi.dwPlatformId==VER_PLATFORM_WIN32_WINDOWS)
    {
        // Win/95 or 98 or ME

        hInstLib = LoadLibraryA("Kernel32.DLL");
        if( hInstLib == NULL )
            return FALSE ;

        // Get procedure addresses.
        // We are linking to these functions of Kernel32
        // explicitly, because otherwise a module using
        // this code would fail to load under Windows NT,
        // which does not have the Toolhelp32
        // functions in the Kernel 32.
        lpfCreateToolhelp32Snapshot=
            (HANDLE(WINAPI *)(DWORD,DWORD))
            GetProcAddress( hInstLib,
            "CreateToolhelp32Snapshot" ) ;
        lpfProcess32First=
            (BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32))
            GetProcAddress( hInstLib, "Process32First" ) ;
        lpfProcess32Next=
            (BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32))
            GetProcAddress( hInstLib, "Process32Next" ) ;
        lpfModule32First=
            (BOOL(WINAPI *)(HANDLE,LPMODULEENTRY32))
            GetProcAddress( hInstLib, "Module32First" ) ;
        lpfModule32Next=
            (BOOL(WINAPI *)(HANDLE,LPMODULEENTRY32))
            GetProcAddress( hInstLib, "Module32Next" ) ;
        if( lpfProcess32Next == NULL ||
            lpfProcess32First == NULL ||
            lpfModule32Next == NULL ||
            lpfModule32First == NULL ||
            lpfCreateToolhelp32Snapshot == NULL )
        {
            FreeLibrary(hInstLib);
            return 605;
        }

        // The Process32.. and Module32.. routines return names in all uppercase

        // Get a handle to a Toolhelp snapshot of all the systems processes.

        hSnapShot = lpfCreateToolhelp32Snapshot(
            TH32CS_SNAPPROCESS, 0 ) ;
        if( hSnapShot == INVALID_HANDLE_VALUE )
        {
            FreeLibrary(hInstLib);
            return 605;
        }

        // Get the first process' information.
        procentry.dwSize = sizeof(PROCESSENTRY32);
        bResult=lpfProcess32First(hSnapShot,&procentry);

        // While there are processes, keep looping and checking.
        while(bResult)
        {
            // Get a handle to a Toolhelp snapshot of this process.
            hSnapShotm = lpfCreateToolhelp32Snapshot(
                TH32CS_SNAPMODULE, procentry.th32ProcessID) ;
            if( hSnapShotm == INVALID_HANDLE_VALUE )
            {
                CloseHandle(hSnapShot);
                FreeLibrary(hInstLib);
                return 605;
            }
            // Get the module list for this process
            modentry.dwSize=sizeof(MODULEENTRY32);
            bResultm=lpfModule32First(hSnapShotm,&modentry);

            // While there are modules, keep looping and checking
            while(bResultm)
            {
                if(strcmp(modentry.szModule,szToFindUpper)==0)
                {
                    // Process found
                    CloseHandle(hSnapShotm);
                    CloseHandle(hSnapShot);
                    FreeLibrary(hInstLib);
                    return 1;
                }
                else
                {  // Look for next modules for this process
                    modentry.dwSize=sizeof(MODULEENTRY32);
                    bResultm=lpfModule32Next(hSnapShotm,&modentry);
                }
            }

            //Keep looking
            CloseHandle(hSnapShotm);
            procentry.dwSize = sizeof(PROCESSENTRY32);
            bResult = lpfProcess32Next(hSnapShot,&procentry);
        }
        CloseHandle(hSnapShot);
    }
    FreeLibrary(hInstLib);
    return 0;

}

6

监控子进程的另一种方法是创建一个工作线程,它将:

  1. 调用CreateProcess()函数
  2. 调用WaitForSingleObject()函数 // 工作线程现在会等待子进程执行完毕。也可以获取主函数(main())的返回值。

2

8
如果进程2已经停止运行,那么该进程的PID可以重新使用。 - Adrian McCarthy
1
http://stackoverflow.com/questions/2384022/winsdk-determining-whether-an-arbitrary-pid-identifies-a-running-process-on-win 这个问题有一个答案,其中包含遍历的示例,如果这有帮助的话。 - rogerdpack

1

你永远无法检查并确定一个进程是否正在运行,你只能检查并确定一个进程在最近的过去某个时间点曾经运行过。进程是一个不受你的应用程序控制的实体,它可以在任何时刻退出。在检查进程是否正在运行和相应操作之间,无法保证进程不会退出。

最好的方法是直接执行所需的操作,并捕获如果进程未运行时可能抛出的异常。


@Michael,这里的意图很明确。OP明确表示他们想检查Process2是否存活,以便他们可以对其进行某些操作。 - JaredPar
1
另一个问题是PID可以很快地被重新使用。因此,如果进程2死亡并且PID被回收,您可能会认为您的进程仍在运行,但实际上并不是这样。 - Adrian McCarthy

1
JaredPar是正确的,你无法知道进程是否正在运行。你只能知道在你检查的那一刻进程是否在运行。它可能已经在此期间终止了。
你还必须意识到PID可以很快地被回收利用。所以仅因为有一个PID与你的进程相关,这并不意味着它是你的进程。
让进程共享一个GUID。(进程1可以生成GUID,并将其通过命令行传递给进程2。)进程2应该使用该GUID创建一个命名互斥体。当进程1想要进行检查时,它可以对该互斥体进行0超时的WaitForSingleObject操作。如果进程2已经离开,则返回代码将告诉您互斥体已经被弃置,否则您将得到超时。

0

您可以通过迭代运行进程并通过CreateToolhelp32Snapshot获取运行进程的快照,然后使用Process32First和Process32Next调用来判断一个进程(根据其名称或PID)是否正在运行。

然后,您可以使用结果为PROCESSENTRY32结构体的th32ProcessID字段或szExeFile字段,具体取决于您是想按PID还是可执行文件名搜索。可以在这里找到一个简单的实现。


仅提供链接答案通常被认为不太有用,最好是您能扩展您的回答。也许可以简要解释一下我们可以在文章中找到什么。 - madth3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接