最近接手一个小项目,要求使用谷歌的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;
}

这样,我们就可以拿到子进程输出结果并加以分析。我这儿简单处理了下,就输出来。也算善始善终吧。
       附上工程。

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

  1. Bash中的标准输入、标准输出和标准错误输出

    原理 每个程序在运行后,都会至少打开三个文件描述符,分别是0:标准输入:1:标准输出:2:标准错误. 例如,对于test.sh脚本,我们通过下面的步骤看到它至少打开了三个文件描述符: ./test.s ...

  2. 如何理解Linux shell中的“2>1”(将文件描述2(标准错误输出)的内容重定向到文件描述符1(标准输出))(尼玛>符号竟然不支持搜索,害我搜搜不到,只能搜)

    文章目录 前言 有何妙用 如何理解 总结 前言 有时候我们常看到类似这样的脚本调用: ./test.sh > log.txt 2>&1 这里的2>&1是什么意思?该如 ...

  3. vue怎么调用子元素的方法_vue 父组件中调用子组件函数的方法

    vue 父组件中调用子组件函数的方法 在父组件中调用子组件的方法: 1.给子组件定义一个ref属性.eg:ref="childItem" 2.在子组件的methods中声明一个函数 ...

  4. vue 父刷新子_vue.js从父级中更新子组件数据

    如何从父组件中更新子组件中的数据?我正在尝试从父级中更新autores属性,并让它更新子级数据.目前什么都没有发生,我认为我没有正确的数据链接.如果我将它作为数据添加到父组件中,那么当代码运行时,父组 ...

  5. 从父组件中获取子组件的值

    父组件中获取子组件中的值(三种的方式) 1. this.$root.子组件中的值 2. this.$parent.子值 $parent是一级一级访问的,如果子组件多的话需要多个$parent3. &l ...

  6. 默认标准错误文件linux,Linux中标准输出和标准错误的重导向

    若是一个命令须要长时间在服务器上运行,那么不少时候会用到nohup命令,这时即使远程登陆ssh中断了与服务器的联系,那么在服务器上运行的命令也不会所以而被迫中止.shell 一般状况下,nohup与& ...

  7. vue 父组件中调用子组件的方法

    Vue项目中如何在父组件中直接调用子组件的方法: 方案一:通过ref直接调用子组件的方法: //父组件中<template><div><Button @click=&qu ...

  8. vue父组件中调用子组件的方法

    (1)通过ref直接调用子组件的方法 //父组件中<template><div><Button @click="handleClick">点击调 ...

  9. vue父组件中调用子组件中的方法 及vue父组件调用孙子组件的方法

    一:通过ref直接调用子组件的方法: 子组件child.vue: <template><div>我是子组件</div> </template> < ...

最新文章

  1. 计算机原理说课教案,《 计算机组成原理 》 说课
  2. java web服务与配置,Java Web远端服务器的配置与部署
  3. [原创]微软网络协议数据分析工具 Microsoft Network Monitor 介绍
  4. Win10下Wireshark找不到网卡的方法
  5. 2015 11月30日 一周工作计划与执行
  6. 7.微服务:从设计到部署 --- 重构单体应用为微服务
  7. 微信 html5商城,微信公众号H5商城源码
  8. mysql 初始化序列_GitHub - IMAUZS/seq: 基于mysql的序列号生成器
  9. JQuery提交表单form
  10. 偶极子天线参数优化记录
  11. SCI、EI、ISTP收录检索技巧及核心期刊投稿导引
  12. “威海海鲜官方旗舰店”揭牌;每日黑巧新一代健康巧克力;麦咖啡全国门店秋季新品 | 食品饮料新品...
  13. 从faces_glintasia的.rec数据集转换为jpg图片
  14. JS原生---歌词滚动效果案例
  15. QQ看点模块(初稿截图)
  16. java并发编程之再学习
  17. gcc -Wl,--wrap,malloc 替换系统函数
  18. 使用 Apache ECharts 实现圣都装饰的延期日历图
  19. 手动清除2345流氓主页小记录以及对过去的一些回忆
  20. 历史记录画笔 photoshop基础教程-历史记录画笔工具

热门文章

  1. Open3D点云处理算法最全合集
  2. PCL基础3:点云程序运行时间计时
  3. 奶牛排序 cow sort 置换群
  4. CloudCompare基础架构介绍(PPT)
  5. 【点云论文速读】点云高质量3D表面重建
  6. 参考WebStorm设置VSCode“转到编辑器中的符号”快捷键为Shift双击(这是一个频繁使用的快捷键)
  7. div渐变遮罩效果:纵向和水平反向渐变遮罩效果,让戛然而止的页面多一丝丝淡淡的过渡效果,温柔中透露着一缕缕优雅...
  8. 解决笔记本集成声卡在某些情况下视频、语音没有声音
  9. 在CentOS 6.3 64bit上安装ATS 5.3 LTS版本并测试
  10. C语言的单链表创建:头插法/尾插法