console执行命令的操作方式,即通过shell操作电脑的方式,在Linux下的方便快捷无需言说。同样,在Windows下的cmd是一个也是一个很有用的工具,有很多命令,通过console的cmd执行后,能够快速得出结果。
在编程中,有很多应用软件或者服务软件需要执行一些windows常用的cmd作为软件辅助,不仅如此,执行有些cmd后还要得出相应的执行结果,以便于根据结果做相应逻辑调整;另一方面,执行cmd后也需要得知执行是否成功,不成功的话会是什么错误。
本篇博客使用Windows下的API CreateProcess来执行cmd,并通过创建匿名管道来获取执行结果。
1、获取cmd执行成功还是失败
经过测试发现,windows下的cmd有后缀可得出命令执行的成功结果。通过
"cmd" + "command string" + "&&" + "echo string1" + "||" + "echo string2"的命令形式,可在执行命令结束后返回结果,其中"command string"是具体的命令本身。
如果command string执行成功,则返回 string1;否则返回string2。
例如执行打开记事本命令(用例系统为win10,64位):
可看到command string 为"notepad",其后跟后缀"&&echo S_OK || echo E_FAILED",其中的"S_OK"为string1,"notepad"命令成功后的返回string1,"E_FAILED"为string2,"notepad"命令失败后返回string2。
我们的例子中返回了S_OK,说明命令执行成功,也可看到确实成功打开了记事本。
如果command执行后有字符串返回,则先返回字符串,在返回的字符串末尾返回string1或string2。例如:
当然,string1和string2的字符串可自定义。
2、匿名管道通信
BOOL
WINAPI
CreatePipe(
_Out_ PHANDLE hReadPipe,
_Out_ PHANDLE hWritePipe,
_In_opt_ LPSECURITY_ATTRIBUTES lpPipeAttributes,
_In_ DWORD nSize
);
CreatePipe是创建一个匿名管道,并从中得到读写管道的句柄。
hReadPipe[out] 返回一个可用于读管道数据的文件句柄
hWritePipe[out] 返回一个可用于写管道数据的文件句柄
lpPipeAttributes[in, optional] 传入一个SECURITY_ATTRIBUTES结构的指针,该结构用于决定该函数返回的句柄是否可被子进
程继承。如果传NULL,则返回的句柄是不可继承的。
该结构的lpSecurityDescriptor成员用于设定管道的安全属性,如果传NULL,那么该管道将获得一个默认的安全属性,该属性与
创建该管道的用户账户权限ACLs的安全令牌(token)相同。
nSize[in]
管道的缓冲区大小。但是这仅仅只是一个理想值,系统根据这个值创建大小相近的缓冲区。如果传入0 ,那么系统将使用一个默
认的缓冲区大小。
BOOL
WINAPI
PeekNamedPipe(
_In_ HANDLE hNamedPipe,
_Out_writes_bytes_to_opt_(nBufferSize, *lpBytesRead) LPVOID lpBuffer,
_In_ DWORD nBufferSize,
_Out_opt_ LPDWORD lpBytesRead,
_Out_opt_ LPDWORD lpTotalBytesAvail,
_Out_opt_ LPDWORD lpBytesLeftThisMessage
);
预览一个管道中的数据,或取得与管道中的数据有关的信息。
参数
hNamedPipe [in]
管道句柄。这个参数可以是一个命名管道实例句柄,返回,由CreateNamedPipe或CreateFile函数,或者它可以是一个匿名管
道的读端句柄,返回由CREATEPIPE功能。句柄必须有GENERIC_READ权限的管道。
lpBuffer [out, optional]
接收从管道读取数据的缓冲区的指针。如果没有数据要读取,此参数可以为NULL。
nBufferSize [in]
lpBuffer参数以字节为单位,由指定的缓冲区大小。如果lpBuffer是NULL,则忽略此参数。
lpBytesRead [out, optional]
接收从管道中读取的字节数的变量的指针。此参数可以为NULL,如果没有数据要读取。
lpTotalBytesAvail [out, optional]
一个指针变量,接收从管道读取的字节总数。此参数可以为NULL,如果没有数据要读取。
lpBytesLeftThisMessage [out, optional]
指向剩余的字节数的变量的指针消息。此参数将是零字节类型的命名管道或匿名管道。此参数可以为NULL,如果没有数据要读取。
返回值
如果函数成功,返回值为非零。
如果函数失败,返回值是零。为了获得更多错误信息,调用GetLastError。
BOOL
WINAPI
ReadFile(
_In_ HANDLE hFile,
_Out_writes_bytes_to_opt_(nNumberOfBytesToRead, *lpNumberOfBytesRead) __out_data_source(FILE) LPVOID lpBuffer,
_In_ DWORD nNumberOfBytesToRead,
_Out_opt_ LPDWORD lpNumberOfBytesRead,
_Inout_opt_ LPOVERLAPPED lpOverlapped
);
从文件指针指向的位置开始将数据读出到一个文件中, 且支持同步和异步操作,
参数说明
HANDLE hFile, 需要读入数据的文件指针,这个指针指向的文件必须是GENERIC_READ 访问属性的文件。
LPVOID lpBuffer,接收数据的缓冲区。
DWORD nNumberOfBytesToRead,指定要读取的字节数。
LPDWORD lpNumberOfBytesRead,指向一个DWORD类型变量的指针,用来接收读取的字节数。如果下一个参数为NULL,那么一定要传入这个参数。
LPOVERLAPPED lpOverlapped OVERLAPPED结构体指针,如果文件是以FILE_FLAG_OVERLAPPED方式打开的话,那么这个指针就不能为NULL。
FILE_FLAG_OVERLAPPED 允许对文件进行重叠操作
返回值
调用成功返回非0,否则返回0
3、实现
对执行命令并匿名管道读取结果,本博客封装了一个类,提供接口供外部调用。
以下是封装类的头文件:
/* ====================================================
* Copyright: 275659704@qq.com
* Author: wfy
* Date: 2018-4-15
* Description: 提供CreateProcess()执行
* 命令并通过匿名管道获取的封装
==================================================== */
#ifndef __CMD_HANDLER_H__
#define __CMD_HANDLER_H__
#include <Windows.h>
/* buffer的最大长度 */
#define PIPE_BUFFER_SIZE 1024
/* 命令参数 */
typedef struct _CHCmdParam CHCmdParam;
struct _CHCmdParam
{
/* 初始化为 sizeof(CommandParam) */
int iSize;
/* 外部命令,不需要用则设置为-1, 提供给外部使用
*/
int iCommand;
/* 超时时间,单位秒 */
int iTimeOut;
/* 命令行要执行的命令 */
TCHAR szCommand[MAX_PATH];
/* 用户数据 */
void* pUserData;
/* 命令执行后的回调 */
void (*OnCmdEvent)(const CHCmdParam* pParam, HRESULT hResultCode, char* szResult);
};
class CCmdHandler
{
private:
BOOL m_bInit;
STARTUPINFO m_startupInfo;
PROCESS_INFORMATION m_processInfo;
SECURITY_ATTRIBUTES m_saOutPipe;
DWORD m_dwErrorCode;
HANDLE m_hPipeRead;
HANDLE m_hPipeWrite;
CHCmdParam m_CommandParam;
TCHAR m_szReadBuffer[PIPE_BUFFER_SIZE];
TCHAR m_szWriteBuffer[PIPE_BUFFER_SIZE];
char m_szPipeOut[PIPE_BUFFER_SIZE];
HRESULT ExecuteCmdWait();
public:
CCmdHandler();
~CCmdHandler();
/*
* 初始化接口,调用其余接口之前调用
* 成功返回S_OK
*/
HRESULT Initalize();
/*
* 结束接口
*/
HRESULT Finish();
/*
* 执行命令接口,接口调用成功返回S_OK
* param[in] pCommmandParam: 指向一个CHCmdParam命令参数结构的指针
*/
HRESULT HandleCommand(CHCmdParam* pCommmandParam);
/*
* 返回错误码,便于差距接口调用失败后产生什么错误
*/
DWORD GetErrorCode() { return m_dwErrorCode; }
};
#endif // !__CMD_HANDLER_H__
以下是实现文件:
#include "CmdHandler.h"
#include <tchar.h>
#define EXCEPTIION_STATE_CHECK \
if (!m_bInit) return E_NOTIMPL
CCmdHandler::CCmdHandler()
: m_bInit(FALSE)
, m_dwErrorCode(0)
, m_hPipeRead(NULL)
, m_hPipeWrite(NULL)
{
ZeroMemory(m_szReadBuffer, sizeof(m_szReadBuffer));
ZeroMemory(m_szWriteBuffer, sizeof(m_szWriteBuffer));
ZeroMemory(&m_CommandParam, sizeof(m_CommandParam));
}
CCmdHandler::~CCmdHandler()
{
}
HRESULT CCmdHandler::Initalize()
{
// 初始化,创建匿名管道
if (m_bInit) return S_OK;
m_bInit = TRUE;
ZeroMemory(m_szReadBuffer, sizeof(m_szReadBuffer));
ZeroMemory(&m_saOutPipe, sizeof(m_saOutPipe));
m_saOutPipe.nLength = sizeof(SECURITY_ATTRIBUTES);
m_saOutPipe.lpSecurityDescriptor = NULL;
m_saOutPipe.bInheritHandle = TRUE;
ZeroMemory(&m_startupInfo, sizeof(STARTUPINFO));
ZeroMemory(&m_processInfo, sizeof(PROCESS_INFORMATION));
if (!CreatePipe(&m_hPipeRead, &m_hPipeWrite, &m_saOutPipe, PIPE_BUFFER_SIZE))
{
m_dwErrorCode = GetLastError();
return E_FAIL;
}
return S_OK;
}
HRESULT CCmdHandler::Finish()
{
EXCEPTIION_STATE_CHECK;
if (m_hPipeRead)
{
CloseHandle(m_hPipeRead);
m_hPipeRead = NULL;
}
if (m_hPipeWrite)
{
CloseHandle(m_hPipeWrite);
m_hPipeWrite = NULL;
}
return S_OK;
}
HRESULT CCmdHandler::HandleCommand(CHCmdParam* pCommmandParam)
{
EXCEPTIION_STATE_CHECK;
if (!pCommmandParam || pCommmandParam->iSize != sizeof(CHCmdParam))
return E_INVALIDARG;
if (_tcslen(pCommmandParam->szCommand) <= 0)
return E_UNEXPECTED;
memset(&m_CommandParam, 0, sizeof(m_CommandParam));
m_CommandParam = *pCommmandParam;
return ExecuteCmdWait();
}
HRESULT CCmdHandler::ExecuteCmdWait()
{
EXCEPTIION_STATE_CHECK;
HRESULT hResult = E_FAIL;
DWORD dwReadLen = 0;
DWORD dwStdLen = 0;
m_startupInfo.cb = sizeof(STARTUPINFO);
m_startupInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
m_startupInfo.hStdOutput = m_hPipeWrite;
m_startupInfo.hStdError = m_hPipeWrite;
m_startupInfo.wShowWindow = SW_HIDE;
DWORD dTimeOut = m_CommandParam.iTimeOut >= 3000 ? m_CommandParam.iTimeOut : 5000;
do
{
if (!CreateProcess(NULL, m_CommandParam.szCommand,
NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL,
&m_startupInfo, &m_processInfo))
{
m_dwErrorCode = GetLastError();
hResult = E_FAIL;
break;
}
if (WAIT_TIMEOUT == WaitForSingleObject(m_processInfo.hProcess, dTimeOut))
{
m_dwErrorCode = GetLastError();
hResult = CO_E_SERVER_START_TIMEOUT;
if (m_CommandParam.OnCmdEvent)
m_CommandParam.OnCmdEvent(&m_CommandParam, CO_E_SERVER_START_TIMEOUT, "");
break;
}
// 预览管道中数据的内容
if (!PeekNamedPipe(m_hPipeRead, NULL, 0, NULL, &dwReadLen, NULL)
|| dwReadLen <= 0)
{
m_dwErrorCode = GetLastError();
hResult = E_FAIL;
break;
}
else
{
ZeroMemory(m_szPipeOut, sizeof(m_szPipeOut));
// 读取管道中的数据
if (ReadFile(m_hPipeRead, m_szPipeOut, dwReadLen, &dwStdLen, NULL))
{
hResult = S_OK;
if (m_CommandParam.OnCmdEvent)
m_CommandParam.OnCmdEvent(&m_CommandParam, S_OK, m_szPipeOut);
break;
}
else
{
m_dwErrorCode = GetLastError();
break;
}
}
} while (0);
if (m_processInfo.hThread)
{
CloseHandle(m_processInfo.hThread);
m_processInfo.hThread = NULL;
}
if (m_processInfo.hProcess)
{
CloseHandle(m_processInfo.hProcess);
m_processInfo.hProcess = NULL;
}
return hResult;
}
4、测试
针对以上的封装做出测试,就使用dir命令来测试好了。以下文件为main函数所在文件:
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#include <string>
#include "CmdHandler.h"
#define SYSTEM_PAUSE system("pause")
void OnCommandEvent(const CHCmdParam* pParam, HRESULT hResultCode, char* szResult);
int main(int argc, TCHAR** argv)
{
CHCmdParam cmdParam;
CCmdHandler cmdHandler;
HRESULT cmdResult = S_OK;
ZeroMemory(&cmdParam, sizeof(cmdParam));
cmdParam.iSize = sizeof(CHCmdParam);
// 这里测试F磁盘Test目录下,命令格式为 cmd.exe /C + 命令
TCHAR* szCmd = _T("cmd.exe /C dir F:\\Test&& echo S_OK || echo E_FAIL");
_tcscpy_s(cmdParam.szCommand, szCmd);
cmdParam.OnCmdEvent = OnCommandEvent;
cmdParam.iTimeOut = 3000;
cmdResult = cmdHandler.Initalize();
if (cmdResult != S_OK)
{
printf("cmd handler 初始化失败\n");
SYSTEM_PAUSE;
return 0;
}
cmdResult = cmdHandler.HandleCommand(&cmdParam);
if (cmdResult != S_OK)
{
printf("cmd handler 执行命令接口调用失败\n");
cmdHandler.Finish();
SYSTEM_PAUSE;
return 0;
}
system("pause");
return 0;
}
void OnCommandEvent(const CHCmdParam* pParam, HRESULT hResultCode, char* szResult)
{
if (!szResult || !szResult[0]) return;
if (!pParam || hResultCode != S_OK) return;
printf("============== 回调 ==============\n");
std::string echo_data(szResult);
std::string s_ok("S_OK");
std::string::size_type pos = echo_data.find(s_ok);
if (pos != std::string::npos)
printf("命令执行成功\n");
else
printf("命令执行失败\n");
printf("执行返回的结构:\n");
printf("========================================\n");
printf("%s\n", szResult);
}
在本机上运行以上代码,测试结果为:
可看到打印的测试结果后跟后缀“S_OK”。
注意:
1、在ExecuteCmdWait()函数中,WaitForSingleObject那一行传入了一个默认的超时时间至少在3秒以上。其实对于有些cmd例如打开记事本的"notepad",则只有记事本退出时,WaitForSingleObject()函数才返回,因此有必要将超时时间设置成INFINITE,否则ExecuteCmdWait()会返回超时;
2、传入的cmdParam中的OnCmdEvent()回调不可阻塞,否则ExecuteCmdWait()将无法返回;
3、针对2,如果需要可随意对实现代码进行修改,在不同的线程中执行。