How to write to stdout from child script? Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
doubledave22
Posts: 343
Joined: 08 Jun 2019, 17:36

How to write to stdout from child script?

20 Sep 2021, 19:29

Main script starts and runs a child script which is also persistent. I'd like to do some basic logging from the child script and view it in the stdout.

I am using this which works great in script 1:

Code: Select all

FileAppend, % "Some text", *
However it seems that script 2's fileappend * command doesn't end up writing to the stdout. Anyone ever run into this? Thanks for any help
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: How to write to stdout from child script?

20 Sep 2021, 20:01

doubledave22 wrote: However it seems that script 2's fileappend * command doesn't end up writing to the stdout
Why do you think so? It definitely does:

Code: Select all

script := "FileAppend, Auto, *" . "`n"
        . "FileAppend, Hot, *"  . "`n"
        . "FileAppend, key, *"

Shell := ComObjCreate("WScript.Shell")
Exec := Shell.Exec(A_AHKPath . " *")
Exec.StdIn.Write(script)
Exec.StdIn.Close()

StdOut := Exec.StdOut
str := ""
while !StdOut.AtEndOfStream
   str .= StdOut.ReadLine()

MsgBox, % str
doubledave22
Posts: 343
Joined: 08 Jun 2019, 17:36

Re: How to write to stdout from child script?

20 Sep 2021, 20:43

@teadrinker thanks but I guess I am more referring to the console at the bottom of SciTE4Autohotkey. When I run your script I see the msgbox alright but nothing shows in the console.
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: How to write to stdout from child script?

20 Sep 2021, 21:00

Of course, the console can only be attached to one process, the child script is running as a separate process.
doubledave22
Posts: 343
Joined: 08 Jun 2019, 17:36

Re: How to write to stdout from child script?

20 Sep 2021, 21:20

I see... I did some work with ObjRegisterActive to pass logs through but it was a bit slow and clunky. I didnt know if there was a better way. Maybe a separate console window for the second script somewhere? Open to ideas.
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: How to write to stdout from child script?

20 Sep 2021, 22:28

As an option:

Code: Select all

script =
(
FileAppend, Auto, *
Sleep, 1000
FileAppend, Hot, *
Sleep, 1000
FileAppend, key, *
)

DynaRunReadStdOut(script, "ShowStdOutFromChild")

ShowStdOutFromChild(out := "") {
   static EM_SETSEL := 0xB1, EM_REPLACESEL := 0xC2
        , hGui, _ := ShowStdOutFromChild()
   if !hGui {
      Gui, New, +hwndhGui +AlwaysOnTop +LabelConsole, My Console
      Gui, Font, s10, Lucida Console
      Gui, Add, Edit, w300 h200
      Gui, Show
   }
   SendMessage, EM_SETSEL, -2, -1, Edit1, ahk_id %hGui%
   SendMessage, EM_REPLACESEL, false, &out, Edit1, ahk_id %hGui%
}

ConsoleClose() {
   ExitApp
}

