同步或異步調用EXE(創建子進程並重定向子進程的輸入、輸出)

  工作將要調用第三方的EXE完成一些操作,這些EXE類似CMD命令。因此,閒暇之餘寫了個類來調用EXE,並寫了個DEMO,界面如下圖:

  

  這類似的例子網上很多,我這個版本可以設置超時時長,若在指定的時間內子進程還未退出,則結束掉子進程,調用返回失敗,失敗描述中說明是超時導致。

  考慮這樣的情況,點擊調用按鈕後,創建子進程執行命令,比如”ping 127.1“,在這個過程中,子進程不斷輸出信息,調用者(對話框)可以獲得這些信息,但是由於UI線程正阻塞在調用按鈕的響應函數中,因此對話框獲得子進程輸出的信息後,還不能及時將信息顯示在界面上,只能等到子進程結束調用完成後,才能一起把獲得的信息顯示出來。而且調用過程中,整個對話框都無響應了,假死一般。這對於我來說如鯁在喉,非常不痛快,因此,我又加了個異步調用的功能。調用者異步調用可以立即返回,後臺開了個線程去創建子進程並等待子進程結束,然後這個線程再調用回調通知調用者子進程執行情況。

  當然調用者可以開啓一個線程來執行同步調用,那麼阻塞也是阻塞的這個線程,不會將主線程阻塞。但若類中實現異步,只多一個參數的話,使用起來就簡單的多了。

  要注意一點的是,在XP下一個線程用ReadFile讀取pipe阻塞時,另一個線程用PeekNamedPipe去peek這個pipe時也會阻塞,具體可參見PeekNamedPipe function頁面下部的Community Additions,引用如下(爲什麼引用不見了,多了個引用code呢):

PeekNamedPipe() may not return immediately

WARNING: PeekNamedPipe() can hang up and will not return immediately when the read end of an anonymous pipe has zero bytes in the pipe. This happens for me under Windows XP (my XP test machines are all XP Pro SP2, fully patched, and all have the problem). Interestingly, the exact same PeekNamedPipe() test EXE does not hang up and works just fine under both Windows 98 and Windows Vista.

UPDATE: The PeekNamedPipe() problem on XP only happens when another thread has entered ReadFile() on the read end of the pipe and has not yet returned. Namely, a ReadFile() blocked on the read end of the pipe in one thread will cause any subsequent PeekNamedPipe() in a second thread to also block -- until ReadFile() in the first thread returns. The same code that works on Windows 98 and Windows Vista may block under Windows XP. See http://www.duckware.com/tech/peeknamedpipe.html for test code.

  爲了方便查找,把代碼也帖上:

  InvokeExternalExe.h

// InvokeExternalExe.h: interface for the CInvokeExternalExe class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_INVOKEEXTERNALEXE_H__9BA72D3F_E9AD_4054_9F51_7C5281B2D2F2__INCLUDED_)
#define AFX_INVOKEEXTERNALEXE_H__9BA72D3F_E9AD_4054_9F51_7C5281B2D2F2__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include <Windows.h>
#include <string>
#include <tchar.h>

#if (defined UNICODE) || (defined _UNICODE)
#define tstring std::wstring
#else
#define tstring std::string
#endif

// 調用的程序中途輸出信息後,會調用此回調
// pUserData : CInvokeExternalExe::SetUserData()設置的值
// pButOutput : 調用的程序輸出信息的Buffer,可能存的是ANSI,也可能存的是UNICODE
// dwOutputSize : 調用的程序輸出信息Buffer大小
typedef BOOL (__stdcall * LPExecuteOutputCB)(
	const void * const pUserData, 
	const CHAR * const pButOutput, 
	DWORD dwOutputSize
);

// 異步調用時,執行完程序得到結果後會回調
// pUserData : CInvokeExternalExe::SetUserData()設置的值
// bInvodeSuccess : 調用是否成功
// dwExitCode : 若調用成功,保存應用程序的退出代碼
// strErrorMsg : 若調用失敗,返回失敗信息
typedef BOOL (__stdcall * LPAsyncInvokeResultCB)(
	const void * const pUserData, 
	const BOOL bInvodeSuccess,
	const DWORD dwExitCode,
	const tstring strErrorMsg
);

