Jump to content


Photo

Using standard input/output


  • Please log in to reply
15 replies to this topic

#1 drifter

drifter
  • Members
  • 121 posts

Posted 09 August 2008 - 02:35 PM

stdio.ahk

FUNCTION: Success := StdioInitialize()
Attaches to the parent application's console window or, if there isn't one, allocates a new console window. After calling this function, the script can use FileAppend to write to the console window.FUNCTION: Success := FreeConsole()
Detaches from or deallocates the currently attached console window, if one exists.FUNCTION: Success := printf(str)
Provides support for writing to the standard output of the script (the console window). This function should be used in place of Ahk's FileAppend command when writing to the console, for now. This is because the Win32 console functions (used in this stdio.ahk) do not correct the true STDIO handle of the script. Also, FileAppend does not currently "flush" the text to the stream until the program ends. That means that no program output will be displayed until the script finishes.FUNCTION: Success := RunIO(CmdLine, WorkingDir="", Reserved="", byref PID="")
Runs a program and redirects its standard input/output to the current script's standard input/output. This allows a script to run other console applications and have them write to current console window, instead of allocating a new console window.FUNCTION: Success := RunIOWait(CmdLine, WorkingDir="", Reserved="", byref ExitCode="", byref PID="")
Same as RunIO, except it does not return until the process has closed.Example
;Run this script from a command prompt.
#Include stdio.ahk
StdioInitialize()
FileAppend, This method should not work with console windows, *
printf("`n")
printf("Hello World`n")
RunIOWait(comspec " /c echo Hello from a child process.")
FreeConsole()
Source Code
;
; AutoHotkey Version: 1.x
; Language:       English
; Platform:       WinXP
; Author:         Marcus Cortes <macortes84@yahoo.com>
;
; Script Function:
;	Provides console manipulation tools for Win32 AutoHotkey scripts.
;
/*
EXAMPLE:
	;Run this script from a command prompt.
	#Include stdio.ahk
	StdioInitialize()
	FileAppend, This method should not work with console windows, *
	printf("`n")
	printf("Hello World`n")
	RunIOWait(comspec " /c echo Hello from a child process.")
	FreeConsole()
*/
#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
;#Include stdlib.ahk

/*
FUNCTION		StdioInitialize
DESCRIPTION
	Attaches to the parent application's console window or, if there isn't one, allocates a new console window.  After
	calling this function, the script can use FileAppend to write to the console window.
RETURN VALUE
	If the function succeeds, the return value is nonzero.
	If the function fails, the return value is zero.
REMARKS
	When called by a normal console program, such as cmd.exe, the calling program will not wait for this script to exit
	before it continues processing.  That is why the command prompt (e.g. 'C:\>'), is reprinted on the console window
	before any messages from the script are printed.
*/
StdioInitialize()
{
	global
	if (!DllCall("AttachConsole", int, -1, int))
		if (!DllCall("AllocConsole", int))
			return,  false
	;atexit("FreeConsole")
	return true
}

/*
FUNCTION		FreeConsole
DESCRIPTION
	Detaches from or deallocates the currently attached console window, if one exists.
RETURN VALUE
	If the function succeeds, the return value is nonzero.
	If the function fails, the return value is zero.
*/
FreeConsole()
{
	;atexitrem("FreeConsole")
	return DllCall("FreeConsole", int)
}

/*
FUNCTION      printf
DESCRIPTION
	Provides support for writing to the standard output of the script (the console window).  This 
	function should be used in place of Ahk's FileAppend command for writing to consoles 
	because the FileAppend command does not currently support writing to consoles.  FileAppend 
	also does not currently "flush" the text out of the output buffer and onto the screen until the 
	program ends.  That means that no program output would be displayed until the script finishes.
RETURN VALUE
	If the function succeeds, the return value is nonzero (TRUE).
	If the function fails, the return value is zero (FALSE)
*/
printf(str)
{
	str = [%str%] ;convert escape characters while preserving leading and trailing spaces and not changing AutoTrim
	StringTrimLeft, str, str, 1
	StringTrimRight, str, str, 1
	if (hStdout := DllCall("GetStdHandle", "int", -11))
		return DllCall("WriteFile", "uint", hStdout, "uint", &str, "uint", strlen(str), "uint", malloc(BytesWritten, 4), "uint", NULL)
}

