当前位置: 首页 > 编程日记 > 正文

如何在父进程中读取子(外部)进程的标准输出和标准错误输出结果

最近接手一个小项目,要求使用谷歌的aapt.exe获取apk软件包中的信息。依稀记得去年年中时,有个同事也问过我如何获取被调用进程的输出结果,当时还研究了一番,只是没有做整理。今天花点时间,将该方法整理成文。(转载请指明出于breaksoftware的csdn博客)

在信息化非常发达的今天,可能已经过了江湖“武侠”草莽的时代。仅凭一己之力想完成惊人的创举,可谓难上加难。于是社会分工越来越明确:你擅长写驱动,你就去封装个驱动出来;他擅长写界面,就让他写套界面出来。如果你非常好心,可以将自己的研究成果开源,那么可能会有千万人受益。如果你想保持神秘感,但是还是希望别人可以分享你的成果,你可能会将模块封装出来供别人使用。比如你提供了一个DLL文件和调用方法样例。但是,实际情况并不是我们想的那么简单。比如我文前提到的问题:别人提供了一个Console控制台程序,我们将如何获取其执行的输出结果呢?这个问题,从微软以为为我们考虑过了,我们可以从一个API中可以找到一些端倪——CreateProcess。

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
);

做Windows开发的同学对CreateProcess这个API应该非常眼熟,也应该经常调用过。但是仔细研究过这个API每个参数的同学应该不会太多吧。这个API的参数非常多,我想我们工程中对CreateProcess的调用可能就关注于程序路径(lpApplicationName),或者命令行(lpCommandLine)。而其他参数我们可能就保守的选择了NULL。(遥想2年前,我就是在这个API上栽了一个大大的跟头。)

本文,我们将关注一个可能很少使用的参数lpStartupInfo。它是我们启动子进程时,控制子进程启动方式的参数。其结构体是STARTUPINFO