class CInvokeExternalExe  
{
private:
	void *m_pUserData;
	LPExecuteOutputCB m_pExecuteOutputCB;
	LPAsyncInvokeResultCB m_pAyncInvokeResultCB;
	HANDLE m_hChildStd_IN_Rd;
	HANDLE m_hChildStd_IN_Wr;
	HANDLE m_hChildStd_OUT_Rd;
	HANDLE m_hChildStd_OUT_Wr;
	HANDLE m_hProcessHandle;
	HANDLE m_hOutputThreadHandle;
	BOOL m_bInvokeFinish;
	HANDLE m_hAsyncInvokeThreadHandle;
	BOOL m_bIsInvoking;

	// 異步調用時需要保存以下信息
	BOOL m_bApplicationNameIsNull;
	tstring m_strApplicationName;
	BOOL m_bCommandLineIsNull;
	tstring m_strCommandLine;
	DWORD m_dwMilliseconds;
	WORD m_wShowWindow;

	CRITICAL_SECTION m_csLock;

private:
	tstring GetLastErrorMsg(LPCTSTR lpszFunction, const DWORD dwLastError);
	void CloseAllHandles();

	unsigned ReadOutput();
	static unsigned __stdcall ReadOutputThreadFunc(void* pArguments);

	unsigned AsyncInvoke();
	static unsigned __stdcall AsyncInvokeThreadFunc(void* pArguments);

	// 同步調用
	BOOL Invoke(
		IN LPCTSTR lpApplicationName,
		IN LPTSTR lpCommandLine,
		IN const DWORD dwMilliseconds,
		IN const WORD wShowWindow,
		OUT DWORD &dwExitCode,
		OUT tstring &strErrorMsg
		);

public:
	CInvokeExternalExe();
	virtual ~CInvokeExternalExe();

public:
	void SetUserData(void *pUserData)
	{
		m_pUserData = pUserData;
	}

	void SetExecuteOutputCB(LPExecuteOutputCB pExecuteOutputCB)
	{
		m_pExecuteOutputCB = pExecuteOutputCB;
	}

	void SetAsyncInvokeResultCB(LPAsyncInvokeResultCB pAsyncInvokeResultCB)
	{
		m_pAyncInvokeResultCB = pAsyncInvokeResultCB;
	}

	// 同步調用時返回值表示調用應用程序是否成功
	// 異常調用表示發起調用是否成功
	BOOL Invoke(
		IN LPCTSTR lpApplicationName,		// 應用程序名 
		IN LPTSTR lpCommandLine,			// 參數
		IN const BOOL bSynchronousInvoke,	// 是否同步調用
		IN const DWORD dwMilliseconds,		// 超時時長,單位毫秒,0表示不超時
		IN const WORD wShowWindow,			// 是否顯示窗口,可傳SW_...系列的宏
		OUT DWORD &dwExitCode,				// 調用成功後,應用程序的退出代碼
		OUT tstring &strErrorMsg			// 若調用失敗,返回失敗信息
		);

	// 取消調用
	void CancelInvoke();
};

#endif // !defined(AFX_INVOKEEXTERNALEXE_H__9BA72D3F_E9AD_4054_9F51_7C5281B2D2F2__INCLUDED_)

  InvokeExternalExe.cpp

// InvokeExternalExe.cpp: implementation of the CInvokeExternalExe class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "InvokeExternalExe.h"
#include <process.h>
#include <strsafe.h>

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

#define BUFSIZE 4096 

class CAutoLock
{
private:
	LPCRITICAL_SECTION m_pcsLcok;
	
public:
	CAutoLock(LPCRITICAL_SECTION pcsLcok)
	{
		m_pcsLcok = pcsLcok;
		if (m_pcsLcok)
		{
			EnterCriticalSection(m_pcsLcok);
		}
	}
	
	~CAutoLock()
	{
		if (m_pcsLcok)
		{
			LeaveCriticalSection(m_pcsLcok);
			m_pcsLcok = NULL;
		}
	}
};

CInvokeExternalExe::CInvokeExternalExe()
{
	m_pUserData = NULL;
	m_pExecuteOutputCB = NULL;
	m_pAyncInvokeResultCB = NULL;
	m_hChildStd_IN_Rd = NULL;
	m_hChildStd_IN_Wr = NULL;
	m_hChildStd_OUT_Rd = NULL;
	m_hChildStd_OUT_Wr = NULL;
	m_hProcessHandle = NULL;
	m_hOutputThreadHandle = NULL;
	m_bInvokeFinish = FALSE;
	m_hAsyncInvokeThreadHandle = NULL;
	m_bIsInvoking = FALSE;

	m_bApplicationNameIsNull = TRUE;
	m_bCommandLineIsNull = TRUE;
	m_dwMilliseconds = 0;
	m_wShowWindow = SW_NORMAL;

	InitializeCriticalSection(&m_csLock);
}

