【MFC】Windows 执行bat批处理并获取其执行结果

01、目录

02、此文背景故事

Cain 小熊是深圳市XXXXXX科技有限公司的一名职工,有一天,Beck老大叫Cain 小熊去办公室。

Beck老大:Cain 小熊,我们XXX版块的编译器还需要一个功能:支持输出编译结果,失败或者成功都会有执行的结果,你应该能办到吧?

Cain 小熊:当然可以(不敢说不可以)

Beck老大: 我们当前这里,前段时间你是用的bat批处理执行生成的,你要好好想想怎么把bat的结果输出出来,多余的信息我不想要,只需要核心编译:预处理、编译、汇编、链接这个过程的结果。

Cain 小熊: 放心,Beck老大,一定完成任务。

Beck老大: 嗯嗯,不错,那你先去忙吧!

出了办公室,Cain 小熊就四处找资料,怎么把批处理的结果得到,那个可是另起的一个线程。

直到他看见一篇bLog:管道技术实现重定向,他心中渐渐有了一丝希望,然后就参照管道技术实现重定向,做着做着,最后实现了此功能,现在Cain 小熊把他的体会与实践写出来,希望能够帮助更多的人。

下面根据上面文章浅谈一下两个重要的Window API。

03、CreatePipe 与 CreateProcess

3.1 CreatePipe

管 道(Pipe)实际是用于进程间通信的一段共享内存,创建管道的进程称为管道服务器,连接到一个管道的进程为管道客户机。一个进程在向管道写入数据后,另 一进程就可以从管道的另一端将其读取出来。匿名管道(Anonymous Pipes)是在父进程和子进程间单向传输数据的一种未命名的管道,只能在本地计算机中使用,而不可用于网络间的通信。

匿名管道: 未命名管道,只能用于本机间使用,父进程与子进程间使用。

第一:匿名管道只能实现本地进程之间的通信,不能实现跨网络之间的进程间的通信。
第二:匿名管道只能实现父进程和子进程之间的通信,而不能实现任意两个本地进程之间的通信。

创建管道函数原型:

BOOL WINAPI CreatePipe(
  _Out_    PHANDLE               hReadPipe,
  _Out_    PHANDLE               hWritePipe,
  _In_opt_ LPSECURITY_ATTRIBUTES lpPipeAttributes,
  _In_     DWORD                 nSize
);

Return Value:

如果函数成功,返回值不为零。
如果函数失败,返回值为零。要获取扩展错误信息,请调用GetLastError。

Remarks:
CreatePipe创建管道,将指定的管道大小分配给存储缓冲区。 CreatePipe还会在随后的ReadFile和WriteFile函数调用中创建该进程用于读取和写入缓冲区的句柄。
要从管道读取,一个进程在调用ReadFile函数时使用读取句柄。当以下任一条件为真时,ReadFile返回:写操作在管道的写入端完成,请求的字节数已被读取或发生错误。
当一个进程使用WriteFile写入匿名管道时,写操作在所有字节都被写入之前不会完成。如果在写入所有字节之前管道缓冲区已满,则WriteFile不会返回,直到另一个进程或线程使用ReadFile来创建更多的缓冲区空间。
使用具有唯一名称的命名管道实现匿名管道。因此,您经常可以将匿名管道的句柄传递给需要命名管道句柄的函数。
如果CreatePipe失败,输出参数的内容是不确定的。在这个事件中,不应该假设他们的内容。
要释放管道使用的资源,应用程序应该不再需要关闭句柄,这可以通过调用CloseHandle函数或与实例句柄关联的进程结束。请注意,管道的一个实例可能有多个与之关联的句柄。当命名管道的实例的最后一个句柄关闭时,管道的实例总是被删除。

3.2 CreateProcess

创建一个新进程及其主线程。新进程在调用进程的安全上下文中运行。

如果调用进程模拟另一个用户,则新进程使用调用进程的令牌,而不是模拟令牌。若要在模拟令牌表示的用户的安全上下文中运行新进程,请使用CreateProcessAsUserCreateProcessWithLogonW功能。

微软Win32 文档:

BOOL CreateProcessA(
  LPCSTR                lpApplicationName,
  LPSTR                 lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL                  bInheritHandles,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCSTR                lpCurrentDirectory,
  LPSTARTUPINFOA        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);
