I'd like to see this posted in the v2 Scripts forum as well, especially since @SKAN has showed a disinterest in updating RunCMD due to the flaws you fixed. I don't think he'd want to copy off you unless you gave him express permission to (though he might not want to even then).teadrinker wrote: ↑14 Jul 2023, 07:04I've tried to write code (v2) for asynchronous reading of stdout using a different approach. It turned out to be a bit cumbersome, but it lacks the above-mentioned disadvantage and is really non-blocking:Code: Select all
#Requires AutoHotkey v2 inst := '' F3:: ; F3 without timeout F4:: { ; F4 timeout 2 sec ReadOutput('', '') try global inst := AsyncStdoutReader('ping -n 8 google.com', ReadOutput, A_ThisHotkey = 'F3' ? unset : 2000) catch MethodError as e { MsgBox A_Clipboard := e.Stack MsgBox e.Message . '`n' . e.What . '`nLine: ' e.Line ExitApp } MsgBox 'AsyncStdoutReader is non-blocking', 'test', 0x1000 . ' T2' } ReadOutput(line, complete := 0) { global inst static EM_SETSEL := 0xB1, myGui := '', text := '', edit := '' if !myGui { myGui := Gui('+Resize', 'Async reading of stdout') myGui.MarginX := myGui.MarginY := 0 myGui.SetFont('s12', 'Consolas') myGui.AddText('x10 y10', 'Complete: ') text := myGui.AddText('x+5 yp w100', 'false') edit := myGui.AddEdit('xm y+10 w650 h500') edit.GetPos(, &y := unset) myGui.OnEvent('Size', (o, m, w, h) => edit.Move(,, w, h - y)) myGui.OnEvent('Close', (*) => ExitApp()) myGui.Show() } (line = '' && complete = '' && edit.Value := '') text.Value := complete = -1 ? 'timed out' : complete = false ? 'false' : 'true' SendMessage EM_SETSEL, -2, -1, edit EditPaste line, edit if inst && complete { outData := inst.outData, inst := '' MsgBox outData, 'Complete stdout', 0x2040 } } class AsyncStdoutReader { __New(cmd, callback?, timeout?, encoding?) { encoding := encoding ?? 'cp' . DllCall('GetOEMCP', 'UInt') this.event := %this.__Class%.Event() this.params := { buf: Buffer(4096, 0), overlapped: Buffer(A_PtrSize * 3 + 8, 0), hEvent: this.event.handle, outData: '', encoding: encoding, complete: false } (IsSet(callback) && this.params.callback := callback) if IsSet(timeout) { this.params.timeout := timeout this.params.startTime := A_TickCount } this.process := %this.__Class%.Process(cmd, this.params) this.signal := %this.__Class%.EventSignal(this.process, this.params) this.process.Read() } processID => this.process.PID complete => this.params.complete outData => this.params.outData __Delete() { DllCall('CancelIoEx', 'Ptr', this.process.hPipeRead, 'Ptr', this.params.overlapped) this.event.Set() this.signal.Clear() this.process.Clear() this.params.buf.Size := 0 this.params.outData := '' } class Event { __New() => this.handle := DllCall('CreateEvent', 'Int', 0, 'Int', 0, 'Int', 0, 'Int', 0, 'Ptr') __Delete() => DllCall('CloseHandle', 'Ptr', this.handle) Set() => DllCall('SetEvent', 'Ptr', this.handle) } class Process { __New(cmd, info) { this.info := info this.CreatePipes() if !this.PID := this.CreateProcess(cmd) { throw OSError('Failed to create process') } } CreatePipes() { static FILE_FLAG_OVERLAPPED := 0x40000000, PIPE_ACCESS_INBOUND := 0x1 , pipeMode := (PIPE_TYPE_BYTE := 0) | (PIPE_WAIT := 0) , GENERIC_WRITE := 0x40000000, OPEN_EXISTING := 0x3 , FILE_ATTRIBUTE_NORMAL := 0x80, HANDLE_FLAG_INHERIT := 0x1 this.hPipeRead := DllCall('CreateNamedPipe', 'Str', pipeName := '\\.\pipe\StdOut_' . A_TickCount, 'UInt', PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, 'UInt', pipeMode, 'UInt', 1, 'UInt', this.info.buf.Size, 'UInt', this.info.buf.Size, 'UInt', 120000, 'Ptr', 0, 'Ptr') this.hPipeWrite := DllCall('CreateFile', 'Str', pipeName, 'UInt', GENERIC_WRITE, 'UInt', 0, 'Ptr', 0, 'UInt', OPEN_EXISTING, 'UInt', FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 'Ptr', 0, 'Ptr') DllCall('SetHandleInformation', 'Ptr', this.hPipeWrite, 'UInt', HANDLE_FLAG_INHERIT, 'UInt', HANDLE_FLAG_INHERIT) } CreateProcess(cmd) { static STARTF_USESTDHANDLES := 0x100, CREATE_NO_WINDOW := 0x8000000 STARTUPINFO := Buffer(siSize := A_PtrSize * 9 + 4 * 8, 0) NumPut('UInt', siSize, STARTUPINFO) NumPut('UInt', STARTF_USESTDHANDLES, STARTUPINFO, A_PtrSize * 4 + 4 * 7) NumPut('Ptr', this.hPipeWrite, 'Ptr', this.hPipeWrite, STARTUPINFO, siSize - A_PtrSize * 2) PROCESS_INFORMATION := Buffer(A_PtrSize * 2 + 4 * 2, 0) if !DllCall('CreateProcess', 'Ptr', 0, 'Str', cmd, 'Ptr', 0, 'Ptr', 0, 'UInt', true, 'UInt', CREATE_NO_WINDOW, 'Ptr', 0, 'Ptr', 0, 'Ptr', STARTUPINFO, 'Ptr', PROCESS_INFORMATION) Return this.Clear() DllCall('CloseHandle', 'Ptr', this.hPipeWrite), this.hPipeWrite := 0 Return PID := NumGet(PROCESS_INFORMATION, A_PtrSize * 2, 'UInt') } Read() { buf := this.info.buf, overlapped := this.info.overlapped overlapped.__New(overlapped.Size, 0) NumPut('Ptr', this.info.hEvent, overlapped, A_PtrSize * 2 + 8) bool := DllCall('ReadFile', 'Ptr', this.hPipeRead, 'Ptr', buf, 'UInt', buf.Size, 'UIntP', &size := 0, 'Ptr', overlapped) if bool { this.info.outData .= str := StrGet(buf, size, this.info.encoding) (this.info.HasProp('callback') && SetTimer(this.info.callback.Bind(str), -10)) this.Read() } else if !bool && A_LastError != ERROR_IO_PENDING := 997 { this.info.complete := true (this.info.HasProp('callback') && SetTimer(this.info.callback.Bind('', true), -10)) } } Clear() { DllCall('CloseHandle', 'Ptr', this.hPipeRead) (this.hPipeWrite && DllCall('CloseHandle', 'Ptr', this.hPipeWrite)) } } class EventSignal { __New(stdOut, info) { this.info := info this.stdOut := stdOut this.onEvent := ObjBindMethod(this, 'Signal') timeout := info.HasProp('timeout') ? info.timeout : -1 this.regWait := this.RegisterWaitCallback(this.info.hEvent, this.onEvent, timeout) } Signal(handle, timedOut) { if timedOut { (this.info.HasProp('callback') && SetTimer(this.info.callback.Bind('', -1), -10)) return } if !DllCall('GetOverlappedResult', 'Ptr', handle, 'Ptr', this.info.overlapped, 'UIntP', &size := 0, 'UInt', false) { (this.info.HasProp('callback') && SetTimer(this.info.callback.Bind('', true), -10)) return this.info.complete := true } this.info.outData .= str := StrGet(this.info.buf, size, this.info.encoding) (this.info.HasProp('callback') && SetTimer(this.info.callback.Bind(str), -10)) this.stdOut.Read() timeout := this.info.HasProp('timeout') ? this.info.timeout - A_TickCount + this.info.startTime : -1 this.regWait := this.RegisterWaitCallback(this.info.hEvent, this.onEvent, timeout) } Clear() { this.regWait.Unregister() this.DeleteProp('regWait') this.DeleteProp('onEvent') } RegisterWaitCallback(handle, callback, timeout := -1) { ; by lexikos https://www.autohotkey.com/boards/viewtopic.php?t=110691 static waitCallback, postMessageW, wnd, nmsg := 0x5743 if !IsSet(waitCallback) { if A_PtrSize = 8 { NumPut('int64', 0x8BCAB60F44C18B48, 'int64', 0x498B48C18B4C1051, 'int64', 0x20FF4808, waitCallback := Buffer(24)) } else { NumPut('int64', 0x448B50082444B60F, 'int64', 0x70FF0870FF500824, 'int64', 0x0008C2D0FF008B04, waitCallback := Buffer(24)) } DllCall('VirtualProtect', 'ptr', waitCallback, 'ptr', 24, 'uint', 0x40, 'uint*', 0) postMessageW := DllCall('GetProcAddress', 'ptr', DllCall('GetModuleHandle', 'str', 'user32', 'ptr'), 'astr', 'PostMessageW', 'ptr') wnd := Gui(), DllCall('SetParent', 'ptr', wnd.hwnd, 'ptr', -3) ; HWND_MESSAGE = -3 OnMessage(nmsg, messaged, 255) } NumPut('ptr', postMessageW, 'ptr', wnd.hwnd, 'uptr', nmsg, param := %StrSplit(this.__Class, '.')[1]%.EventSignal.RegisteredWait()) NumPut('ptr', ObjPtr(param), param, A_PtrSize * 3) param.callback := callback, param.handle := handle if !DllCall('RegisterWaitForSingleObject', 'ptr*', &waitHandle := 0, 'ptr', handle, 'ptr', waitCallback, 'ptr', param, 'uint', timeout, 'uint', 8) throw OSError() param.waitHandle := waitHandle, param.locked := ObjPtrAddRef(param) return param static messaged(wParam, lParam, nmsg, hwnd) { if hwnd = wnd.hwnd { local param := ObjFromPtrAddRef(NumGet(wParam + A_PtrSize * 3, 'ptr')) (param.callback)(param.handle, lParam) param._unlock() } } } class RegisteredWait extends Buffer { static prototype.waitHandle := 0, prototype.locked := 0 __new() => super.__new(A_PtrSize * 5, 0) __delete() => this.Unregister() _unlock() { (p := this.locked) && (this.locked := 0, ObjRelease(p)) } Unregister() { wh := this.waitHandle, this.waitHandle := 0 (wh) && DllCall('UnregisterWaitEx', 'ptr', wh, 'ptr', -1) this._unlock() } } } }
RunCMD() v0.97 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.
-
- Posts: 25
- Joined: 18 Apr 2019, 06:24
Re: RunCMD() v0.96 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.
-
- Posts: 4368
- Joined: 29 Mar 2015, 09:41
- Contact:
Re: RunCMD() v0.97 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.
Since there are so many requests, I'll post it of these days.
Re: RunCMD() v0.97 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.
Thanks for this class @teadrinker.
Would you say the following code is a minimal working example of how you intend the class to be used to get stdout?
Code: Select all
inst := AsyncStdoutReader("C:\rust\p1\src\main.exe hullo", (line, complete := 0){
if inst && complete {
outData := inst.outData
inst := ''
MsgBox outData ; do things with stdout
}
})
-
- Posts: 4368
- Joined: 29 Mar 2015, 09:41
- Contact:
Re: RunCMD() v0.97 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.
This is not v2.0 syntax, so it is not a working example.bonobo wrote: ↑Would you say the following code is a minimal working example of how you intend the class to be used to get stdout?Code: Select all
AsyncStdoutReader("C:\rust\p1\src\main.exe hullo", (line, complete := 0){
At least in v2.0 they are not.
Also, AsyncStdoutReader is a class, not a function, how would you use promis here?
-
- Posts: 4368
- Joined: 29 Mar 2015, 09:41
- Contact:
Re: RunCMD() v0.97 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.
@bonobo
As an option:
As an option:
Code: Select all
inst := [AsyncStdoutReader('ping google.com', (line, complete := 0) => (
complete && (outData := inst[1].outData, inst[1] := '', MsgBox(outData))
))]
Re: RunCMD() v0.97 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.
@bonobo
https://github.com/thqby/ahk2_lib/blob/master/Promise.ahk
I have implemented ahk's promise, but i don't think this is suitable for streaming callbacks.
The above is a valid v2.1 grammar.
https://github.com/thqby/ahk2_lib/blob/master/Promise.ahk
I have implemented ahk's promise, but i don't think this is suitable for streaming callbacks.
Code: Select all
((...){
})
Re: RunCMD() v0.97 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.
Thanks @teadrinker. I was using 2.1 alpha hence the function def expression. Your class works there without a hitch and has very low latency, from my testing so far.teadrinker wrote: ↑24 Oct 2023, 19:43@bonobo
As an option:Code: Select all
inst := [AsyncStdoutReader('ping google.com', (line, complete := 0) => ( complete && (outData := inst[1].outData, inst[1] := '', MsgBox(outData)) ))]
Thanks @thqby. I thought I have vague memory of such a class from when I dug into ahk2lib last time!thqby wrote: ↑25 Oct 2023, 01:05@bonobo
https://github.com/thqby/ahk2_lib/blob/master/Promise.ahk
I have implemented ahk's promise, but i don't think this is suitable for streaming callbacks.
The above is a valid v2.1 grammar.Code: Select all
((...){ })
-
- Posts: 4368
- Joined: 29 Mar 2015, 09:41
- Contact:
Re: RunCMD() v0.97 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.
Created a topic in Scripts and Functions (v2), please ask questions there.
Re: RunCMD() v0.97 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.
Hello, I need to enable the hidden windows command line. Execute the command, enter the password and confirm it. Finally get the output from the console to the variable.
Is this doable using RunCmd? The topic I created about this. viewtopic.php?style=17&f=76&t=128671
Is this doable using RunCmd? The topic I created about this. viewtopic.php?style=17&f=76&t=128671
Re: RunCMD() v0.97 : Capture stdout to variable. Non-blocking version. Pre-process/omit individual lines.
this is impossible using stdin
https://github.com/paulej/AESCrypt/blob/2997c62594ff08cf545b34b9035a8fe07e2d4c80/Windows/Console/password.c#L92
https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/getch-getwch
anyone knows how to send to console ? (of child process)
https://github.com/paulej/AESCrypt/blob/2997c62594ff08cf545b34b9035a8fe07e2d4c80/Windows/Console/password.c#L92
https://stackoverflow.com/questions/51803083/child-process-via-createprocess-stalls-on-getch-with-redirected-stdout-and-sand error in _getch call - The _getch and _getwch functions read a single character from the console - console ! but not redirected stdin –
RbMm
Aug 11, 2018 at 21:46
https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/getch-getwch
anyone knows how to send to console ? (of child process)