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
创建一个新进程及其主线程。新进程在调用进程的安全上下文中运行。
如果调用进程模拟另一个用户,则新进程使用调用进程的令牌,而不是模拟令牌。若要在模拟令牌表示的用户的安全上下文中运行新进程,请使用CreateProcessAsUser或CreateProcessWithLogonW功能。
微软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的一些小问题吧,此文就到此结束。
版权声明:转载请注明出处,谢谢!