怎么在VB中截獲shell程序的輸出
發(fā)表時(shí)間:2023-07-31 來(lái)源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]在Windows環(huán)境下的所謂shell程序就是dos命令行程序,比如VC的CL.exe命令行編譯器,JDK的javac編譯器,啟動(dòng)java程序用的java.exe都是標(biāo)準(zhǔn)的shell程序。截獲一個(gè)s...
在Windows環(huán)境下的所謂shell程序就是dos命令行程序,比如VC的CL.exe命令行編譯器,JDK的javac編譯器,啟動(dòng)java程序用的java.exe都是標(biāo)準(zhǔn)的shell程序。截獲一個(gè)shell程序的輸出是很有用的,比如說(shuō)您可以自己編寫一個(gè)IDE(集成開(kāi)發(fā)環(huán)境),當(dāng)用戶發(fā)出編譯指令時(shí)候,你可以在后臺(tái)啟動(dòng)shell 調(diào)用編譯器并截獲它們的輸出,對(duì)這些輸出信息進(jìn)行分析后在更為友好的用戶界面上顯示出來(lái)。為了方便起見(jiàn),我們用VB作為本文的演示語(yǔ)言。
通常,系統(tǒng)啟動(dòng)Shell程序時(shí)缺省給定了3個(gè)I/O信道,標(biāo)準(zhǔn)輸入(stdin), 標(biāo)準(zhǔn)輸出stdout, 標(biāo)準(zhǔn)錯(cuò)誤輸出stderr。之所以這么區(qū)分是因?yàn)樵谠缙诘挠?jì)算機(jī)系統(tǒng)如PDP-11的一些限制。那時(shí)沒(méi)有GUI, 將輸出分為stdout,stderr可以避免程序的調(diào)試信息和正常輸出的信息混雜在一起。
通常, shell程序把它們的輸出寫入標(biāo)準(zhǔn)輸出管道(stdout)、把出錯(cuò)信息寫入標(biāo)準(zhǔn)錯(cuò)誤管道(stderr)。缺省情況下,系統(tǒng)將管道的輸出直接送到屏幕,這樣一來(lái)我們就能看到應(yīng)用程序運(yùn)行結(jié)果了。
為了捕獲一個(gè)標(biāo)準(zhǔn)控制臺(tái)應(yīng)用程序的輸出,我們必須把standOutput和standError管道輸出重定向到我們自定義的管道。
下面的代碼可以啟動(dòng)一個(gè)shell程序,并將其輸出截獲。
'執(zhí)行并返回一個(gè)命令行程序(shell程序)的標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出'通常命令行程序的所有輸出都直接送到屏幕上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 '負(fù)責(zé)讀取的管道 Dim hWritePipe As Long '負(fù)責(zé)Shell程序的標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出的管道 Dim sOutput As String '放返回的數(shù)據(jù) 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' 把標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出重定向到同一個(gè)管道中去。start.hStdOutput = hWritePipe start.hStdError = hWritePipe start.wShowWindow = SW_HIDE ’隱含shell程序窗口 ' 啟動(dòng)shell程序, sCmdLine指明執(zhí)行的路徑 ret = CreateProcessA(0&, sCmdline, sa, sa, True, NORMAL_PRIORITY_CLASS, _ 0&, 0&, start, proc) If ret = 0 Then MsgBox "無(wú)法建立新進(jìn)程,錯(cuò)誤碼:" & Err.LastDllError Exit Function End If ' 本例中不必向shell程序送信息,因此可以先關(guān)閉hWritePipe CloseHandle hWritePipe ' 循環(huán)讀取shell程序的輸出,每次讀取256個(gè)字節(jié)。 Do ret = ReadFile(hReadPipe, sBuffer, 256, lngBytesRead, 0&) sOutput = sOutput & Left$(sBuffer, lngBytesRead) Loop While ret <> 0 ' 如果ret=0代表沒(méi)有更多的信息需要讀取了 ' 釋放相關(guān)資源 CloseHandle proc.hProcess CloseHandle proc.hThread CloseHandle hReadPipe ExecuteApp = sOutput ' 輸出結(jié)果End Function
我對(duì)這個(gè)程序進(jìn)行一些解釋。
ret = CreatePipe(hReadPipe, hWritePipe, sa, 0)
大家可以看到,首先我們建立一個(gè)匿名管道。該匿名管道稍候?qū)⒂脕?lái)取得與被截獲的應(yīng)用程序的聯(lián)系。其中hReadPipe用來(lái)獲取shell程序的輸出,而hWritePipe可以用來(lái)向應(yīng)用程序發(fā)送信息。如同現(xiàn)實(shí)世界中的水管一樣,水從管道的一端流進(jìn)從另一端流出。您把水想象為信息,水管就是匿名管道,這樣一來(lái)就很好理解這段程序了。
然后就是設(shè)置shell應(yīng)用程序的初始屬性。 Dwflags可以指示系統(tǒng)在創(chuàng)建新進(jìn)程時(shí)新進(jìn)程使用了自定義的wShowWindow, hStdInput,hStdOutput和hStdError。(windows顯示屬性,標(biāo)準(zhǔn)輸入,標(biāo)準(zhǔn)輸出,標(biāo)準(zhǔn)錯(cuò)誤輸出。)
再把shell應(yīng)用程序的標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出都定向到我們預(yù)先建好的管道中。
代碼如下:
start.dwFlags = STARTF_USESTDHANDLES Or STARTF_USESHOWWINDOW
start.hStdOutput = hWritePipe
start.hStdError = hWritePipe
好,現(xiàn)在可以調(diào)用建立新進(jìn)程的函數(shù)了:
ret = CreateProcessA(0&, sCmdline, sa, sa, True, NORMAL_PRIORITY_CLASS, 0&, 0&, start, proc)
然后,循環(huán)讀管道里的數(shù)據(jù)直到無(wú)數(shù)據(jù)可讀為止。
Do
ret = ReadFile(hReadPipe, sBuffer, 256, lngBytesRead, 0&) '每次讀256字節(jié)
sOutput = sOutput & Left$(sBuffer, lngBytesRead) '送入一個(gè)字符串中
Loop While ret <> 0 '若 ret = 0 表明沒(méi)有數(shù)據(jù)等待讀取。
然后,釋放不用的資源。
用法很簡(jiǎn)單:比如:
MsgBox ExecuteApp("c:\windows\command\mem.exe)
是很方便吧?
不過(guò),這些程序是在NT下的,如果要在95下實(shí)現(xiàn)還需要一點(diǎn)點(diǎn)改動(dòng)。因?yàn)槿绻摵瘮?shù)調(diào)用一個(gè)純win32的程序,沒(méi)問(wèn)題。可是95是16,win32混合的系統(tǒng),當(dāng)你試圖調(diào)用一個(gè)16位的DOS應(yīng)用程序那么,那么這個(gè)辦法會(huì)導(dǎo)致相關(guān)進(jìn)程掛起。因?yàn)檫@涉及到WindowsNT和Windows 95對(duì)shell的不同實(shí)現(xiàn)。
在win95中,16位shell程序關(guān)閉時(shí)并不保證重定向的管道也關(guān)閉,這樣,當(dāng)你的程序試圖讀取一個(gè)已經(jīng)關(guān)閉的shell程序的重定向管道時(shí),你的程序就掛了。
那么,有解決辦法嗎?回答是肯定的。
解決辦法就是用一個(gè)win32的應(yīng)用程序作為您的應(yīng)用程序和shell程序的中間人。中間人程序繼承并重定向了主程序的輸入輸出,然后中間人程序啟動(dòng)指定的shell程序。該shell程序也就繼承并重定向了主程序的輸入輸出。中間人程序一直等到shell程序結(jié)束才結(jié)束。
當(dāng)shell程序結(jié)束時(shí),中間人程序也結(jié)束,同時(shí)因?yàn)橹虚g人程序是一個(gè)win32程序,那么它就會(huì)關(guān)閉相應(yīng)的重定向了管道。這樣,你的程序可以發(fā)現(xiàn)管道已經(jīng)關(guān)閉,便可以跳出循環(huán)。你的程序就不會(huì)掛起了。
下面是相關(guān)的中間人程序C代碼的實(shí)現(xiàn):
#include <windows.h>#include <stdio.h>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并放在系統(tǒng)可以調(diào)用到的路徑目錄中。
然后把文章開(kāi)頭提到的代碼中的CreateProcessA語(yǔ)句改為:
ret = CreateProcessA(0&, "conspawn """ & sCmdline & """", sa, sa, True,
NORMAL_PRIORITY_CLASS, 0&, 0&, start, proc)
好,這樣一來(lái),我們這個(gè)函數(shù)可以同時(shí)很好的支持WindowsNT和Windows95/98了。