上一题下一题
跳转到
 
 
  世界如此多姿,发展如此迅速,窥一斑未必还能知全豹。但正如万花筒一样,每一个管窥都色彩斑斓。  
 
 
  知识通道 | 学习首页 | 教师首页 | PK首页 | 知识创造首页 | 企业首页 | 登录
 
本文对应知识领域
如何在VB中截获shell程序的输出
作者:硅谷动力 申领版权
2010年11月15日 共有 843 次访问 【添加到收藏夹】 【我要附加题目
受欢迎度:

    【简  介】
    在Windows环境下的所谓shell程序就是dos命令行程序,比如VC的CL.exe命令行编译器,JDK的javac编译器,启动java程序用的java.exe都是标准的shell程序。截获一个shell程序的输出是很有用的,比如说您可以自己编写一个IDE(集成开发环境),当用户发出编译指令时候,你可以在后台启动shell 调用编译器并截获它们的输出,对这些输出信息进行分析后在更为友好的用户界面上显示出来。为了方便起见,我们用VB作为本文的演示语言。
    
    
    
    在Windows环境下的所谓shell程序就是dos命令行程序,比如VC的CL.exe命令行编译器,JDK的javac编译器,启动java程序用的java.exe都是标准的shell程序。截获一个shell程序的输出是很有用的,比如说您可以自己编写一个IDE(集成开发环境),当用户发出编译指令时候,你可以在后台启动shell 调用编译器并截获它们的输出,对这些输出信息进行分析后在更为友好的用户界面上显示出来。为了方便起见,我们用VB作为本文的演示语言。
    
    通常,系统启动Shell程序时缺省给定了3个I/O信道,标准输入(stdin), 标准输出stdout, 标准错误输出stderr。之所以这么区分是因为在早期的计算机系统如PDP-11的一些限制。那时没有GUI, 将输出分为stdout,stderr可以避免程序的调试信息和正常输出的信息混杂在一起。
    
    通常, shell程序把它们的输出写入标准输出管道(stdout)、把出错信息写入标准错误管道(stderr)。缺省情况下,系统将管道的输出直接送到屏幕,这样一来我们就能看到应用程序运行结果了。
    
    为了捕获一个标准控制台应用程序的输出,我们必须把standOutput和standError管道输出重定向到我们自定义的管道。
    
    下面的代码可以启动一个shell程序,并将其输出截获。
    程序代码:
    '执行并返回一个命令行程序(shell程序)的标准输出和标准错误输出
    '通常命令行程序的所有输出都直接送到屏幕上
    Private Function ExecuteApp(sCmdline As String) As String
    Dim proc As PROCESS_INFORMATION, ret As Long
    Dim start As STARTUPINFO
    Dim sa As SECURITY_ATTRIBUTES
    Dim hReadPipe As Long '负责读取的管道
    Dim hWritePipe As Long '负责Shell程序的标准输出和标准错误输出的管道
    Dim sOutput As String '放返回的数据
    Dim lngBytesRead As Long, sBuffer As String * 256
    sa.nLength = Len(sa)
    sa.bInheritHandle = True
    ret = CreatePipe(hReadPipe, hWritePipe, sa, 0)
    If ret = 0 Then
    MsgBox "CreatePipe failed. Error: " & Err.LastDllError
    Exit Function
    End If
    start.cb = Len(start)
    start.dwFlags = STARTF_USESTDHANDLES Or STARTF_USESHOWWINDOW ' 把标准输出和标准错误输出重定向到同一个管道中去。
    start.hStdOutput = hWritePipe
    start.hStdError = hWritePipe
    start.wShowWindow = SW_HIDE '隐含shell程序窗口
    ' 启动shell程序, sCmdLine指明执行的路径
    ret = CreateProcessA(0&, sCmdline, sa, sa, True, NORMAL_PRIORITY_CLASS, 0&, 0&, start, proc)
    If ret = 0 Then
    MsgBox "无法建立新进程,错误码:" & Err.LastDllError
    Exit Function
    End If ' 本例中不必向shell程序送信息,因此可以先关闭hWritePipe
    CloseHandle hWritePipe ' 循环读取shell程序的输出,每次读取256个字节。
    Do
    ret = ReadFile(hReadPipe, sBuffer, 256, lngBytesRead, 0&)
    sOutput = sOutput & Left$(sBuffer, lngBytesRead)
    Loop While ret <> 0 ' 如果ret=0代表没有更多的信息需要读取了
    ' 释放相关资源
    CloseHandle proc.hProcess
    CloseHandle proc.hThread
    CloseHandle hReadPipe
    ExecuteApp = sOutput ' 输出结果
    End Function
    
    我对这个程序进行一些解释。
    ret = CreatePipe(hReadPipe, hWritePipe, sa, 0)
    
    大家可以看到,首先我们建立一个匿名管道。该匿名管道稍候将用来取得与被截获的应用程序的联系。其中hReadPipe用来获取shell程序的输出,而hWritePipe可以用来向应用程序发送信息。如同现实世界中的水管一样,水从管道的一端流进从另一端流出。您把水想象为信息,水管就是匿名管道,这样一来就很好理解这段程序了。
    然后就是设置shell应用程序的初始属性。 Dwflags可以指示系统在创建新进程时新进程使用了自定义的wShowWindow, hStdInput,hStdOutput和hStdError。(windows显示属性,标准输入,标准输出,标准错误输出。)
    再把shell应用程序的标准输出和标准错误输出都定向到我们预先建好的管道中。
    代码如下:
    程序代码:
    start.dwFlags = STARTF_USESTDHANDLES Or STARTF_USESHOWWINDOW
    start.hStdOutput = hWritePipe
    start.hStdError = hWritePipe
    好,现在可以调用建立新进程的函数了:
    ret = CreateProcessA(0&, sCmdline, sa, sa, True, NORMAL_PRIORITY_CLASS, 0&, 0&, start, proc)
    然后,循环读管道里的数据直到无数据可读为止。
    Do
    ret = ReadFile(hReadPipe, sBuffer, 256, lngBytesRead, 0&) '每次读256字节
    sOutput = sOutput & Left$(sBuffer, lngBytesRead) '送入一个字符串中
    Loop While ret <> 0 '若 ret = 0 表明没有数据等待读取。
    
    然后,释放不用的资源。
    
    用法很简单:比如:
    MsgBox ExecuteApp("c:\windows\command\mem.exe)
    
    是很方便吧?
    不过,这些程序是在NT下的,如果要在95下实现还需要一点点改动。因为如果该函数调用一个纯win32的程序,没问题。可是95是16,win32混合的系统,当你试图调用一个16位的DOS应用程序那么,那么这个办法会导致相关进程挂起。因为这涉及到WindowsNT和Windows 95对shell的不同实现。
    在win95中,16位shell程序关闭时并不保证重定向的管道也关闭,这样,当你的程序试图读取一个已经关闭的shell程序的重定向管道时,你的程序就挂了。
    那么,有解决办法吗?回答是肯定的。
    解决办法就是用一个win32的应用程序作为您的应用程序和shell程序的中间人。中间人程序继承并重定向了主程序的输入输出,然后中间人程序启动指定的shell程序。该shell程序也就继承并重定向了主程序的输入输出。中间人程序一直等到shell程序结束才结束。
    当shell程序结束时,中间人程序也结束,同时因为中间人程序是一个win32程序,那么它就会关闭相应的重定向了管道。这样,你的程序可以发现管道已经关闭,便可以跳出循环。你的程序就不会挂起了。
    下面是相关的中间人程序C代码的实现:
    程序代码:
    #include
    void main (int argc, char *argv[])
    {
    BOOL bRet = FALSE;
    STARTUPINFO si = {0};
    PROCESS_INFORMATION pi = {0};
    // Make child process use this app's standard files. si.cb = sizeof(si);
    si.dwFlags = STARTF_USESTDHANDLES;
    si.hStdInput = GetStdHandle (STD_INPUT_HANDLE);
    si.hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
    si.hStdError = GetStdHandle (STD_ERROR_HANDLE);
    bRet = CreateProcess (NULL, argv[1], NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi );
    if (bRet)
    {
    WaitForSingleObject (pi.hProcess, INFINITE);
    CloseHandle (pi.hProcess);
    CloseHandle (pi.hThread);
    }
    }
    
    把该程序编译为conspawn.exe并放在系统可以调用到的路径目录中。
    然后把文章开头提到的代码中的CreateProcessA语句改为:
    ret = CreateProcessA(0&, "conspawn """ & sCmdline & """", sa, sa, True,
    NORMAL_PRIORITY_CLASS, 0&, 0&, start, proc)

    

 

相关新闻

您可能对这些感兴趣  

用VB做定时断线程序
VisualBasic中的界面设计原则和编程技巧
VB6.0与Windows API 间的呼叫技巧
制作可以自动隐藏的弹出式菜单
ListBox中的字符串超长显示的解决方法
VB中的Unicode 和 Ansi 格式
优化程序显示速度
Visual Basic 产生渐层的 Form 背景
用VB实现客户——服务器(TCP/IP)
用VB制作注册软件的方法

题目筛选器
日期:
类型:
状态:
得分: <=
分类:
作者:
职业:
关键字:
搜索

 
 
 
  焦点事件
 
  知识体系
 
  职业列表
 
 
  最热文章
 
 
  最多引用文章
 
 
  最新文章
 
 
 
 
网站介绍 | 广告服务 | 招聘信息 | 保护隐私权 | 免责条款 | 法律顾问 | 意见反馈
版权所有 不得转载
沪ICP备 10203777 号 联系电话:021-54428255
  帮助提示    
《我的太学》是一种全新的应用,您在操作中遇到疑问或者问题,请拨打电话13564659895,15921448526。
《我的太学》