CInvokeExternalExe::~CInvokeExternalExe()
{
	CancelInvoke();
	DeleteCriticalSection(&m_csLock);
}

unsigned CInvokeExternalExe::ReadOutput()
{
	unsigned usRet = 1;

	DWORD dwTotalBytesAvail = 0;
	DWORD dwRead = 0;
	CHAR chBuf[BUFSIZE] = {0};
	BOOL bSuccess = FALSE;

	while (TRUE)
	{
		if (!PeekNamedPipe(m_hChildStd_OUT_Rd, NULL, 0, NULL, &dwTotalBytesAvail, NULL))
		{
			usRet = GetLastError();
			break;
		}
		if (0 == dwTotalBytesAvail)
		{
			if (m_bInvokeFinish)
			{
				usRet = 0;
				break;
			}
			else
			{
				Sleep(1);
				continue;
			}
		}

		bSuccess = ReadFile(m_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
		if (!bSuccess || dwRead == 0)
		{
			usRet = GetLastError();
			break;
		}
		if (dwRead < BUFSIZE)
		{
			chBuf[dwRead] = 0;
			if (IsTextUnicode(chBuf, dwRead, NULL) && dwRead + 1 < BUFSIZE)
			{
				chBuf[dwRead + 1] = 0;
			}
		}
		if (m_pExecuteOutputCB)
		{
			m_pExecuteOutputCB(m_pUserData, chBuf, dwRead);
		}
	}
	
	return usRet;
}

unsigned CInvokeExternalExe::ReadOutputThreadFunc(void* pArguments)
{
	CInvokeExternalExe *pThis = (CInvokeExternalExe *)pArguments;
	if (NULL == pThis)
	{
		_endthreadex (1);
		return 1;
	}

	unsigned usRet = pThis->ReadOutput();
	_endthreadex (usRet);
	return usRet;
}

unsigned CInvokeExternalExe::AsyncInvoke()
{
	unsigned usRet = 1;
	
	DWORD dwExitCode = 0;
	tstring strErrorMsg;

	PTCHAR lpCommandLine = NULL;
	if (!m_bCommandLineIsNull)
	{
		lpCommandLine = new TCHAR[m_strCommandLine.length() + 1];
		m_strCommandLine.copy(lpCommandLine, m_strCommandLine.length(), 0);
		lpCommandLine[m_strCommandLine.length()] = _T('\0');
	}
	
	BOOL bInvokeSuccess = Invoke(
		m_bApplicationNameIsNull ? NULL : m_strApplicationName.c_str(),
		lpCommandLine,
		m_dwMilliseconds,
		m_wShowWindow,
		dwExitCode,
		strErrorMsg
		);
	if (lpCommandLine)
	{
		delete [] lpCommandLine;
		lpCommandLine = NULL;
	}
	if (m_pAyncInvokeResultCB)
	{
		m_pAyncInvokeResultCB(m_pUserData, bInvokeSuccess, dwExitCode, strErrorMsg);
	}
	m_bIsInvoking = FALSE;

	return usRet;
}

unsigned CInvokeExternalExe::AsyncInvokeThreadFunc(void* pArguments)
{
	CInvokeExternalExe *pThis = (CInvokeExternalExe *)pArguments;
	if (NULL == pThis)
	{
		_endthreadex (1);
		return 1;
	}
	
	unsigned usRet = pThis->AsyncInvoke();
	_endthreadex (usRet);
	return usRet;
}


BOOL CInvokeExternalExe::Invoke(
								IN LPCTSTR lpApplicationName,
								IN LPTSTR lpCommandLine,
								IN const BOOL bSynchronousInvoke,
								IN const DWORD dwMilliseconds,
								IN const WORD wShowWindow,
								OUT DWORD &dwExitCode,
								OUT tstring &strErrorMsg)
{
	{
		CAutoLock autolock(&m_csLock);
		if (m_bIsInvoking)
		{
			strErrorMsg = _T("In executing.");
			return FALSE;
		}
		m_bIsInvoking = TRUE;
	}


	// 同步調用
	if (bSynchronousInvoke)
	{
		BOOL bRet = Invoke(
			lpApplicationName,
			lpCommandLine,
			dwMilliseconds,
			wShowWindow,
			dwExitCode,
			strErrorMsg
			);
		m_bIsInvoking = FALSE;
		return bRet;
	}

	// 異步調用
	if (lpApplicationName)
	{
		m_bApplicationNameIsNull = FALSE;
		m_strApplicationName = lpApplicationName;
	}
	else
	{
		m_bApplicationNameIsNull = TRUE;
	}
	if (lpCommandLine)
	{
		m_bCommandLineIsNull = FALSE;
		m_strCommandLine = lpCommandLine;
	}
	else
	{
		m_bCommandLineIsNull = TRUE;
	}
	m_dwMilliseconds = dwMilliseconds;
	m_wShowWindow = wShowWindow;

	unsigned threadID = 0;
	m_hAsyncInvokeThreadHandle = 
		(HANDLE)_beginthreadex(NULL, 0, &AsyncInvokeThreadFunc, this, 0, &threadID);
	if (NULL == m_hAsyncInvokeThreadHandle)
	{
		strErrorMsg = GetLastErrorMsg(_T("_beginthreadex"), GetLastError());
		return FALSE;
	}

	return TRUE;
}

BOOL CInvokeExternalExe::Invoke(
								IN LPCTSTR lpApplicationName,
								IN LPTSTR lpCommandLine,
								IN const DWORD dwMilliseconds,
								IN const WORD wShowWindow,
								OUT DWORD &dwExitCode,
								OUT tstring &strErrorMsg)
{
	m_bInvokeFinish = FALSE;

	if (NULL == lpApplicationName && NULL == lpCommandLine)
	{
		strErrorMsg = _T("Both lpApplicationName and lpCommandLine are NULL.");
		CloseAllHandles();
		return FALSE;
	}

	SECURITY_ATTRIBUTES saAttr; 
	
	saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
	saAttr.bInheritHandle = TRUE;
	saAttr.lpSecurityDescriptor = NULL; 
	
	if (!CreatePipe(&m_hChildStd_OUT_Rd, &m_hChildStd_OUT_Wr, &saAttr, 0))
	{
		strErrorMsg = GetLastErrorMsg(_T("StdoutRd CreatePipe"), GetLastError());
		CloseAllHandles();
		return FALSE;
	}

	if (!SetHandleInformation(m_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
	{
		strErrorMsg = GetLastErrorMsg(_T("Stdout SetHandleInformation"), GetLastError());
		CloseAllHandles();
		return FALSE;
	}

	if (!CreatePipe(&m_hChildStd_IN_Rd, &m_hChildStd_IN_Wr, &saAttr, 0))
	{
		strErrorMsg = GetLastErrorMsg(_T("Stdin CreatePipe"), GetLastError());
		CloseAllHandles();
		return FALSE;
	}
	
	if (!SetHandleInformation(m_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
	{
		strErrorMsg = GetLastErrorMsg(_T("Stdin SetHandleInformation"), GetLastError());
		CloseAllHandles();
		return FALSE;
	}

	unsigned threadID = 0;
	m_hOutputThreadHandle = 
		(HANDLE)_beginthreadex(NULL, 0, &ReadOutputThreadFunc, this, 0, &threadID);
	if (NULL == m_hOutputThreadHandle)
	{
		strErrorMsg = GetLastErrorMsg(_T("_beginthreadex"), GetLastError());
		CloseAllHandles();
		return FALSE;
	}
	
	PROCESS_INFORMATION piProcInfo; 
	STARTUPINFO siStartInfo;
	BOOL bSuccess = FALSE; 
	
	ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
		
	ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
	siStartInfo.cb = sizeof(STARTUPINFO); 
	siStartInfo.hStdError = m_hChildStd_OUT_Wr;
	siStartInfo.hStdOutput = m_hChildStd_OUT_Wr;
	siStartInfo.hStdInput = m_hChildStd_IN_Rd;
	siStartInfo.wShowWindow = wShowWindow;
	siStartInfo.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
	
	bSuccess = CreateProcess(lpApplicationName, 
		lpCommandLine, // command line 
		NULL,          // process security attributes 
		NULL,          // primary thread security attributes 
		TRUE,          // handles are inherited 
		0,             // creation flags 
		NULL,          // use parent's environment 
		NULL,          // use parent's current directory 
		&siStartInfo,  // STARTUPINFO pointer 
		&piProcInfo);  // receives PROCESS_INFORMATION 
	if (!bSuccess)
	{
		strErrorMsg = GetLastErrorMsg(_T("CreateProcess"), GetLastError());
		CloseAllHandles();
		return FALSE;
	}
	else
	{
		m_hProcessHandle = piProcInfo.hProcess;
		CloseHandle(piProcInfo.hThread);
	}

	switch (WaitForSingleObject(m_hProcessHandle, 0 == dwMilliseconds ? INFINITE : dwMilliseconds))
	{
	case WAIT_OBJECT_0:
		{
			if (GetExitCodeProcess(m_hProcessHandle, &dwExitCode))
			{
				m_bInvokeFinish = TRUE;
				WaitForSingleObject(m_hOutputThreadHandle, INFINITE);
				CloseAllHandles();
				return TRUE;
			}
			else
			{
				strErrorMsg = GetLastErrorMsg(_T("GetExitCodeProcess"), GetLastError());
				CloseAllHandles();
				return FALSE;
			}
		}
		break;
	case WAIT_TIMEOUT:
		{
			if (TerminateProcess(m_hProcessHandle, 1))
			{
				WaitForSingleObject(m_hProcessHandle, INFINITE);
			}

			strErrorMsg = GetLastErrorMsg(_T("WaitForSingleObject"), ERROR_TIMEOUT);
			CloseAllHandles();
			return FALSE;
		}
		break;
	default:
		{
			if (TerminateProcess(m_hProcessHandle, 1))
			{
				WaitForSingleObject(m_hProcessHandle, INFINITE);
			}
			
			strErrorMsg = GetLastErrorMsg(_T("WaitForSingleObject"), GetLastError());
			CloseAllHandles();
			return FALSE;
		}
		break;
	}
	
	return TRUE;
}

tstring CInvokeExternalExe::GetLastErrorMsg(LPCTSTR lpszFunction, const DWORD dwLastError)
{
	LPVOID lpMsgBuf = NULL;
    LPVOID lpDisplayBuf = NULL;
	
	FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dwLastError,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );

    lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, 
        (lstrlen((LPCTSTR)lpMsgBuf)+lstrlen((LPCTSTR)lpszFunction)+40)*sizeof(TCHAR)); 
    StringCchPrintf((LPTSTR)lpDisplayBuf, 
        LocalSize(lpDisplayBuf) / sizeof(TCHAR),
        TEXT("%s failed with error %d: %s"), 
        lpszFunction, dwLastError, lpMsgBuf); 

	tstring strLastError = (LPTSTR)lpDisplayBuf;
	
    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);

	return strLastError;
}

void CInvokeExternalExe::CloseAllHandles()
{
	CAutoLock autolock(&m_csLock);

	if (m_hChildStd_IN_Wr)
	{
		CloseHandle(m_hChildStd_IN_Wr);
		m_hChildStd_IN_Wr = NULL;
	}
	if (m_hChildStd_IN_Rd)
	{
		CloseHandle(m_hChildStd_IN_Rd);
		m_hChildStd_IN_Rd = NULL;
	}
	if (m_hChildStd_OUT_Wr)
	{
		CloseHandle(m_hChildStd_OUT_Wr);
		m_hChildStd_OUT_Wr = NULL;
	}
	if (m_hChildStd_OUT_Rd)
	{
		CloseHandle(m_hChildStd_OUT_Rd);
		m_hChildStd_OUT_Rd = NULL;
	}
	if (m_hOutputThreadHandle)
	{
		CloseHandle(m_hOutputThreadHandle);
		m_hOutputThreadHandle = NULL;
	}
	if (m_hProcessHandle)
	{
		CloseHandle(m_hProcessHandle);
		m_hProcessHandle = NULL;
	}
	if (m_hAsyncInvokeThreadHandle)
	{
		CloseHandle(m_hAsyncInvokeThreadHandle);
		m_hAsyncInvokeThreadHandle = NULL;
	}
}

void CInvokeExternalExe::CancelInvoke()
{
	CAutoLock autolock(&m_csLock);

	if (m_hOutputThreadHandle)
	{
		TerminateThread(m_hOutputThreadHandle, 1);
	}

	if (m_hProcessHandle)
	{
		TerminateProcess(m_hProcessHandle, 1);
	}

	if (m_hAsyncInvokeThreadHandle)
	{
		TerminateThread(m_hAsyncInvokeThreadHandle, 1);
		if (m_pAyncInvokeResultCB)
		{
			m_pAyncInvokeResultCB(m_pUserData, FALSE, 1, _T("Canceled Asynchronous Invoke."));
		}
	}

	CloseAllHandles();

	m_bIsInvoking = FALSE;
}



  DEMO下載地址如下,環境:Windows XP SP3 + Visual C++ 6.0

  同步或異步調用EXE(創建子進程並重定向子進程的輸入、輸出)

  兩篇參考文章,備案以供查找:

  MSDN:Creating a Child Process with Redirected Input and Output

  CODEPROJECT:Redirecting an arbitrary Console's Input/Output



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章