2012. 3. 23. 14:50

프로세스 생성 종결자 (ShellExecute / CreateProcess)

Vista 이상에서 되도록 ShellExecute를 권장하고 있습니다.
그래서 ShellExecute를 그냥 사용하게 되는데,
간혹 Shell 쪽의 COM이 깨져 ShellExecute가 실패하는 경우가 있습니다.
그때는 CreateProcess를 해줘야 합니다.

ShellExecute / CreateProcess 모두 많은 Argument가 있어 사용하기 복잡합니다.
그래서,

  • 실행 경로 (lpszExePath)
  • 종료때 까지 기다릴지 여부 (bBlock)
  • Exit Code (pnExitCode, Optional)
  • 실행 파라미터 (lpszParam, Optional)
  • 실행 프로세스의 Current Directory (lpszDirectory, Optional)

와 같이 실제 자주 사용되는 Argument로 종합한 함수를 공유합니다.
물론, Optional은 NULL이나 0을 전달해도 무방합니다.

내부에서 ShellExecute가 실패하면 CreateProcess로 연결되니, 걱정않으셔도 됩니다. :)

// TRACELOG는 TRACE로그를 찍는 것일 수 있음
// 각자 정의한 것으로 Replace 요망
// 삭제 가능

// Block이 TRUE이고 ERROR_SUCCESS 리턴일때 pnExitCode 의미 있음
DWORD ExecuteProcess(IN LPCTSTR lpszExePath, IN BOOL bBlock, OPTIONAL OUT PINT pnExitcode, OPTIONAL IN LPCTSTR lpszParam, OPTIONAL IN LPCTSTR lpszDirectory)
{
	DWORD				dwRtnValue		= ERROR_SUCCESS;
	DWORD				dwResult		= 0;
	SHELLEXECUTEINFO	stInfo			= {0,};
	HANDLE				hCreateProcess	= NULL;
	STARTUPINFO			si				= {0,};
	PROCESS_INFORMATION pi				= {0,};
	LPTSTR				lpszCmd			= {0,};
	CString				strCmdLine;

	if (NULL != pnExitcode)
	{
		(*pnExitcode) = -1;
	}

	if (NULL == lpszExePath)
	{
		dwRtnValue = ERROR_INVALID_PARAMETER;
		goto FINAL;
	}

	TRACELOG(TEXT("[INFO] ExecuteCalled"));
	TRACELOG(TEXT("[INFO] lpszExePath : %s"), lpszExePath);
	if (NULL != lpszParam)
	{
		TRACELOG(TEXT("[INFO] lpszParam : %s"), lpszParam);
	}
	if (NULL != lpszDirectory)
	{
		TRACELOG(TEXT("[INFO] lpszDirectory : %s"), lpszDirectory);
	}
	TRACELOG(TEXT("[INFO] Block : %d"), bBlock);

	stInfo.cbSize		= sizeof(stInfo);
	stInfo.lpFile		= lpszExePath;
	stInfo.lpDirectory	= lpszDirectory;
	stInfo.lpParameters	= lpszParam;
	stInfo.fMask		= SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI | SEE_MASK_CLASSNAME;
	stInfo.nShow		= SW_SHOW;
	stInfo.lpClass		= TEXT("exefile");

	if (FALSE == ::ShellExecuteEx(&stInfo))
	{
		// ShellExcute가 실패했다.
		TRACELOG(TEXT("[ERROR] Fail to ShellExecuteEx, %d"), ::GetLastError());

		// CreateProcess로 재도전
		strCmdLine = lpszExePath;
		strCmdLine.TrimRight(TEXT("\""));
		strCmdLine.TrimLeft (TEXT("\""));
		strCmdLine.Insert(0, TEXT("\""));
		strCmdLine += TEXT("\"");

		si.cb = sizeof(si);

		if (NULL != lpszParam)
		{
			strCmdLine += TEXT(" ");
			strCmdLine += lpszParam;
		}

		lpszCmd = new TCHAR[_tcslen(strCmdLine)+1];
		if (NULL == lpszCmd)
		{
			dwRtnValue = ERROR_NOT_ENOUGH_MEMORY;
			goto FINAL;
		}
		ZeroMemory(lpszCmd, sizeof(TCHAR)*(_tcslen(strCmdLine)+1));
		::StringCchCopy(lpszCmd, _tcslen(strCmdLine)+1, strCmdLine);
		strCmdLine.Empty();
		if (FALSE == ::CreateProcess(NULL, 
									 lpszCmd, 
									 NULL, 
									 NULL, 
									 FALSE, 
									 0, 
									 NULL, 
									 lpszDirectory, 
									 &si,
									 &pi))
		{
			dwRtnValue = ::GetLastError();
			TRACELOG(TEXT("[INFO] Fail to CreateProcess, %d"), dwRtnValue);
			goto FINAL;
		}

		if (TRUE == bBlock)
		{
			TRACELOG(TEXT("[INFO] Try to Wait process(CP) %s"), lpszExePath);
			dwRtnValue = ::WaitForSingleObject(pi.hProcess, INFINITE);
			TRACELOG(TEXT("[INFO] Finished to Wait process(CP) %s"), lpszExePath);

			if (NULL != pnExitcode)
			{
				if (FALSE == ::GetExitCodeProcess(pi.hProcess, (DWORD*)pnExitcode))
				{
					dwRtnValue = ::GetLastError();
					TRACELOG(TEXT("[ERROR] Fail to GetExitCode(CP), GetLastError=%d"), dwRtnValue);
					goto FINAL;
				}

				TRACELOG(TEXT("[INFO] ExitCode(CP)=%d"), (*pnExitcode));
			}
		}

		if (NULL != pi.hProcess)
		{
			::CloseHandle(pi.hProcess);
			pi.hProcess = NULL;
		}

		if (NULL != pi.hThread)
		{
			::CloseHandle(pi.hThread);
			pi.hThread = NULL;
		}
	}
	else
	{
		if (NULL != stInfo.hProcess)
		{
			if (TRUE == bBlock)
			{
				TRACELOG(TEXT("[INFO] Try to Wait process %s"), lpszExePath);
				dwResult = ::WaitForSingleObject(stInfo.hProcess, INFINITE);
				TRACELOG(TEXT("[INFO] Finished to Wait process %s"), lpszExePath);

				if (NULL != pnExitcode)
				{
					if (FALSE == ::GetExitCodeProcess(stInfo.hProcess, (DWORD*)pnExitcode))
					{
						dwRtnValue = ::GetLastError();
						TRACELOG(TEXT("[ERROR] Fail to GetExitCode, GetLastError=%d"), dwRtnValue);
						goto FINAL;
					}

					TRACELOG(TEXT("[INFO] ExitCode=%d"), (*pnExitcode));
				}
			}

			::CloseHandle(stInfo.hProcess);
			stInfo.hProcess = NULL;
		}
	}

	dwRtnValue = ERROR_SUCCESS;

FINAL:
	if (NULL == lpszCmd)
	{
		delete [] lpszCmd;
		lpszCmd = NULL;
	}

	if (NULL != stInfo.hProcess)
	{
		::CloseHandle(stInfo.hProcess);
		stInfo.hProcess = NULL;
	}
	if (NULL != pi.hProcess)
	{
		::CloseHandle(pi.hProcess);
		pi.hProcess = NULL;
	}

	if (NULL != pi.hThread)
	{
		::CloseHandle(pi.hThread);
		pi.hThread = NULL;
	}
	return dwRtnValue;
}