;Runs a process and sets its STDIO to the same as the current process.
/*
FUNCTION		RunIO

DESCRIPTION
	Runs a program and redirects its standard input/output to the current script's standard input/output.
	This allows a script to run other console applications and have them write to current console window,
	instead of allocating a new console window.

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.
	
	UsedInternally [in, optional, String]
			This parameter is used internally and should be left blank.

RETURN VALUE
	If the function succeeds, the return value is the process identifier of the child process.
	If the function fails, the return value is zero.

http://msdn.microsoft.com/en-us/library/ms682425.aspx
*/
RunIO(CommandLine, WorkingDir="", Reserved="", byref PID="", byref UsedInternally="")
{
	global
	local sa, pi, si, hStdout, hStdin, hStderror, ec
	
	;Alloc pi and si
	malloc(pi, 16) ;PROCESS_INFORMATION
	calloc(si, 4*18, 0) ;STARTUP_INFO
	
	;Alloc and initialize a security attributes structure
	calloc(sa, 12, 0) ;security attributes
	NumPut(12,  sa, 0, "uint")
	NumPut(0, sa, 4, "uint")
	NumPut(true, sa, 8, "int")
	
	hStdin := DllCall("GetStdHandle", int, -10, int)
	hStdout := DllCall("GetStdHandle", int, -11, int)
	hStderror := DllCall("GetStdHandle", int, -12, int)
	
	NumPut(4*18,  si, 0, "uint") ;STARTUP_INFO {HANDLE cbSize}
	NumPut(0x100, si, 4*11, "uint") ;STARTUP_INFO {dwFlags}
	NumPut(hStdin, si, 4*14, "uint") ;STARTUP_INFO {HANDLE hStdInput}
	NumPut(hStdout, si, 4*15, "uint") ;STARTUP_INFO {HANDLE hStdOutput}
	NumPut(hStderror, si, 4*16, "uint") ;STARTUP_INFO {HANDLE hStderror}
	
	if (!DllCall("CreateProcess", "uint", 0, "uint", &CommandLine, "uint", 0, "uint", 0, "int", true, "uint", 0, "uint", 0, "uint", 0, "uint", &si, "uint", &pi)) {
		;dprint("Execution failed.")
		return false
	}
	PID := NumGet(pi, 8, "uint") ;DWORD dwProcessId
	if (UsedInternally == "LEAVE_OPEN")
		UsedInternally := NumGet(pi, 0, "uint")
	else
		DllCall("CloseHandle", "uint", NumGet(pi, 0, "uint")) ;HANDLE hProcess
	DllCall("CloseHandle", "uint", NumGet(pi, 4, "uint")) ;HANDLE hThread
	return PID
}

/*
FUNCTION		RunIOWait
DESCRIPTION
	Runs a program, redirects its standard input/output to the current script's standard input/output, and
	waits for the program to finish before returning.  To return immediately without waiting, see the RunIO
	function.  These functions allow a script to run other console applications and have them write to 
	current console window rather than allocating a new console window.
PARAMETERS
	CommandLine	The command line to be executed.  See parameter for RunIO.
	WorkingDir	The full path to the current directory for the process.  See parameter for RunIO.
	Reserved		Reserved for future use.
	ExitCode		A variable that receives the exit code of the process.
	PID			A variable that receives the process identifier.
RETURN VALUE
	If the function succeeds, the return value is the process identifier of the child process.
	If the function fails, the return value is zero.
*/
RunIOWait(CommandLine, WorkingDir="", Reserved="", byref ExitCode="", byref PID="")
{
	hProcess := "LEAVE_OPEN"
	if (!RunIO(CommandLine, WorkingDir, Reserved, PID, hProcess))
		return false
	DllCall("WaitForSingleObject", "uint", hProcess, "int", -1, "uint")
	DllCall("GetExitCodeProcess", "uint", hProcess, "uint", malloc(ec, 4), "int")
	ExitCode := NumGet(ec, 0, "uint")
	DllCall("CloseHandle", "uint", hProcess)
	return, PID
}

