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
static unsafe int _RunConsole(Action <string> outAction, StringBuilder outStr, string exe, string args, string curDir, Encoding encoding) { exe = _NormalizeFile(true, exe, out _, out _); //args = APath.ExpandEnvVar(args); //rejected 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; char *c = null; 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.StartLL(out var pi, inheritHandles: true)) { throw new AuException(0); } hOutWrite.Dispose(); //important: must be here pi.hThread.Dispose(); hProcess = pi.hProcess; //variables for 'prevent getting partial lines' bool needLines = outStr == null /*&& !flags.Has(RCFlags.RawText)*/; int offs = 0; bool skipN = false; int bSize = 8000; b = (byte *)AMemory.Alloc(bSize); for (bool ended = false; !ended;) { if (bSize - offs < 1000) //part of 'prevent getting partial lines' code { b = (byte *)AMemory.ReAlloc(b, bSize *= 2); AMemory.Free(c); c = null; } if (Api.ReadFile(hOutRead, b + offs, bSize - offs, out int nr)) { if (nr == 0) { continue; } nr += offs; } else { if (ALastError.Code != Api.ERROR_BROKEN_PIPE) { throw new AuException(0); } //process ended if (offs == 0) { break; } nr = offs; offs = 0; ended = true; } //prevent getting partial lines. They can be created by the console program, or by the above code when buffer too small. int moveFrom = 0; if (needLines) { if (skipN) //if was split between \r and \n, remove \n now { skipN = false; if (b[0] == '\n') { Api.memmove(b, b + 1, --nr); } if (nr == 0) { continue; } } int i; for (i = nr; i > 0; i--) { var k = b[i - 1]; if (k == '\n' || k == '\r') { break; } } if (i == nr) //ends with \n or \r { offs = 0; if (b[--nr] == '\r') { skipN = true; } else if (nr > 0 && b[nr - 1] == '\r') { nr--; } } else if (i > 0) //contains \n or \r { moveFrom = i; offs = nr - i; if (b[--i] == '\n' && i > 0 && b[i - 1] == '\r') { i--; } nr = i; } else if (!ended) { offs = nr; continue; } } if (c == null) { c = (char *)AMemory.Alloc(bSize * 2); } if (encoding == null) { if ((encoding = s_oemEncoding) == null) { Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); var oemCP = Api.GetOEMCP(); try { encoding = Encoding.GetEncoding(oemCP); } catch { encoding = Encoding.GetEncoding(437); } s_oemEncoding = encoding; } } int nc = encoding.GetChars(b, nr, c, bSize); if (moveFrom > 0) { Api.memmove(b, b + moveFrom, offs); //part of 'prevent getting partial lines' code } var s = new string(c, 0, nc); if (needLines) { if (s.FindAny("\r\n") < 0) { outAction(s); } else { foreach (var k in s.Segments(SegSep.Line)) { outAction(s[k.start..k.end]);