typedef struct _STARTUPINFO {DWORD  cb;LPTSTR lpReserved;LPTSTR lpDesktop;LPTSTR lpTitle;DWORD  dwX;DWORD  dwY;DWORD  dwXSize;DWORD  dwYSize;DWORD  dwXCountChars;DWORD  dwYCountChars;DWORD  dwFillAttribute;DWORD  dwFlags;WORD   wShowWindow;WORD   cbReserved2;LPBYTE lpReserved2;HANDLE hStdInput;HANDLE hStdOutput;HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;

粗看该结构体,我们可以知道:我们可以通过它控制子窗口出现的位置和大小还有显示方式。但是细看下它最后三个参数:StdInput、StdOutput和StdError。这三个参数似乎就点中了标题中的两个关键字“标准输出”、“标准错误输出”。是的!我们正是靠这几个参数来解决我们所遇到的问题。那么如何使用这些参数呢?
我们选用的还是老方法——管道。
BOOL ExecDosCmd(const CString& cstrCmd, char** ppBuffer)
{    HANDLE hRead = NULL;HANDLE hWrite = NULL;SECURITY_ATTRIBUTES sa;sa.nLength = sizeof(SECURITY_ATTRIBUTES);sa.lpSecurityDescriptor = NULL;// 新创建的进程继承管道读写句柄sa.bInheritHandle = TRUE;if ( FALSE == CreatePipe( &hRead, &hWrite, &sa, 0 ) )  {return FALSE;} if ( NULL == hRead || NULL == hWrite ) {return FALSE;}
这儿我们创建一个管道,该管道提供两个句柄:hRead和hWrite。我们之后将hWrite交给我们创建的子进程,让它去将信息写入管道。而我们父进程,则使用hRead去读取子进程写入管道的内容。此处要注意的就是将SECURITY_ATTRIBUTES对象的bInheritHandle设置为TRUE,这样我们获取的两个操作管道的句柄就有可继承属性。为什么需要可继承属性,我们会在之后说明。
创建好管道后,我们将着手准备创建进程
    // 组装命令CString cstrNewDosCmd = L"Cmd.exe /C ";cstrNewDosCmd += cstrCmd;// 设置启动程序属性,将STARTUPINFO si;si.cb = sizeof(STARTUPINFO);GetStartupInfo(&si); si.hStdError = hWrite;            // 把创建进程的标准错误输出重定向到管道输入si.hStdOutput = hWrite;           // 把创建进程的标准输出重定向到管道输入si.wShowWindow = SW_HIDE;// STARTF_USESHOWWINDOW:The wShowWindow member contains additional information.// STARTF_USESTDHANDLES:The hStdInput, hStdOutput, and hStdError members contain additional information.si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;PROCESS_INFORMATION pi; // 启动进程BOOL bSuc = CreateProcess(NULL, cstrNewDosCmd.GetBuffer(), NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi);cstrNewDosCmd.ReleaseBuffer();
此处我们要注意几个点:
  • “Cmd..exe /C” 我们使用CMD运行我们代理的程序。注意,我们启动的是CMD,而不是我们传入的文件路径。关于CMD命令的说明如下:

  • 设置标准输出和标准错误输出句柄
    si.hStdError = hWrite;            // 把创建进程的标准错误输出重定向到管道输入si.hStdOutput = hWrite;           // 把创建进程的标准输出重定向到管道输入
  • 隐藏CMD控制台
    si.wShowWindow = SW_HIDE;
  • 设置有效属性
    si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
这两个有效属性要设置。我们设置STARTF_USESHOWWINDOW的原因是:我们要控制CMD窗口不出现,所以我们修改了wShowWindow属性。我们使用STARTF_USESTDHANDLES的原因是:我们使用了标准输出和标准错误输出句柄。此处我们还要特别将一下STARTF_USESTDHANDLES属性的说明,我们看MSDN有如下描述
If this flag is specified when calling one of the process creation functions, the handles must be inheritable and the function's bInheritHandles parameter must be set to TRUE. 
也就是说,我们设置的这些句柄要有可继承性。这就解释了我们之前为什么在创建管道时要将句柄可继承性设置为TRUE的原因。
        一般来说,我们要代理的程序已经输入好信息了。我们要关闭写管道
    if ( NULL != hWrite ) {CloseHandle(hWrite);hWrite = NULL;}
之后便是读取管道信息。我想应该有人借用过网上相似的代码,但是却发现一个问题,就是读取出来的信息是不全的。这个问题的关键就在读取的方法上,其实没什么玄妙,只要控制好读取起始位置就行了。
    // 先分配读取的数据空间DWORD dwTotalSize = NEWBUFFERSIZE;                     // 总空间char* pchReadBuffer = new char[dwTotalSize];memset(pchReadBuffer, 0, NEWBUFFERSIZE);DWORD dwFreeSize = dwTotalSize;                 // 闲置空间do {if ( FALSE == bSuc ) {break;}// 重置成功标志,之后要视读取是否成功来决定bSuc = FALSE;char chTmpReadBuffer[NEWBUFFERSIZE] = {0};DWORD dwbytesRead = 0; // 用于控制读取偏移OVERLAPPED Overlapped;memset(&Overlapped, 0, sizeof(OVERLAPPED) );while (true) {   // 清空缓存memset(chTmpReadBuffer, 0, NEWBUFFERSIZE);// 读取管道BOOL bRead = ReadFile( hRead, chTmpReadBuffer, NEWBUFFERSIZE, &dwbytesRead, &Overlapped );DWORD dwLastError = GetLastError();if ( bRead ) {if ( dwFreeSize >= dwbytesRead ) {// 空闲空间足够的情况下,将读取的信息拷贝到剩下的空间中memcpy_s( pchReadBuffer + Overlapped.Offset, dwFreeSize, chTmpReadBuffer, dwbytesRead );// 重新计算新空间的空闲空间dwFreeSize -= dwbytesRead;}else {// 计算要申请的空间大小DWORD dwAddSize = ( 1 + dwbytesRead / NEWBUFFERSIZE ) * NEWBUFFERSIZE;// 计算新空间大小DWORD dwNewTotalSize = dwTotalSize + dwAddSize;// 计算新空间的空闲大小dwFreeSize += dwAddSize;// 新分配合适大小的空间char* pTempBuffer = new char[dwNewTotalSize];// 清空新分配的空间memset( pTempBuffer, 0, dwNewTotalSize );// 将原空间数据拷贝过来memcpy_s( pTempBuffer, dwNewTotalSize, pchReadBuffer, dwTotalSize );// 保存新的空间大小dwTotalSize = dwNewTotalSize;// 将读取的信息保存到新的空间中memcpy_s( pTempBuffer + Overlapped.Offset, dwFreeSize, chTmpReadBuffer, dwbytesRead );// 重新计算新空间的空闲空间dwFreeSize -= dwbytesRead;// 将原空间释放掉delete [] pchReadBuffer;// 将原空间指针指向新空间地址pchReadBuffer = pTempBuffer;}// 读取成功,则继续读取,设置偏移Overlapped.Offset += dwbytesRead;}else{if ( ERROR_BROKEN_PIPE == dwLastError ) {bSuc = TRUE;}break;}}} while (0);
因为读取的信息量是不确定的,所以我段代码动态申请了一段内存,并根据实际读取出来的结果动态调整这块内存的大小。这段注释写的很清楚了,我就不再赘述。
        善始善终,最后代码处理是
    if ( NULL != hRead ) {CloseHandle(hRead);hRead = NULL;}if ( bSuc ) {*ppBuffer = pchReadBuffer;}else {delete [] pchReadBuffer;pchReadBuffer = NULL;}return bSuc;
}
这个函数传入了一个指向指针的指针用于外部获取结果,外部一定要释放这段空间以免造成内存泄露。
#define NEWBUFFERSIZE 0x100
#define EXECDOSCMD L"aapt.exe"
int _tmain(int argc, _TCHAR* argv[])
{char* pBuffer = NULL;WCHAR wchFilePath[MAX_PATH] = {0};DWORD dwSize = MAX_PATH - 1;if ( FALSE == GetModuleFileName(NULL, wchFilePath, dwSize) ) {return -1;}CString cstrFilePath = wchFilePath;int nIndex = cstrFilePath.ReverseFind('\\');if ( nIndex == -1 ) {return -1;}cstrFilePath = cstrFilePath.Left(nIndex + 1);cstrFilePath += EXECDOSCMD;cstrFilePath += L"\"";cstrFilePath = L"\"" + cstrFilePath;if ( ExecDosCmd( cstrFilePath, &pBuffer ) &&NULL != pBuffer ) {CString cstrBuffer = CA2W(pBuffer, CP_UTF8);delete [] pBuffer;wprintf(L"%s", cstrBuffer);}return 0;
}
这样,我们就可以拿到子进程输出结果并加以分析。我这儿简单处理了下,就输出来。也算善始善终吧。
       附上工程。

相关文章:

Ruby环境的安装(In Ubuntu 7.10)

今天开始学习Ruby。准备的图书呢,就是《Programming Ruby - 2nd》。为了准备一个实验的环境,于是要给我的Ubuntu上安装Ruby的环境。1、安装解释器:sudo apt-get install ruby 2、安装一个即时执行工具irb:由于第一部安装的结果…

基于animation.css实现动画旋转特效

分享一款基于animation.css实现动画旋转特效。这是一款基于CSS3实现的酷炫的动画旋转特效代码。效果图如下&#xff1a; 在线预览 源码下载 实现的代码。 html代码&#xff1a; <div class"wrap"><div class"mod_bg"><div class"bg…

VC:CString用法整理(转载)

1.CString::IsEmpty BOOL IsEmpty( ) const; 返回值&#xff1a;如果CString 对象的长度为0&#xff0c;则返回非零值&#xff1b;否则返回0。 说明&#xff1a;此成员函数用来测试一个CString 对象是否是空的。 示例&#xff1a; 下面的例子说明了如何使用CString::IsEmp…

WMI技术介绍和应用——查询本地用户和组

本文使用了《 WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类》中代码做为基础。本节只是列出了WQL语句&#xff0c;具体使用参看前面的例子。&#xff08; 转载请指明出于breaksoftware的csdn博客&#xff09;本文主要介绍Win32_Group和Win32_UserAccount类。 如何…

AAAI 2020 | 时间可以是二维的吗?基于二维时间图的视频内容片段检测

作者 | 彭厚文、傅建龙来源 | 微软研究院AI头条编者按&#xff1a;当时间从一维走向二维&#xff0c;时序信息处理问题中一种全新的建模思路由此产生。根据这种新思路及其产生的二维时间图概念&#xff0c;微软亚洲研究院提出一种新的解决时间定位问题的通用方法&#xff1a;二…

UITextField的代理方法

- (void)textFieldDidBeginEditing:(UITextField *)textField 当textField开始编辑的时候调用&#xff0c;可用完成如下的需求&#xff1a;点击textField可以使页面往上滑动 必须加上这句代码 [UIView animateWithDuration:.25 animations:^{ _scrollView.contentOffset CGPo…

以金山界面库(openkui)为例思考和分析界面库的设计和实现——问题

随着物质生活的丰富&#xff0c;人们的精神生活也越来越丰富。人们闲暇的时间也相对变多&#xff0c;于是很多人就开始寻找打发时间的方法。其中电视便是其中一种非常重要的消遣方式。假如我们打开电视机&#xff0c;看到了一个电视台正在播一部我们之前没看过的&#xff0c;正…

知识图谱,下一代数据中台的核心技术

作者 | 杨威&#xff0c;明略科技技术中心负责人 编辑 | 夕颜 出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09; 本文为CSDN即将推出的《新战场&#xff1a;决胜中台》专刊的第 3 篇文章。 【导读】数据中台火的十分突然&#xff0c;似乎年前还在炒概念&#xff0c;年…

StoneAge Dict 技术方案的可行性[1]

上次和项目经理讨论了技术选型的事&#xff0c;现在简单地说一下&#xff0c;等下次开会再系统地讲。1. 平台环境&#xff1a;Java1.52. 用户接口&#xff1a;Web: JSP, JSFSpring2.5xDesktop: JavaFX3. 技术&#xff1a;dictzip(解析StarDict词库), XML因为StarDict是目前很受…

using的几种用途

using 常用来引用命名空间 1 using System; 2 using System.Data; 3 using System.Data.SqlClient; 4 using System.Collections.Generic; 5 using Model; 6 using IDAL; 7 using DBUnititly; using另一个用途是给类和命名空间指定别名 1 using spacename system.io2 3 using…

以金山界面库(openkui)为例思考和分析界面库的设计和实现——资源读取模块分析

按照软件的执行流程&#xff0c;我们首先遇到《以金山界面库&#xff08;openkui&#xff09;为例思考和分析界面库的设计和实现——问题》中提出的最后一个问题&#xff1a;界面描述文件的放置位置。我们曾提出一种方案&#xff1a;将界面描述文件打包后放在资源文件中&#x…

开发者如何赶上5G风口?

随着5G正式步入商用&#xff0c;5G 技术引发广泛关注。据信息通信研究院《5G经济社会影响白皮书》预测&#xff0c;2030年&#xff0c;5G将直接带动的总产出、经济增加值、就业机会分别为6.3万亿元、2.9万亿元和800万个。据BOSS直聘 《2019年5G相关人才数据观察》报告指出&…

使用C# 3.0编译器编译 Asp.Net 项目代码

只需要在 web.config 里添加这样的一段设置就OK了:<configuration><system.codedom><compilers><compiler language"c#;cs;csharp"extension".cs"type"Microsoft.CSharp.CSharpCodeProvider,System, Version2.0.0.0, Cultureneu…

java 它 引用(基本类型的包装,构造函数和析构函数c++不同)

一个&#xff1a;java 和c参考控制 他提到引用&#xff0c;我们会想到java它不喜欢c里面的指针。当然java内引用和c里面的引用是不同的。 比如&#xff1a; 比方C中&#xff0c;我对某一个函数的声明。int a(int &b)&#xff0c;b即为引用类型&#xff0c;函数内b的改动能够…

使用程序解决一道逻辑推理题

今天看朋友发了一个老问题&#xff0c;一道很有意思的推理题&#xff1a;&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 小明和小强都是张老师的学生&#xff0c;张老师的生日是M月N日&#xff0c;2人都知道张老师的生日是下列10组中的一天&#xff1a; 3月4…

AjaxControlToolKit之DragPanelExtender用法

1、将控件ToolkitScriptManager拖至页面中...2、定义3个Panel&#xff0c;用于实现窗体拖动效果&#xff0c;代码如下&#xff1a;1<body>2<form id"form1"runat"server">3<div>4<cc1:ToolkitScriptManager ID"ToolkitScriptMan…

自带数据线的迷你数显充电宝,旅途必备

还有20多天就过年了有件极其考验情商的事情也来临了就是我们这群90后过年最怕的事情——相亲但是在尴尬的场合手机可是一个缓解气氛的好东西不管是想要选择看电影&#xff0c;还是找附近的游玩只要有手机&#xff0c;就可以从容不迫的应对但是带手机最尴尬的事情莫过于结账的时…

SpringJDBC的简单应用

此处写上应用JdbcTemplate的dao操作数据库的一些代码&#xff08;含基本的增删改查&#xff0c;注&#xff1a;重点是查询出多条语句的写法&#xff09;&#xff1a; package org.sakaiproject.zhaorui.dao.impl;import java.sql.ResultSet;import java.sql.SQLException;impor…

WMI技术介绍和应用——查询硬件信息

这个月实在太忙了&#xff0c;一直没有时间去继续写WMI的应用例子。 本来是希望将《WMI技术介绍和应用》系列博文写的像WMI百科全书般&#xff0c;但是貌似对这个技术感兴趣的同学并不多&#xff0c;所以我决定对部分知识点点到为止&#xff0c;有需求的同学可以查询MSDN相关类…

微软开源的自动机器学习工具上新了:NNI概览及新功能详解

作者 | 宋驰来源 | 微软研究院AI头条&#xff08;ID: MSRAsia&#xff09;2018年9月&#xff0c;微软亚洲研究院发布了第一版 NNI (Neural Network Intelligence) &#xff0c;目前已在 GitHub 上获得 3.8K 星&#xff0c;成为最热门的自动机器学习&#xff08;AutoML&#xff…

10624 - Super Number

题目链接 题意&#xff1a;给出n到m的范围&#xff0c;求出一个数在前i位数组成的数字能被i整除。假设存在输出这个数&#xff0c;假设不存在。输出-1. 思路&#xff1a;回溯&#xff0c;每次放第i位&#xff0c;然后推断是否符合题意。这题踩着时间过去的2.6s&#xff08;看了…

2008找回企业久违的网速

曾几何时&#xff0c;单位上网访访问页面也是忽忽的&#xff0c;等待10秒简直是不可忍受&#xff1b;曾几何时&#xff0c;公司网络下载是嗖嗖的&#xff0c;转眼已是2M开外&#xff1b;曾几何时&#xff0c;办公室上网看视频是杠杠的&#xff0c;那流畅那画面都快赶上电视直播…

发现一个windows7(32bit或64bit)DirectUI的bug

前段时间发现一个windows7的一个bug&#xff0c;不是什么严重的问题&#xff0c;我在此记录下。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 重现步骤如下: 0 在文件夹的“更改您的视图”中选择下图中用红色叉叉标记的项 1 新建一个文件夹名为“Cs" 2…

阿里达摩院2020趋势第一弹:感知智能的“天花板”和认知智能的“野望”

作者 | Just出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;“感知智能与认知智能是相辅相成的关系。认知智能需要感知系统来进行信号处理和概念识别&#xff0c;而感知系统也需要认知系统的反馈来决定如何进行更有效的提取和识别。”1月2日&#xff0c;阿里巴巴达摩…

Java 对synchronized的补充Lock锁

Java并发编程&#xff1a;Lock 从Java 5之后&#xff0c;在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问&#xff0c;那就是Lock。 也许有朋友会问&#xff0c;既然都可以通过synchronized来实现同步访问了&#xff0c;那么为什么还需要提供Lock&#xff1…

有奖评选 | 2020年的AI技术公开课,你想听到哪些干货?

CSDN技术公开课有奖评选开始啦~~听过课的小伙伴们&#xff0c;哪位讲师的分享让你获益匪浅&#xff1f;记得给TA投票哦&#xff01;投票后获取入群方式&#xff0c;参与抽奖&#xff0c;奖品很丰厚哦~~进入付费时代&#xff0c;如今我们看似只要招招手&#xff0c;一切知识随手…

一个分析“文件夹”选择框实现方法的过程

在软件开发中&#xff0c;我们如果存在“导入导出”的场景时&#xff0c;难免会用到“文件夹”选择框。之前一直没有太关注过这个的实现过程。最近在工作中遇到了一些问题&#xff0c;我做了一些研究。在此记录下研究的过程。&#xff08;转载请指明出于breaksoftware的csdn博客…

Openssl req命令

一、简介 req指令用来创建和处理PKCS#10格式的证书 二、语法 openssl req [-inform PEM|DER] [-outform PEM|DER] [-in filename] [-out filename] [-text] [-pubkey] [-noout] [-verify] [-modulus] [-nodes] [-subject] [-passin arg] [-passout arg] [-key filename] [-key…

使用windbg抓取崩溃文件和分析的过程

在软件编程中&#xff0c;崩溃的场景比较常见的。且说微软技术再牛X&#xff0c;也是会出现崩溃的场景。网上有一段Win98当着比尔盖茨蓝屏的视频非常有意思。 &#xff08;转载请指明出于breaksoftware的csdn博客&#xff09;我们身边的很多软件都引入了dump生成和收集机制。但…

TF 2.1.0-rc2发布,2020年停止支持Python 2

作者 | 神经星星来源 | HyperAI超神经&#xff08;ID:HyperAI&#xff09;【导读】2020 年 1 月 1 日&#xff0c;Python 2 停止维护&#xff0c;正式退休。Python 3 全面登场的时刻&#xff0c;TensorFlow 也在悄悄改变。近日 TensorFlow 官方 GitHub 账号中&#xff0c;发布了…