/*
FUNCTION	
PURPOSE
	To provide a simple solution to displaying an error message to a console and quitting.
*/
ErrorExitIO(Msg="Unexpected error; quitting.", ExitCode=1, Unused="")
{
	BlockInput, Off
	FileAppend, %Msg%`n, *
	FreeConsole()
	ExitApp, ExitCode
}

;INTENDED PRIVATE
malloc(byref Var, size=0)
{
	if (cb := VarSetCapacity(Var, size) != size) {
		MsgBox 48, , Out of memory.
		ExitApp, -1
	}
	return &Var
}

calloc(byref Var, size=0, fillbyte=0)
{
	if (cb := VarSetCapacity(Var, size, fillbyte) != size) {
		MsgBox 48, , Out of memory.
		ExitApp, -1
	}
	return &Var
}

STDIO_EndOfFile:
NULL := "" ;Prevent label from pointing to something labels can't point to.


#2 BoBo²

BoBo²
  • Guests

Posted 09 August 2008 - 11:50 PM

Thx for sharing this. Much appreciated. 8)

#3 drifter

drifter
  • Members
  • 121 posts

Posted 10 August 2008 - 07:07 AM

You're welcome.

#4 Obi-Wahn

Obi-Wahn
  • Members
  • 77 posts

Posted 10 October 2009 - 12:37 PM

Thanks a lot for that functions. But it seems like I have to send Ctrl+c to the comspec-Window after performing the FreeConsole command.
Does anyone has the same issue?

#5 drifter

drifter
  • Members
  • 121 posts

Posted 12 October 2009 - 07:52 PM

This has long since been a known issue with the Windows' API console functions (used to manipulate console windows from normal processes). The problem is that the command prompt does not reprint itself after the process is finished with the console. This is a side-effect of the multitasking environment.

#6 Obi-Wahn

Obi-Wahn
  • Members
  • 77 posts

Posted 13 October 2009 - 07:20 AM

Sorry, I didn't know that.
But wouldn't fix a DllCall to GenerateConsoleCtrlEvent fix this issue?

(that's just a shot into the blue. Frankly, I just barely know the syntax of DllCalls...)

#7 drifter

drifter
  • Members
  • 121 posts

Posted 14 October 2009 - 12:19 AM

Thank you. Yes, sending a Ctrl-C event to the console will work in some cases; however, it won't work in every case. StdioInitialize() will use the console window associated with the parent process if one exists. Now, if the parent process is cmd.exe, and cmd.exe has no parent process of its own, then it will reprint the command prompt as desired. However, under all other circumstances it will cause the parent process to exit, and is not necessarily be desirable.

If I remember correctly, this isn't a problem if there is no parent process with a console window. If this problem does apply to this scenario, then this fix will work, but you cannot use it with StdioInitialize(). If this problem does apply to this scenario, let me know and I'll post the code for the fix.

#8 Obi-Wahn

Obi-Wahn
  • Members
  • 77 posts

Posted 14 October 2009 - 09:02 AM

Well, I'm launching the script from an existing console which hasn't a parent process. I think, that should work, right?!?

Even if it doesn't, I allways appreciate code. Maybe I'll learn Dllcalls this way.


THXIA
Obi-Wahn

#9 drifter

drifter
  • Members
  • 121 posts

Posted 14 October 2009 - 10:19 PM

It will work only for you assuming you only run it from a console without a parent process.

You should use this code in place of the call to StdInitialize():

if (!DllCall("AllocConsole", INT))
{
    MsgBox % "Failed to allocate console"
    Exit
}

To send the Ctrl-C event from the script you could use this code:

DllCall("GenerateConsoleCtrlEvent", INT, 0, INT, 0, INT)

GenerateConsoleCtrlEvent sends a Ctrl-C event to processes associated with the console window, not the console window. Calling GenerateConsoleCtrlEvent like this will send Control-C to all processes that share the console associated with the calling process. Be careful because the GenerateConsoleCtrlEvent might send the event to your script as well.

#10 Obi-Wahn

Obi-Wahn
  • Members
  • 77 posts

Posted 29 October 2009 - 07:17 AM

Hi drifter!

Sorry for responding so late but I wasn't notified by the forum that a new post is here.

Thanks for your response, I'll try it today and I'll response ASAP.

#11 Std_O plz

Std_O plz
  • Guests

Posted 02 January 2010 - 11:12 AM

I'm trying to get my head around this set of funtions.

I need to be able to get a commandline program's Std out into a variable rather than a text file.

Alls I see in the examples is a way to get text from ahk to the console but not the other way around.

Can stdio.ahk do this?

Any help is greatly appreciated.

#12 drifter

drifter
  • Members
  • 121 posts

Posted 03 January 2010 - 05:52 AM

I don't remember. Let me check... ah no. But, a few scripts have been created for this purpose. Have a look at the following:

StdoutToVar: http://www.autohotke...topic16823.html
CMDret: http://www.autohotke.../topic8606.html
RedStdio: http://www.autohotke...hlight=redstdio

The first two are similar. The main difference being CMDret hides the console, and StdoutToVar doesn't create the console window in the first place. Also, CMDret has an asynchronous function, whereas StdoutToVar does not. StdoutToVar was placed in the script showcase so this may be the preferred script to use for this purpose.

#13 Crash&Burn

Crash&Burn
  • Members
  • 228 posts

Posted 08 September 2010 - 07:11 AM

You could write the output to a registry key in HKEY_CURRENT_USER\Volatile Environment. While not as slick perhaps as some of the other methods that pass messages -- it does at least avoid temporary text files and doesn't require the clipboard. As well, the volatile environment key is purged after a reboot.

I've been testing it as a way to store data between script launches, or even for script communication. And to store data for cmd prompts to recall information from a previous command in a different commmand-prompt window.

Since AHK can do regRead/Writes. As well many tools exist for command prompt for registry handling: including SetEnv.exe (which was where I found the Volatile Environment key) -- after using SetEnv -v SomeVar "value"

#14 Lexikos

Lexikos
  • Administrators
  • 8853 posts

Posted 25 October 2010 - 01:25 PM

This script came up in discussion recently. I thought I'd take the opportunity to review it. ;)

str = [%str%] ;convert escape characters while preserving leading and trailing spaces and not changing AutoTrim
StringTrimLeft, str, str, 1
StringTrimRight, str, str, 1

All it actually does is waste time and space. Escape sequences are converted at load-time in literal strings. Transform, var, Deref, string converts escape sequences at run-time, but in this case it's unnecessary.

"uint", malloc(BytesWritten, 4)

It would be faster and clearer to use "uint*", BytesWritten.

FileAppend does not currently "flush" the text to the stream until the program ends.

That changed in v1.0.48.04. Buffering is disabled for stdout, so FileAppend can be used.

the FileAppend command does not currently support writing to consoles

I believe FileAppend text, CONOUT$ has always been supported. Similarly, you can accept input via FileReadLine var, CONIN$, 1 (but the script will seem unresponsive while waiting for input). CONOUT$ and CONIN$ are documented by Microsoft here.

The "Out of memory" checks in malloc() and calloc() are unnecessary, since the thread will automatically exit if the script runs out of memory. To demonstrate:
VarSetCapacity(var, 2**31)
They do however cause problems since VarSetCapacity is not guaranteed to set the capacity to exactly the amount requested. For instance,
malloc(a, 8)
malloc(a, 4)
;or
malloc(b, 2)
It's more apparent in Unicode builds of AutoHotkey_L, where the minimum allocation is 8 bytes.

Btw, I think StdioInitialize() may be shortened to just
return DllCall("AttachConsole", "int", -1) || DllCall("AllocConsole")


#15 drifter

drifter
  • Members
  • 121 posts

Posted 25 October 2010 - 08:42 PM

Thanks for bringing these to my attention Lexikos. I stopped supporting this script some time ago due to lack of interest. But, I would love to correct these and make several more changes if it's ever needed, such as better named functions, better memory management, a better error management API, and better collection/array/hash table API. Instead, I've been focusing on other things, such as porting AutoHotkey to .NET using AutoHotkey.dll, which will have the benefit of a professional grade library, and powerful IDEs to start with.