同步或异步调用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



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