drifter
Joined: 08 Aug 2008 Posts: 4
|
Posted: Sat Aug 09, 2008 12:13 pm Post subject: Redirecting Stdio of a child process in AHK |
|
|
redstdio.ahk
FUNCTION: RedHandle := RunRed(CmdLine, WorkingDir="", Reserved="", byref PID="")
Runs a program and redirects its standard input/output to the script. The standard output can be retrieved via the GetChildStdout function. There is currently no function to simplify writing to the process's STDIN.
FUNCTION: Result := GetChildStdout(RedHandle, byref Stdout, byref BytesAppended = 0)
Appends the child process's standard output to a variable.
If the operation was successful and the process still exists, the return value is 0.
If the operation was successful and the child process no longer exists, the return value is 1.
If there is an error, the return value is a number greater than or equal to 2.
When the function returns a value of 1 (indicating the process has closed), the resources allocated for that process are freed, and the handle passed to the function becomes invalid.
FUNCTION: VOID := RedStdioDestroy()
Frees all resources in use by RedSTDIO.ahk. It is not normally necessary to call this function except when an error occurs and your program must close, because the GetChildStdout function automatically frees the resources for each process when it detects that the process has closed and returns a value of 1.
Example
| Code: | #NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
#Include redstdio.ahk
;Create GUI window with an edit control to print the output.
Gui, Add, Edit, x15 y15 w460 h300 -Wrap ReadOnly hscroll vtxtStdout,
Gui, Show, w490 h330, Standard Input/Output Redirection Example
;Create the process
hRedProcess := RunRed("cmd.exe /c echo Hello World.")
if (hRedProcess == 0)
ErrorExit("Unable to start cmd.exe")
;Retrieve the process's output and append it to the edit control in a loop. Break when the process
;has closed.
Loop
{
retv := GetChildStdout(hRedProcess, strStdout, BytesAppended) ; append output of child process to strStdout
if (BytesAppended)
GuiControl, , txtStdout, %strStdout% ; append the output to the edit control.
if (retv == 1)
break ; a return value of 1 indicates the process no longer exists.
else if (retv == 2)
ErrorExit("Invalid handle value.")
else if (retv > 2)
ErrorExit("Unexpected error; quitting.")
sleep 50 ; Give up remainder of time-slice to give the child process a chance to generate more output.
}
Return
ErrorExit(Msg="Unexpected error; quitting.", ExitCode=1, Title="")
{
RedStdioDestroy() ; Free resources used by RedStdio.ahk
MsgBox, 48, %Title%, %Msg%
ExitApp, ExitCode
}
GuiClose:
ExitApp |
Source Code
| Code: | ;
; AutoHotkey Version: 1.x
; Language: English
; Platform: Win9x/NT
; Author: Marcus Cortes <macortes84@yahoo.com>
;
; Script Function:
; Provides a class of functions for redirecting and capturing
; the standard input and output of other programs.
;
/*
;EXAMPLE
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
#Include redstdio.ahk
;Create GUI window with an edit control to print the output.
Gui, Add, Edit, x15 y15 w460 h300 -Wrap ReadOnly hscroll vtxtStdout,
Gui, Show, w490 h330, Standard Input/Output Redirection Example
;Create the process
hRedProcess := RunRed("cmd.exe /c echo Hello World.")
if (hRedProcess == 0)
ErrorExit("Unable to start cmd.exe")
;Retrieve the process's output and append it to the edit control in a loop. Break when the process
;has closed.
Loop
{
retv := GetChildStdout(hRedProcess, strStdout, BytesAppended) ; append output of child process to strStdout
if (BytesAppended)
GuiControl, , txtStdout, %strStdout% ; append the output to the edit control.
if (retv == 1)
break ; a return value of 1 indicates the process no longer exists.
else if (retv == 2) {
ErrorExit("Invalid handle value.")
else if (retv > 2)
ErrorExit("Unexpected error; quitting.")
sleep 50 ; Give up remainder of time-slice to give the child process a chance to generate more output.
}
Return
ErrorExit(Msg="Unexpected error; quitting.", ExitCode=1, Title="")
{
RedStdioDestroy() ; Free resources used by RedStdio.ahk
MsgBox, 48, %Title%, %Msg%
ExitApp, ExitCode
}
GuiClose:
ExitApp
*/
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
;#include stdlib.ahk
/*
FUNCTION RunRed
DESCRIPTION
Runs a program and redirects its standard input/output to the script. The standard output can be retrieved via the GetChildStdout function.
There is currently no wrapper function for writing to the process STDIN.
PARAMETERS
CmdLine [in, optional, String]
The command line to be executed. The maximum length of this string is 32,768 characters, including the Unicode terminating null character.
the first white space–delimited token of the command line specifies the module name. If you are using a long file name that contains a space,
use quoted strings to indicate where the file name ends and the arguments begin (see the explanation for the lpApplicationName parameter).
If the file name does not contain an extension, .exe is appended. Therefore, if the file name extension is .com, this parameter must include
the .com extension. If the file name ends in a period (.) with no extension, or if the file name contains a path, .exe is not appended. If the file
name does not contain a directory path, the system searches for the executable file in the following sequence:
1. The directory from which the application loaded.
2. The current directory for the parent process.
3. The 32-bit Windows system directory. Use the GetSystemDirectory function to get the path of this directory.
4. The 16-bit Windows system directory. There is no function that obtains the path of this directory, but it is searched. The name of this directory
is System.
5. The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
6. The directories that are listed in the PATH environment variable. Note that this function does not search the per-application path specified by
the App Paths registry key. To include this per-application path in the search sequence, use the ShellExecute function.
The system adds a terminating null character to the command-line string to separate the file name from the arguments. This divides the original
string into two strings for internal processing.
WorkingDir [in, optional, String]
The full path to the current directory for the process. The string can also specify a UNC path.
If this parameter is NULL, the new process will have the same current drive and directory as the calling process. (This feature is provided primarily
for shells that need to start an application and specify its initial drive and working directory.)
Reserved [in, optional, void]
Reserved for future use.
PID [in, optional, Integer]
A variable that receives the process identifier.
RETURN VALUE
If successful, this function returns a handle that can be used to indentify the new process in a call to GetChildStdout.
If the function fails, the return value is zero.
http://msdn.microsoft.com/en-us/library/ms682425.aspx
*/
RunRed(CmdLine, WorkingDir="", Reserved="", byref PID="")
{
global
local sa, pi, si
local hStdoutRead, hStdoutWrite, hStdinRead, hStdinWrite
static RedProcNextHandle := 1
calloc(pi, 16, 0) ;PROCESS_INFORMATION
calloc(si, 4*18, 0) ;STARTUP_INFO
NumPut(4*18, si, 0, "uint") ;STARTUP_INFO {HANDLE cbSize}
NumPut(0x100 | 0x1, si, 4*11, "uint") ;STARTUP_INFO {dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW}
;NumPut(0x0, si, 4*12, "ushort") ;STARTUP_INFO {wShowWindow = SW_HIDE}
;ALLOC HANDLES
malloc(hStdoutRead, 4)
malloc(hStdoutWrite, 4)
malloc(hStdinRead, 4)
malloc(hStdinWrite, 4)
;ALLOC AND INIT SECURITY_ATTRIBUTES STRUC
calloc(sa, 12, 0) ;security attributes
NumPut(12, sa, 0, "uint")
NumPut(0, sa, 4, "uint")
NumPut(true, sa, 8, "int")
;CREATE PIPE FOR STDOUT
if (!assert(DllCall("CreatePipe", "uint", &hStdoutRead, "uint", &hStdoutWrite, "uint", &sa, "uint", 0), "Unable to create pipe for STDOUT."))
return false
hStdoutRead := NumGet(hStdoutRead, 0, "uint")
hStdoutWrite := NumGet(hStdoutWrite, 0, "uint")
resource("REDSTDIO_RunRed", hStdoutRead)
resource("REDSTDIO_RunRed", hStdoutWrite)
DllCall("SetHandleInformation", "uint", hStdoutRead, "uint", 1, "uint", 0, "int") ? : dprint("Error setting handle information for hStdoutRead.") ;Ensure pipe handle is not inherited
;CREATE PIPE FOR STDIN
if (!assert(DllCall("CreatePipe", "uint", &hStdinRead, "uint", &hStdinWrite, "uint", &sa, "uint", 0), "Unable to create pipe for STDIN.")) {
ResFree("REDSTDIO_RunRed")
return false
}
hStdinRead := NumGet(hStdinRead, 0, "uint")
hStdinWrite := NumGet(hStdinWrite, 0, "uint")
resource("REDSTDIO_RunRed", hStdinRead)
resource("REDSTDIO_RunRed", hStdinWrite)
DllCall("SetHandleInformation", "uint", hStdinWrite, "uint", 1, "uint", 0, "int") ? : dprint("Error setting handle information for hStdoutRead.") ;Ensure pipe handle is not inherited
;USE PIPE HANDLES FOR STDIO
NumPut(hStdinRead, si, 4*14, "uint") ;STARTUP_INFO {HANDLE hStdInput}
NumPut(hStdoutWrite, si, 4*15, "uint") ;STARTUP_INFO {HANDLE hStdOutput}
NumPut(hStdoutWrite, si, 4*16, "uint") ;STARTUP_INFO {HANDLE hStderror}
if (!DllCall("CreateProcess", "uint", 0, "uint", &CmdLine, "uint", 0, "uint", 0, "int", true, "uint", 0, "uint", 0, "uint", 0, "uint", &si, "uint", &pi)) {
ResFree("REDSTDIO_RunRed")
dprint("Execution failed.")
return false
}
PID := NumGet(pi, 8, "uint") ;DWORD dwProcessId
DllCall("CloseHandle", "uint", NumGet(pi, 4, "uint")) ;HANDLE hThread
cp := ColAdd("REDSTDIO_ChildProcesses", "", RedProcNextHandle)
%cp%hProcess := NumGet(pi, 0, "uint")
%cp%hStdoutRead := hStdoutRead
%cp%hStdoutWrite := hStdoutWrite
%cp%hStdinRead := hStdinRead
%cp%hStdinWrite := hStdinWrite
ResDelete("REDSTDIO_RunRed") ;Delete resource information without freeing the resources.
return RedProcNextHandle++
}
/*
FUNCTION GetChildStdout
PARAMETERS _in int RedHandle ;The process number returned by a call to RunRed.
_out str Stdout ;A variable that will receive the standard output of
the program. The standard output is appended to the
variable.
_out int BytesAppended ;A variable that receives the number of bytes appended
to Stdout.
RETURN VALUE
Returns 1 if the process no longer exists. The contents of Stdout will contain the final output
of the process if there is any. Returns 0 otherwise.
REMARKS
When this function returns with a value of 1, or DestroyRedIO has been called since the RedHandle was
created with RunRed, the RedHandle becomes invalid. If this function is called with an invalid RedHandle,
it will return with a value of 2 indicating RedHandle is invalid.
*/
GetChildStdout(RedHandle, byref Stdout, byref BytesAppended = 0)
{
global
local cp, buf, ec, cb, hProcessOk
cp := ColGet("REDSTDIO_ChildProcesses", RedHandle)
if (strlen(cp) == 0)
return 2
malloc(ec, 4)
malloc(cb, 4)
hProcessOk := DllCall("GetExitCodeProcess", uint, %cp%hProcess, uint, &ec)
BytesAppended := 0
Loop {
DllCall("PeekNamedPipe", "uint", %cp%hStdoutRead, "uint", 0, "uint", 0, "uint", 0, "uint", &cb, "uint", 0)
if (!NumGet(cb, 0, "uint"))
break
if (!REDSTDIO_Private_ReadFile(%cp%hStdoutRead, buf, cb, 1024)) {
dprint("REDSTDIO::GetChildStdout - Error reading process STDOUT; detaching from child process.")
return 3
}
stdout := stdout . buf
BytesAppended += NumGet(cb, 0, "uint")
}
if (!hProcessOk OR NumGet(ec, 0, "uint") != 0x00000103) {
DllCall("CloseHandle", "uint", %cp%hProcess)
DllCall("CloseHandle", "uint", %cp%hStdoutRead)
DllCall("CloseHandle", "uint", %cp%hStdoutWrite)
DllCall("CloseHandle", "uint", %cp%hStdinRead)
DllCall("CloseHandle", "uint", %cp%hStdinWrite)
ColDel(cp, "hProcess")
ColDel(cp, "hStdoutRead")
ColDel(cp, "hStdoutWrite")
ColDel(cp, "hStdinRead")
ColDel(cp, "hStdinWrite")
ColDel(cp)
return 1
}
return 0
}
/*
FUNCTION RedStdioDestroy
PURPOSE
Frees all resources in use by RedSTDIO. Calling GetChildStdout after calling this will cause GetChildStdout
to return with a value of 2, indicating RedHandle is invalid.
PARAMETERS This function has no parameters.
RETURN VALUE
This function has no return value.
USAGE
Calling GetChildStdout after calling this will cause GetChildStdout to return with a value of 2, indicating RedHandle is invalid. This
function does notneed to be called if the function GetChildStdout has returned with a return value of 1 (indicating the process has
closed) for each process created with the RunRed function. This function is designed for use when an application needs to close
even while child processes with redirected input/output might still be running.
*/
RedStdioDestroy()
{
global
local cp, cnt
cnt := "REDSTDIO_ChildProcesses[Count]"
Loop %cnt%
{
cp := "REDSTDIO_ChildProcesses" a_index
DllCall("CloseHandle", "uint", %cp%hProcess)
DllCall("CloseHandle", "uint", %cp%hStdoutRead)
DllCall("CloseHandle", "uint", %cp%hStdoutWrite)
DllCall("CloseHandle", "uint", %cp%hStdinRead)
DllCall("CloseHandle", "uint", %cp%hStdinWrite)
ColDel(cp, "hProcess")
ColDel(cp, "hStdoutRead")
ColDel(cp, "hStdoutWrite")
ColDel(cp, "hStdinRead")
ColDel(cp, "hStdinWrite")
ColDel(cp)
}
}
;PRIVATE FUNCTIONS
;BufferSize is including the terminating null character.
REDSTDIO_Private_ReadFile(hFile, byref buf, byref BytesRead=0, BufferSize=4096)
{
if (BufferSize <= 1)
BufferSize := 4096
malloc(Buf, BufferSize)
malloc(BytesRead, 4)
if (!DllCall("ReadFile", "uint", hFile, "uint", &buf, "uint", BufferSize-1, "uint", &BytesRead, "uint", 0))
return false
BytesRead := NumGet(BytesRead, 0, "uint")
NumPut(0, Buf, BytesRead, "uchar") ;terminate data read with a null
VarSetCapacity(buf, -1)
return true
}
Assert(Expr=0, Text="Unspecified error.", ExitCode=-1)
{
Global NDEBUG
if (!NDEBUG)
if (!Expr) {
OutputDebug, %Text%
BlockInput, Off
ExitApp, ExitCode
}
return Expr
}
dprint(TextOrExpr="Unspecified error.", Text="")
{
Global NDEBUG
if (!NDEBUG) {
if (!strlen(Text))
OutputDebug, %TextOrExpr%
else
if (!TextOrExpr)
OutputDebug, %Text%
}
return, TextOrExpr
}
malloc(byref Var, size=0)
{
if (cb := VarSetCapacity(Var, size) != size) {
MsgBox, 48, , Out of memory.
ExitApp, -1
}
return cb
}
calloc(byref Var, size=0, fillbyte=0)
{
if (cb := VarSetCapacity(Var, size, fillbyte) != size) {
MsgBox, 48, , Out of memory.
ExitApp, -1
}
return cb
}
Resource(GroupName, Res, Type="HANDLE")
{
StringLower, Type, Type
res := coladd("STDLIB_" GroupName "_Resources", Res)
%res%Type := Type
}
ResFree(GroupName)
{
cnt := "STDLIB_" GroupName "_Resources[Count]"
Loop, %cnt% {
res := "STDLIB_" GroupName "_Resources" . a_index
if (%res%Type == "handle")
DllCall("CloseHandle", "uint", %res%)
ColDel(res, "Type")
ColDel(res)
}
}
ResDelete(GroupName)
{
cnt := (resgrp := "STDLIB_" GroupName "_Resources") "[Count]"
Loop, %cnt% {
ColDel(res := resgrp . a_index, "Type")
ColDel(res)
}
}
ColAdd(ColName, vData="", Key="")
{
Global
Local i
%ColName%[Count]++
i := %ColName%[Count]
if (i == 1)
%ColName%[LBound] := 1
%ColName%[UBound] := i
if (vData <> "")
%ColName%%i% := vData
if (Key <> "")
%ColName%%i%[Key] := Key
%ColName%%i%[Index] := i
%ColName%%i%[ColName] := ColName
return, ColName . i
}
ColGet(ColName, Key, Item="")
{
Global
Local Count
Count := %ColName%[Count]
Loop, %Count% {
if (%ColName%%A_Index%[Key] = Key) {
if (Item == "" || Item = ".ref")
return, ColName . A_Index
else if (Item = ".Value")
return, %ColName%%A_Index%
else if (Item = ".Count")
return, %ColName%[Count]
else if (Item = ".Index")
return, A_Index
else
return, %ColName%%A_Index%%Item%
}
}
}
ColDel(ColItem, SubItem="")
{
Global
Local ColName, i, ScrollCount
ColName := %ColItem%[ColName]
if (StrLen(ColName) == 0)
return, 1
i := %ColItem%[Index]
ScrollCount := %ColName%[UBound] - i
Loop, %ScrollCount%
{
n := i + 1
if (strlen(subitem) > 0)
%ColName%%i%%SubItem% := %ColName%%n%%SubItem%
else {
%ColName%%i% := %ColName%%n%
%ColName%%i%[Key] := %ColName%%n%[Key]
}
i++
}
%ColName%%i% =
%ColName%%i%[Key] =
%ColName%%i%[Index] =
%ColName%%i%[ColName] =
if (--%ColName%[Count])
%ColName%[UBound]--
else {
%ColName%[Count] =
%ColName%[UBound] =
%ColName%[LBound] =
}
} |
Last edited by drifter on Sat Aug 09, 2008 5:50 pm; edited 1 time in total |
|