DynaRunReadStdOut(script, callBack := "", encoding := "CP0", args*)
{
   static HANDLE_FLAG_INHERIT := 0x00000001, flags := HANDLE_FLAG_INHERIT
        , STARTF_USESTDHANDLES := 0x100, CREATE_NO_WINDOW := 0x08000000
        , params := [ "UInt", PIPE_ACCESS_OUTBOUND     := 0x2, "UInt", 0
                    , "UInt", PIPE_UNLIMITED_INSTANCES := 255, "UInt", 0
                    , "UInt", 0, "Ptr", 0, "Ptr", 0, "Ptr" ]
        , BOM := Chr(0xFEFF)
   
   DllCall("CreatePipe", "PtrP", hPipeRead, "PtrP", hPipeWrite, "Ptr", 0, "UInt", 0)
   DllCall("SetHandleInformation", "Ptr", hPipeWrite, "UInt", flags, "UInt", HANDLE_FLAG_INHERIT)
   
   VarSetCapacity(STARTUPINFO , siSize :=    A_PtrSize*4 + 4*8 + A_PtrSize*5, 0)
   NumPut(siSize              , STARTUPINFO)
   NumPut(STARTF_USESTDHANDLES, STARTUPINFO, A_PtrSize*4 + 4*7)
   NumPut(hPipeWrite          , STARTUPINFO, A_PtrSize*4 + 4*8 + A_PtrSize*3)
   NumPut(hPipeWrite          , STARTUPINFO, A_PtrSize*4 + 4*8 + A_PtrSize*4)
   
   VarSetCapacity(PROCESS_INFORMATION, A_PtrSize*2 + 4*2, 0)
   
   pipeName := "AHK_" . A_TickCount
   for k, v in ["pipeGA", "pipe"]
      %v% := DllCall("CreateNamedPipe", "Str", "\\.\pipe\" . pipeName, params*)
   
   sCmd := A_AhkPath . " ""\\.\pipe\" . pipeName . """"
   for k, v in args
      sCmd .= " """ . v . """"
      
   if !DllCall("CreateProcess", "UInt", 0, "Str", sCmd, "UInt", 0, "UInt", 0, "Int", true, "UInt", CREATE_NO_WINDOW
                              , "UInt", 0, "UInt", 0, "Ptr", &STARTUPINFO, "Ptr", &PROCESS_INFORMATION)
   {
      DllCall("CloseHandle", "Ptr", hPipeRead)
      DllCall("CloseHandle", "Ptr", hPipeWrite)
      for k, v in ["pipeGA", "pipe"]
         DllCall("CloseHandle", "Ptr", %v%)
      throw "CreateProcess failed"
   }
   DllCall("CloseHandle", "Ptr", hPipeWrite)
   for k, v in ["pipeGA", "pipe"]
      DllCall("ConnectNamedPipe", "Ptr", %v%, "Ptr", 0)
   tempScript := BOM . script
   tempScriptSize := ( StrLen(tempScript) + 1 ) << !!A_IsUnicode
   DllCall("WriteFile", "Ptr", pipe, "Str", tempScript, "UInt", tempScriptSize, "UIntP", 0, "Ptr", 0)
   
   for k, v in ["pipeGA", "pipe"]
      DllCall("CloseHandle", "Ptr", %v%)
   
   VarSetCapacity(sTemp, 4096), nSize := 0
   while DllCall("ReadFile", "Ptr", hPipeRead, "Ptr", &sTemp, "UInt", 4096, "UIntP", nSize, "UInt", 0) {
      sOutput .= stdOut := StrGet(&sTemp, nSize, encoding)
      ( callBack && %callBack%(stdOut) )
   }
   DllCall("CloseHandle", "Ptr", NumGet(PROCESS_INFORMATION))
   DllCall("CloseHandle", "Ptr", NumGet(PROCESS_INFORMATION, A_PtrSize))
   DllCall("CloseHandle", "Ptr", hPipeRead)

   Return sOutput
}
doubledave22
Posts: 343
Joined: 08 Jun 2019, 17:36

Re: How to write to stdout from child script?

21 Sep 2021, 09:57

Pretty cool! It does seem to freeze up when making it persistent though. I can't seem to figure out how to plug it into the second script anyway. It seems you have to use dynarunreadstdout to initially run the second script? Is there a way to only change the second/child script to attach a separate debugger? I'd rather not have to do anything in the main script. I appreciate the help I will look for other options.

Code: Select all

script =
(
#Persistent
FileAppend, Auto, *
sleep, 1000
FileAppend, Hot, *
sleep, 1000
FileAppend, key, *
)
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: How to write to stdout from child script?

21 Sep 2021, 10:49

doubledave22 wrote: Is there a way to only change the second/child script to attach a separate debugger?
I think no way. But you still can use GUI to display any text you want from inside the script.
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: How to write to stdout from child script?  Topic is solved

25 Sep 2021, 04:51

teadrinker wrote:
20 Sep 2021, 21:00
Of course, the console can only be attached to one process, the child script is running as a separate process.
A console can be attached to any number of processes. This would attach the script's process to the console of the parent process, if it has one:

Code: Select all

DllCall("AttachConsole", "int", -1)

However, SciTE has no console; it has an output pane, which is just a Scintilla control. It is not "attached" to a child process. What SciTE most likely does is this:
  • Create pipes and pass their handles to CreateProcess, which is used to launch the child process.
  • Read the output pipe continuously, appending all output to the output pane.
It really doesn't matter which process writes to the output pipe. All you need to do is have your parent process pass its own stdout handle (which is a handle to the pipe SciTE gave it) to its child process. This can be achieved with CreateProcess:

Code: Select all

RunInheritHandles(cmd, workingDir:="") {
	VarSetCapacity(pi, 24, 0), VarSetCapacity(si, si_size := 32+9*A_PtrSize, 0)
	p := NumPut(si_size  , si)                        ; size of si
	p := NumPut(0x100    , p+3*A_PtrSize+28, "uint")  ; dwFlags = STARTF_USESTDHANDLES
    p += 2*A_PtrSize
    Loop 3
        p := NumPut(DllCall("GetStdHandle", "int", -9-A_Index, "ptr"), p+0)
	If !DllCall("CreateProcess", "ptr", 0, "ptr", &cmd, "ptr", 0, "ptr", 0
        , "int", true, "uint", 0, "ptr", 0, "ptr", workingDir!="" ? &workingDir : 0
        , "ptr", &si, "ptr", &pi)
		Return
    DllCall("CloseHandle", "ptr", NumGet(pi, 0))         ; hProcess
	DllCall("CloseHandle", "ptr", NumGet(pi, A_PtrSize)) ; hThread
}
I used the following to test it:

Code: Select all

if (!A_Args.Length()) {
    FileAppend PARENT`n, *
    cmd = "%A_AhkPath%" "%A_ScriptFullPath%" 1
    RunInheritHandles(cmd)
    MsgBox  ; SciTE will not show output after the parent process exits.
} else {
    FileAppend CHILD`n, *
}

It is also possible to directly manipulate the content of the output pane, knowing it is a Scintilla control:

Code: Select all

; Scintilla expects an 8-bit string (probably UTF-8?) even though
; EM_REPLACESEL should accept UTF-16 for Unicode apps such as SciTE.
VarSetCapacity(s8, StrPut(s, "UTF-8"), 1), StrPut(s, &s8, "UTF-8")
Control EditPaste, % s8, Scintilla2, ahk_class SciTEWindow
EditPaste uses EM_REPLACESEL internally. Because EM_REPLACESEL is a system-defined message, the system automatically "marshals" the string between processes.

The code above requires a Unicode version of AutoHotkey. If you use an ANSI version, the system will automatically "convert" the string from ANSI to UTF-16, even though Scintilla will try to interpret it as UTF-8. For a more complete solution, the SCI_ADDTEXT message can be used; but it requires the use of OpenProcess, VirtualAllocEx and WriteProcessMemory since the control is in an external process.
doubledave22
Posts: 343
Joined: 08 Jun 2019, 17:36

Re: How to write to stdout from child script?

25 Sep 2021, 09:35

thanks @lexikos! I was able to successfully use the EditPaste method which works nice except it can cut off fileappends from the parent script mid log (not ideal) since it's just pasting at will. A great solution if logging only needs to occur on child script though.

I have been staring at the other solution for a while and am a bit lost as to how to implement it.

Script 1 starts and loads it's GUI then runs script 2 which is located in a subfolder - one folder down from main script. Just trying to separate them so users won't accidentally run the second script from the main folder. Do I need to adjust the workingdir for RunInheritHandles?

Also do I need to use RunInheritHandles in place of the run command to load the child script? Past that, I am not sure what your test code is doing and how to implement it in my situation. When I run the test code in the main script it just restarts my script and outputs PARENT in the sci pane.

Ultimately I want to be able to use Fileappend, Some Text, * in both scripts and have them both show in the output pane on Sci. I apologize for the required hand holding! Thank you so much for the help.
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: How to write to stdout from child script?

25 Sep 2021, 21:00

doubledave22 wrote:
25 Sep 2021, 09:35
Also do I need to use RunInheritHandles in place of the run command to load the child script?
That is the only thing you need to do. Just replace Run with RunInheritHandles. Run's first two parameters should correspond directly to the function's parameters. However:
  • Options and OutputVarPID aren't supported (but could be).
  • The command must be executable, so if you were running the .ahk file itself, just prefix it with "%A_AhkPath%".
Each EditPaste call is effectively atomic; i.e. the entire string is passed to Scintilla and inserted into the control in one go. If you use EditPaste from both scripts, the content of any two EditPaste calls will not be mixed together, but will be one after the other. By contrast, a single FileAppend call may be broken up into multiple insertions.
doubledave22
Posts: 343
Joined: 08 Jun 2019, 17:36

Re: How to write to stdout from child script?

26 Sep 2021, 10:53

lexikos wrote:
  • Options and OutputVarPID aren't supported (but could be).
Ok, I have worked through replacing the run with RunInheritHandles and worked out the correct cmd but I think I will need OutputVarPID still.

From what I can see it seems to be in the pi (processinformation) variable

Code: Select all

hProcess := NumGet( pi, 0 )
msgbox, % hProcess 
but when I msgbox hProcess its a different value than the one I get in the child script with DllCall("GetCurrentProcessId"). Thanks again
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: How to write to stdout from child script?

08 Oct 2021, 20:21

The PROCESS_INFORMATION struct contains handles and IDs. You are retrieving the handle, not the ID.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: garry, Google [Bot], marypoppins_1, ShatterCoder and 128 guests