BOOL WINAPI CreateProcess(
  _In_opt_    LPCTSTR               lpApplicationName,
  _Inout_opt_ LPTSTR                lpCommandLine,
  _In_opt_    LPSECURITY_ATTRIBUTES lpProcessAttributes,
  _In_opt_    LPSECURITY_ATTRIBUTES lpThreadAttributes,
  _In_        BOOL                  bInheritHandles,
  _In_        DWORD                 dwCreationFlags,
  _In_opt_    LPVOID                lpEnvironment,
  _In_opt_    LPCTSTR               lpCurrentDirectory,
  _In_        LPSTARTUPINFO         lpStartupInfo,
  _Out_       LPPROCESS_INFORMATION lpProcessInformation
);

Return Value:

如果函数成功,则返回值为非零。
如果函数失败,则返回值为零。若要获取扩展错误信息,请调用GetLastError

注意,函数在进程完成初始化之前返回。如果无法找到所需的DLL或无法初始化,则进程将终止。若要获取进程的终止状态,请调用GetExitCodeProcess.

Remarks:

进程被分配一个进程标识符。标识符在进程终止之前是有效的。它可以用于标识进程,或在OpenProcess函数打开进程的句柄。进程中的初始线程也被分配一个线程标识符。可以在OpenThread函数打开线程的句柄。标识符在线程终止之前是有效的,并且可以用来唯一地标识系统中的线程。属性中返回这些标识符。PROCESS_INFORMATION结构。

04、程序小天地

根据上述需求,我将中间步骤封装为一个函数,下面贴出源程序:

CString CMainFrame::ExecuteBat(CString strCmd)
{
	SECURITY_ATTRIBUTES sa;
    HANDLE hRead,hWrite;
 
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;
    if (!CreatePipe(&hRead,&hWrite,&sa,0))
    {
        MessageBox("Error on CreatePipe()!");
        return NULL;
    }
	STARTUPINFO si={sizeof(si)};
    PROCESS_INFORMATION pi;
    si.hStdError = hWrite;
    si.hStdOutput = hWrite;
    si.wShowWindow = SW_HIDE;
    si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
	TCHAR* cmdline=StringToChar(strCmd);
    if (!CreateProcess(NULL,cmdline,NULL,NULL,TRUE,NULL,NULL,NULL,&si,&pi))
    {
        MessageBox("Error on CreateProcess()!");
        return NULL;
    }
    CloseHandle(hWrite);
 
    char buffer[4096];
    memset(buffer,0,4096);
	CString output;
    DWORD byteRead;
    while(true)
    {
        if (ReadFile(hRead,buffer,4095,&byteRead,NULL) == NULL)
        {
            break;
        }
        output += buffer;
    }
	return output;
}

上面程序中还有一个外部程序,源代码如下:

TCHAR* CMainFrame::StringToChar(CString& str)
{
	int len=str.GetLength();
	TCHAR* tr=str.GetBuffer(len);
	str.ReleaseBuffer();
	return tr; 
}

这个函数的作用是讲CString格式的命令转化为TCHAR格式

这段程序确实可以返回其bat文件的所有执行结果,但是,Beck老大说了,只要核心的那一段的结果。
OK,我们在写入bat的时候就将无用的(不需要返回的)命令前面加上@符号。
此处是Dos编程的知识,语句前加上@,告诉编译器,@后面的结果(命令)不需要返回显示了。

这样子,我们将得到的output返回出来,就是核心的返回结果啦。

到此为止,我们继续让程序美化,显示为跟VS编译器一样的编译界面。

CString strInfo = ExecuteBat(XXXXX.bat);
m_ErrPane.m_ErrDlg.ShowInfo("===============生成:项目:XXXX,配置:Debug,win32,正在编译.....===============");
m_ErrPane.m_ErrDlg.ShowInfo("\r\n[InFo:\r\n");
if(成功条件满足)
{
	m_ErrPane.m_ErrDlg.ShowInfo(strInfo);
	m_ErrPane.m_ErrDlg.ShowInfo("================生成:编译成功!================");
}else{
	m_ErrPane.m_ErrDlg.ShowInfo(strInfo);
	m_ErrPane.m_ErrDlg.ShowInfo("================生成:编译失败!================",RGB(255,0,0)); //显示为红色字体
}

OK,想必这份程序可以在Beck老大那里蒙混过关了吧!哈哈哈哈

05、小结

此文是与实际问题相关的一篇总结,或许知识点不多,只有两个API,但是,如果正巧你也遇到同样的问题,这篇文章一定可以给你启发式的效果。
如果文章中有什么地方不够完美,请大家指出,博主会及时纠正的。

好啦,后面再一起讨论关于MFC的一些小问题吧,此文就到此结束。

版权声明:转载请注明出处,谢谢!

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cain Xcy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值