static unsafe int _RunConsole(Action <string> outAction, StringBuilder outStr, string exe, string args, string curDir, Encoding encoding, bool needLines) { exe = _NormalizeFile(true, exe, out _, out _); //args = pathname.expand(args); //rejected encoding ??= Console.OutputEncoding; //fast. Default is an internal type System.Text.OSEncoding that wraps API GetConsoleOutputCP. var decoder = encoding.GetDecoder(); //ensures we'll not get partial multibyte chars (UTF8 etc) at buffer end/start var ps = new ProcessStarter_(exe, args, curDir, rawExe: true); Handle_ hProcess = default; var sa = new Api.SECURITY_ATTRIBUTES(null) { bInheritHandle = 1 }; if (!Api.CreatePipe(out Handle_ hOutRead, out Handle_ hOutWrite, sa, 0)) { throw new AuException(0); } byte * b = null; //buffer before decoding char * c = null; //buffer after decoding StringBuilder sb = null; //holds part of line when buffer does not end with newline try { Api.SetHandleInformation(hOutRead, 1, 0); //remove HANDLE_FLAG_INHERIT ps.si.dwFlags |= Api.STARTF_USESTDHANDLES | Api.STARTF_USESHOWWINDOW; ps.si.hStdOutput = hOutWrite; ps.si.hStdError = hOutWrite; ps.flags |= Api.CREATE_NEW_CONSOLE; if (!ps.StartL(out var pi, inheritHandles: true)) { throw new AuException(0); } hOutWrite.Dispose(); //important: must be here pi.hThread.Dispose(); hProcess = pi.hProcess; //native console API allows any buffer size when writing, but wrappers usually use small buffer, eg .NET 4-5 KB, msvcrt 5 KB, C++ not tested const int bSize = 8000, cSize = bSize + 10; b = MemoryUtil.Alloc(bSize); for (bool skipN = false; ;) { if (Api.ReadFile(hOutRead, b, bSize, out int nr)) { if (nr == 0) { continue; } } else { if (lastError.code != Api.ERROR_BROKEN_PIPE) { throw new AuException(0); } //process ended if (sb != null && sb.Length > 0) { outAction(sb.ToString()); } break; } if (c == null) { c = MemoryUtil.Alloc <char>(cSize); } int nc = decoder.GetChars(b, nr, c, cSize, false); if (needLines) { var k = new Span <char>(c, nc); if (skipN) { skipN = false; if (c[0] == '\n' && nc > 0) { k = k[1..]; //\r\n split in 2 buffers
public bool WaitAndRead(WaitHandle hProcess, Action <string> results) { bool R = false; char *b = null; const int bLen = 7900; var ev = new ManualResetEvent(false); try { var ha = new WaitHandle[2] { ev, hProcess }; for (bool useSB = false; ; useSB = results == null) { var o = new Api.OVERLAPPED { hEvent = ev.SafeWaitHandle.DangerousGetHandle() }; if (!Api.ConnectNamedPipe(_hPipe, &o)) { int e = lastError.code; if (e != Api.ERROR_PIPE_CONNECTED) { if (e != Api.ERROR_IO_PENDING) { break; } int wr = WaitHandle.WaitAny(ha); if (wr != 0) { Api.CancelIo(_hPipe); R = true; break; } //task ended if (!Api.GetOverlappedResult(_hPipe, ref o, out _, false)) { Api.DisconnectNamedPipe(_hPipe); break; } } } if (b == null) { b = (char *)MemoryUtil.Alloc(bLen); } bool readOK; while (((readOK = Api.ReadFile(_hPipe, b, bLen, out int n, null)) || (lastError.code == Api.ERROR_MORE_DATA)) && n > 0) { n /= 2; if (!readOK) { useSB = true; } if (useSB) //rare { _sb ??= new StringBuilder(bLen); if (results == null && _s != null) { _sb.Append(_s); } _s = null; _sb.Append(b, n); } else { _s = new string(b, 0, n); } if (readOK) { if (results != null) { results(ResultString); _sb?.Clear(); } break; } //note: MSDN says must use OVERLAPPED with ReadFile too, but works without it. } Api.DisconnectNamedPipe(_hPipe); if (!readOK) { break; } } } finally { ev.Dispose(); MemoryUtil.Free(b); } return(R); }