免费资源网,https://freexyz.cn/
目录
  • 一、自动重启的原理
  • 二、自动重启的实现
  • 三、相关知识点
    • 3.1 CommandLineToArg
    • 3.2 LocalFree
    • 3.3 OpenProcess
    • 3.4 WaitForSingleObject
    • 3.5 GetExitCodeProcess
    • 3.6 GetModuleFileName
    • 3.7 GetStartupInfo
    • 3.8 CreateProcess
  • 拓展

    一、自动重启的原理

    我不知道为什么很多程序员觉得自动重启很low,就像我始终不明白为什么有些人一听见我说“重新编译一下”就笑,难道不是重新编译一下大部分问题就解决了吗?

    自动重启原理很简单,用一个进程监控另一个进程,挂了就再启动一个。细节也不算多,主要是正确判断进程状态和启动方式,其实最大的工作量是程序恢复时应该如何回到原来的状态,这意味着程序要随时保存状态。

    只要你能做到用户无感,你在背后做了什么用户在意吗?

    二、自动重启的实现

    如果是UNIX,用fork然后监控子进程,挂了就再fork,一个循环就解决问题了。

    windows上麻烦一些,监控进程,控制台程序:

    #include "stdafx.h"
    #include "shellapi.h"
    #include <stdio.h>
    #include <string>
     
    using namespace std;
     
    bool bDebug = false;
     
    int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    	_In_opt_ HINSTANCE hPrevInstance,
    	_In_ LPWSTR    lpCmdLine,
    	_In_ int       nCmdShow)
    {
    	UNREFERENCED_PARAMETER(hPrevInstance);
     
    	// TODO: 在此处放置代码。
    	if (bDebug)MessageBox(NULL, lpCmdLine, TEXT("启动"), 0);
     
    	wchar_t buf[256];
    	DWORD pid = 0;
    	{
    		LPWSTR* szArglist;
    		int nArgs;
    		int i;
     
            //从命令行获取要监控的进程的PID,参数-pid后的下一个参数
    		szArglist = CommandLineToArgvW(lpCmdLine, &nArgs);//注意,如果没有参数,会返回程序名,如果有参数,则不包括程序名(或许是个BUG)
    		if (NULL == szArglist)
    		{
    			MessageBox(NULL, lpCmdLine, TEXT("CommandLineToArgvW失败"), 0);
    			return 0;
    		}
    		else
    		{
    			wsprintf(buf, TEXT("参数个数%d"), nArgs);
    			if (bDebug)MessageBox(NULL, buf, TEXT(""), 0);
    			for (i = 0; i < nArgs; ++i)
    			{
    				if (bDebug)MessageBox(NULL, szArglist[i], TEXT("CommandLineToArgvW"), 0);
    				if (0 == _tcsicmp(szArglist[i], TEXT("-pid")) && i + 1 < nArgs)
    				{
    					pid = _wtol(szArglist[i + 1]);
    					wsprintf(buf, TEXT("%u"), pid);
    					if (bDebug)MessageBox(NULL, buf, szArglist[i + 1], 0);
    				}
    			}
    		}
     
    		LocalFree(szArglist);
    	}
    	wsprintf(buf, TEXT("%u"), pid);
    	if (bDebug)MessageBox(NULL, buf, TEXT("pid"), 0);
     
        //打开进程以供监控
    	HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, pid);
        //等待进程结束
    	DWORD state = WaitForSingleObject(handle, INFINITE);
    	if (WAIT_OBJECT_0 == state)
    	{
    		DWORD exitCode;
    		if (!GetExitCodeProcess(handle, &exitCode))//获得退出码
    		{
    			MessageBox(NULL, TEXT("GetExitCodeProcess 出错"), TEXT("未能获取程序结束状态"), 0);
    		}
    		wsprintf(buf, TEXT("退出码 %u"), exitCode);
    		//MessageBox(NULL, buf, TEXT("任务完成"), 0);
    		if (0 != exitCode)//正常退出是返回码(return 返回码; 或者exit(返回码),一般约定正常返回0),异常结束肯定是非0
    		{//这一段就是以-r参数重启程序,两个程序必须在同一目录下
    			wchar_t _app_pathname[MAX_PATH];
    			GetModuleFileName(NULL, _app_pathname, MAX_PATH);
    			wstring app_pathname = _app_pathname;
    			size_t pos = app_pathname.find_last_of('\\');
    			if (pos != app_pathname.npos)
    			{
    				app_pathname.erase(pos + 1);
    				app_pathname += TEXT("app.exe");
    			}
    			else
    			{
    				app_pathname = TEXT("app.exe");
    			}
     
    			wchar_t szCmdLine[256];
    			wsprintf(szCmdLine, TEXT(" -r"));
     
    			PROCESS_INFORMATION   info;
    			STARTUPINFO startup;
     
    			GetStartupInfo(&startup);
     
    			BOOL   bSucc = CreateProcess(app_pathname.c_str(), szCmdLine, NULL, NULL,
    				FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &startup, &info);
     
    			if (!bSucc)
    			{
    				MessageBox(NULL, TEXT("CreateProcess 出错"), TEXT("恢复程序失败"), 0);
    			}
    		}
    	}
    	else if (WAIT_FAILED == state)
    	{
    		MessageBox(NULL, lpCmdLine, TEXT("WaitForSingleObject失败"), 0);
    	}
    	else
    	{
    		MessageBox(NULL, lpCmdLine, TEXT("WaitForSingleObject非预期的返回值"), 0);
    	}
    	return 0;
    }

    这个程序是这样的,工作的主程序名叫“app.exe”,在适当的时候启动了监控进程(就是这个代码,名称任意,但是必须和app.exe放在一起),并把自己的pid传递给监控进程(命令行参数-pid 主进程),监控进程启动后从命令行获取到需要监控的pid,监视pid状态,如果是正常结束,就退出程序,如果是异常结束,以“-r”参数启动主进程。

    主进程启动过程是这样的:启动到某个阶段,检查命令行,带有“-r”参数说明是自动恢复,走自动恢复流程,否则走正常流程,启动监控进程并把自己的pid传递过去。

    为什么要做一个独立的监控程序,不用主进程自身呢?因为程序太大了,有很多静态初始化的话,不知道起两个会不会有什么问题。

    为什么要通过命令行参数传递进程PID呢?主进程起子进程不是可以获得子进程的PID吗?因为好多程序喜欢套壳啊,返回的子进程又创建子进程干活,自己马上就退出了。

    获取自身PID的方法:

    DWORD pid = GetCurrentProcessId();
    

    要在验证程序异常退出可以return一个非零值,或者调用abort()。如果不区分是否是重启则不用处理参数,启动监控进程的代码和监控进程启动主进程的相似。

    三、相关知识点

    3.1 CommandLineToArg

    win32程序处理命令行真是费劲死了。

    shellapi.h
    Shell32.dll/Shell32.lib
    LPWSTR * CommandLineToArgvW(
      [in]  LPCWSTR lpCmdLine,
      [out] int     *pNumArgs
    );

    注意参数pNumArgs是在函数内部分配的,要在外部释放。这是C的习惯性做法。

    3.2 LocalFree

    释放本地内存对象。

    HLOCAL LocalFree(
      [in] _Frees_ptr_opt_ HLOCAL hMem
    );

    3.3 OpenProcess

    打开进程对象,以便后续等待进程结束。

    HANDLE OpenProcess(
      [in] DWORD dwDesiredAccess,
      [in] BOOL  bInheritHandle,
      [in] DWORD dwProcessId
    );

    3.4 WaitForSingleObject

    等待对象,可以是进程、线程、控制台输入等类型的句柄,等待的事件包括信号、超时等,发生了某种事件函数就会返回。

    DWORD WaitForSingleObject(
      [in] HANDLE hHandle,
      [in] DWORD  dwMilliseconds
    );

    3.5 GetExitCodeProcess

    获得进程退出码。

    BOOL GetExitCodeProcess(
      [in]  HANDLE  hProcess,
      [out] LPDWORD lpExitCode
    );

    3.6 GetModuleFileName

    一般用来获取程序的完整路径名。

    DWORD GetModuleFileNameA(
      [in, optional] HMODULE hModule,
      [out]          LPSTR   lpFilename,
      [in]           DWORD   nSize
    );

    3.7 GetStartupInfo

    获得程序的启动信息,在这个代码里用来传递给新进程。

    void GetStartupInfoW(
      [out] LPSTARTUPINFOW lpStartupInfo
    );

    3.8 CreateProcess

    创建进程。

    BOOL CreateProcessA(
      [in, optional]      LPCSTR                lpApplicationName,
      [in, out, optional] LPSTR                 lpCommandLine,
      [in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
      [in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
      [in]                BOOL                  bInheritHandles,
      [in]                DWORD                 dwCreationFlags,
      [in, optional]      LPVOID                lpEnvironment,
      [in, optional]      LPCSTR                lpCurrentDirectory,
      [in]                LPSTARTUPINFOA        lpStartupInfo,
      [out]               LPPROCESS_INFORMATION lpProcessInformation
    );

    参数虽多,大部分都可以不用。

    拓展

    C++重启进程

    步骤:

    1、查找需要重启进程的进程id

    2、启动需要重启的进程

    3、杀死第一步进程id的进程

    代码:

    1、查找需要重启的进程的进程id

    //通过进程名查找进程Id
    bool FindProcess(std::wstring strProcessName, DWORD& nPid)
    {
    
    	TCHAR tszProcess[64] = { 0 };
    	lstrcpy(tszProcess, strProcessName.c_str());
    	//查找进程
    	STARTUPINFO st;
    	PROCESS_INFORMATION pi;
    	PROCESSENTRY32 ps;
    	HANDLE hSnapshot;
    	memset(&st, 0, sizeof(STARTUPINFO));
    	st.cb = sizeof(STARTUPINFO);
    	memset(&ps, 0, sizeof(PROCESSENTRY32));
    	ps.dwSize = sizeof(PROCESSENTRY32);
    	memset(&pi, 0, sizeof(PROCESS_INFORMATION));
    	// 遍历进程  
    	hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    	if (hSnapshot == INVALID_HANDLE_VALUE)
    		return false;
    	if (!Process32First(hSnapshot, &ps))
    		return false;
    	do {
    		if (lstrcmp(ps.szExeFile, tszProcess) == 0)
    		{
    			//找到制定的程序
    			nPid = ps.th32ProcessID;
    			CloseHandle(hSnapshot);
    			return true;
    		}
    	} while (Process32Next(hSnapshot, &ps));
    	CloseHandle(hSnapshot);
    	return false;
    }
    

    2、启动进程

    //启动进程
    bool StartPrcess(std::wstring strProcessName)
    {
    	TCHAR tszProcess[64] = { 0 };
    	lstrcpy(tszProcess, strProcessName.c_str());
    	//启动程序
    	SHELLEXECUTEINFO shellInfo;
    	memset(&shellInfo, 0, sizeof(SHELLEXECUTEINFO));
    	shellInfo.cbSize = sizeof(SHELLEXECUTEINFO);
    	shellInfo.fMask = NULL;
    	shellInfo.hwnd = NULL;
    	shellInfo.lpVerb = NULL;
    	shellInfo.lpFile = tszProcess;						// 执行的程序名(绝对路径)
    	shellInfo.lpParameters = NULL;
    	shellInfo.lpDirectory = NULL;
    	shellInfo.nShow = SW_MINIMIZE;						//SW_SHOWNORMAL 全屏显示这个程序
    	shellInfo.hInstApp = NULL;
    	ShellExecuteEx(&shellInfo);
    
    	return true;
    }
    

    3、杀死进程

    //杀死进程
    bool KillProcess(DWORD dwPid)
    {
    	printf("Kill进程Pid = %d\n", dwPid); 
    	//关闭进程
    	HANDLE killHandle = OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION |   // Required by Alpha
    		PROCESS_CREATE_THREAD |   // For CreateRemoteThread
    		PROCESS_VM_OPERATION |   // For VirtualAllocEx/VirtualFreeEx
    		PROCESS_VM_WRITE,             // For WriteProcessMemory);
    		FALSE, dwPid);
    	if (killHandle == NULL)
    		return false;
    	TerminateProcess(killHandle, 0);
    	return true;
    }
    

    以上就是C++程序自动重启的实现代码的详细内容,更多关于C++程序自动重启的资料请关注其它相关文章!

    免费资源网,https://freexyz.cn/
    声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。