AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Redirecting Stdio of a child process in AHK

 
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions
View previous topic :: View next topic  
Author Message
drifter



Joined: 08 Aug 2008
Posts: 4

PostPosted: Sat Aug 09, 2008 12:13 pm    Post subject: Redirecting Stdio of a child process in AHK Reply with quote

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
Back to top
View user's profile Send private message
Guest






PostPosted: Sat Aug 09, 2008 12:44 pm    Post subject: Reply with quote

Very nice. Works well on Win98 and XP. I will have to do some comparisons to cmdret.ahk.
Back to top
BoBo˛
Guest





PostPosted: Sun Aug 10, 2008 12:01 am    Post subject: Reply with quote

Thx. Very Happy
Back to top
Display posts from previous:   
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions All times are GMT
Page 1 of 1

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group