1// tProcess.cpp 
2// 
3// This module contains a class for spawning other processes and receiving their exit-codes as well as some simple 
4// commands for spawning one or many processes at once. Windows platform only. 
5// 
6// Copyright (c) 2005, 2017, 2019, 2020, 2022, 2023 Tristan Grimmer. 
7// Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby 
8// granted, provided that the above copyright notice and this permission notice appear in all copies. 
9// 
10// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL 
11// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
12// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 
13// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
14// PERFORMANCE OF THIS SOFTWARE. 
15 
16#include <System/tThrow.h> 
17#include <System/tPrint.h> 
18#include <System/tTime.h> 
19#include <Foundation/tFundamentals.h> 
20#include "Pipeline/tProcess.h" 
21using namespace tPipeline
22 
23 
24// @todo Currently the process stuff, due to the output stream messaging, is windows only. I'm sure with modern C++ 
25// this can be abstracted to be multiplatform. 
26#ifdef PLATFORM_WINDOWS 
27 
28 
29tProcess::tProcess(const tString& cmdLine, const tString& workDir, WinHwnd parent, uint32 userData, bool clearEnvironmentVars, int numEnvPairs, ...) : 
30 Parent(parent), 
31 OutputString(nullptr), 
32 PrintCallback(nullptr), 
33 PrintCallbackUserPointer(nullptr), 
34 ExitCallback(nullptr), 
35 ExitCallbackUserPointer(nullptr), 
36 ChildProcess(0), 
37 ChildThread(0), 
38 MonitorProcessExitThread(0), 
39 MonitorProcessStdOutThread(0), 
40 MonitorProcessStdErrThread(0), 
41 StdOutRead(0), 
42 StdOutWrite(0), 
43 StdErrRead(0), 
44 StdErrWrite(0), 
45 UserData(userData), 
46 ExitCode(nullptr), 
47 ClearEnvironment(clearEnvironmentVars), 
48 WaitInDestructor(true), 
49 Environment(nullptr
50
51 if (numEnvPairs > 0
52
53 va_list args; 
54 va_start(args, numEnvPairs); 
55 Environment = tProcess::BuildNewEnvironmentData_Ascii( !clearEnvironmentVars, numEnvPairs, args ); 
56 va_end(args); 
57
58 
59 CreateChildProcess(cmdLine, workDir); 
60
61 
62 
63tProcess::tProcess(const tString& cmdLine, const tString& workDir, WinHwnd parent, uint32 userData, bool clearEnvironmentVars, int numEnvPairs, va_list args) : 
64 Parent(parent), 
65 OutputString(nullptr), 
66 PrintCallback(nullptr), 
67 PrintCallbackUserPointer(nullptr), 
68 ExitCallback(nullptr), 
69 ExitCallbackUserPointer(nullptr), 
70 ChildProcess(0), 
71 ChildThread(0), 
72 MonitorProcessExitThread(0), 
73 MonitorProcessStdOutThread(0), 
74 MonitorProcessStdErrThread(0), 
75 StdOutRead(0), 
76 StdOutWrite(0), 
77 StdErrRead(0), 
78 StdErrWrite(0), 
79 UserData(userData), 
80 ExitCode(nullptr), 
81 ClearEnvironment(clearEnvironmentVars), 
82 WaitInDestructor(true), 
83 Environment(nullptr
84
85 if (numEnvPairs > 0
86 Environment = tProcess::BuildNewEnvironmentData_Ascii( !clearEnvironmentVars, numEnvPairs, args ); 
87 
88 CreateChildProcess(cmdLine, workDir); 
89
90 
91 
92tProcess::tProcess(const tString& cmdLine, const tString& workDir, WinHandle& waitHandle, ulong* exitCode, bool clearEnvironmentVars) : 
93 Parent(0), 
94 OutputString(nullptr), 
95 PrintCallback(nullptr), 
96 PrintCallbackUserPointer(nullptr), 
97 ExitCallback(nullptr), 
98 ExitCallbackUserPointer(nullptr), 
99 ChildProcess(0), 
100 ChildThread(0), 
101 MonitorProcessExitThread(0), 
102 MonitorProcessStdOutThread(0), 
103 MonitorProcessStdErrThread(0), 
104 StdOutRead(0), 
105 StdOutWrite(0), 
106 StdErrRead(0), 
107 StdErrWrite(0), 
108 UserData(0), 
109 ExitCode(exitCode), 
110 ClearEnvironment(clearEnvironmentVars), 
111 
112 // For this non-blocking constructor, the user of the object is required to wait on the handle! 
113 WaitInDestructor(false), 
114 Environment(nullptr
115
116 CreateChildProcess(cmdLine, workDir); 
117 waitHandle = MonitorProcessExitThread; 
118
119 
120 
121tProcess::tProcess(const tString& cmdLine, const tString& workDir, tString& output, ulong* exitCode, bool clearEnvironmentVars, int numEnvPairs, ...) : 
122 Parent(0), 
123 OutputString(&output), 
124 PrintCallback(nullptr), 
125 PrintCallbackUserPointer(nullptr), 
126 ExitCallback(nullptr), 
127 ExitCallbackUserPointer(nullptr), 
128 ChildProcess(0), 
129 ChildThread(0), 
130 MonitorProcessExitThread(0), 
131 MonitorProcessStdOutThread(0), 
132 MonitorProcessStdErrThread(0), 
133 StdOutRead(0), 
134 StdOutWrite(0), 
135 StdErrRead(0), 
136 StdErrWrite(0), 
137 UserData(0), 
138 ExitCode(exitCode), 
139 ClearEnvironment(clearEnvironmentVars), 
140 WaitInDestructor(false), 
141 Environment(nullptr
142
143 if (numEnvPairs > 0
144
145 va_list args; 
146 va_start(args, numEnvPairs); 
147 Environment = tProcess::BuildNewEnvironmentData_Ascii( !clearEnvironmentVars, numEnvPairs, args ); 
148 va_end(args); 
149
150 
151 CreateChildProcess(cmdLine, workDir); 
152 
153 // Wait for exit threads to finish. After this point we can be sure that the exitCode has been 
154 // updated if it was non-null. Note: The exit thread waits for the other threads to finish. 
155 // Since we are waiting here (blocking), no need to wait in destructor. 
156 WaitForSingleObject(MonitorProcessExitThread, INFINITE); 
157
158 
159 
160tProcess::tProcess(const tString& cmdLine, const tString& workDir, tString& output, ulong* exitCode, bool clearEnvironmentVars, int numEnvPairs , va_list args) : 
161 Parent(0), 
162 OutputString(&output), 
163 PrintCallback(nullptr), 
164 PrintCallbackUserPointer(nullptr), 
165 ExitCallback(nullptr), 
166 ExitCallbackUserPointer(nullptr), 
167 ChildProcess(0), 
168 ChildThread(0), 
169 MonitorProcessExitThread(0), 
170 MonitorProcessStdOutThread(0), 
171 MonitorProcessStdErrThread(0), 
172 StdOutRead(0), 
173 StdOutWrite(0), 
174 StdErrRead(0), 
175 StdErrWrite(0), 
176 UserData(0), 
177 ExitCode(exitCode), 
178 ClearEnvironment(clearEnvironmentVars), 
179 WaitInDestructor(false), 
180 Environment(nullptr
181
182 if (numEnvPairs > 0
183 Environment = tProcess::BuildNewEnvironmentData_Ascii( !clearEnvironmentVars, numEnvPairs, args ); 
184 
185 CreateChildProcess(cmdLine, workDir); 
186 
187 // Wait for exit threads to finish. After this point we can be sure that the exitCode has been 
188 // updated if it was non-null. Note: The exit thread waits for the other threads to finish. 
189 // Since we are waiting here (blocking), no need to wait in destructor. 
190 WaitForSingleObject(MonitorProcessExitThread, INFINITE); 
191
192 
193 
194tProcess::tProcess(const tString& cmdLine, const tString& workDir, ulong* exitCode, bool clearEnvironmentVars, int numEnvPairs, ...) : 
195 Parent(0), 
196 OutputString(nullptr), 
197 PrintCallback(nullptr), 
198 PrintCallbackUserPointer(nullptr), 
199 ExitCallback(nullptr), 
200 ExitCallbackUserPointer(nullptr), 
201 ChildProcess(0), 
202 ChildThread(0), 
203 MonitorProcessExitThread(0), 
204 MonitorProcessStdOutThread(0), 
205 MonitorProcessStdErrThread(0), 
206 StdOutRead(0), 
207 StdOutWrite(0), 
208 StdErrRead(0), 
209 StdErrWrite(0), 
210 UserData(0), 
211 ExitCode(exitCode), 
212 ClearEnvironment(clearEnvironmentVars), 
213 WaitInDestructor(false), 
214 Environment(nullptr
215
216 if (numEnvPairs > 0
217
218 va_list args; 
219 va_start(args, numEnvPairs); 
220 Environment = tProcess::BuildNewEnvironmentData_Ascii( !clearEnvironmentVars, numEnvPairs, args ); 
221 va_end(args); 
222
223 
224 CreateChildProcess(cmdLine, workDir); 
225 
226 // Wait for exit threads to finish. After this point we can be sure that the exitCode has been 
227 // updated if it was non-null. Note: The exit thread waits for the other threads to finish. 
228 // Since we are waiting here (blocking), no need to wait in destructor. 
229 WaitForSingleObject(MonitorProcessExitThread, INFINITE); 
230
231 
232 
233tProcess::tProcess(const tString& cmdLine, const tString& workDir, ulong* exitCode, bool clearEnvironmentVars, int numEnvPairs, va_list args) : 
234 Parent(0), 
235 OutputString(nullptr), 
236 PrintCallback(nullptr), 
237 PrintCallbackUserPointer(nullptr), 
238 ExitCallback(nullptr), 
239 ExitCallbackUserPointer(nullptr), 
240 ChildProcess(0), 
241 ChildThread(0), 
242 MonitorProcessExitThread(0), 
243 MonitorProcessStdOutThread(0), 
244 MonitorProcessStdErrThread(0), 
245 StdOutRead(0), 
246 StdOutWrite(0), 
247 StdErrRead(0), 
248 StdErrWrite(0), 
249 UserData(0), 
250 ExitCode(exitCode), 
251 ClearEnvironment(clearEnvironmentVars), 
252 WaitInDestructor(false), 
253 Environment(nullptr
254
255 if (numEnvPairs > 0
256 Environment = tProcess::BuildNewEnvironmentData_Ascii( !clearEnvironmentVars, numEnvPairs, args ); 
257 
258 CreateChildProcess(cmdLine, workDir); 
259 
260 // Wait for exit threads to finish. After this point we can be sure that the exitCode has been 
261 // updated if it was non-null. Note: The exit thread waits for the other threads to finish. 
262 // Since we are waiting here (blocking), no need to wait in destructor. 
263 WaitForSingleObject(MonitorProcessExitThread, INFINITE); 
264
265 
266 
267tProcess::tProcess(const tString& cmdLine, const tString& workDir, ulong* exitCode, tPrintCallback pc, void* user) : 
268 Parent(0), 
269 OutputString(nullptr), 
270 PrintCallback(pc), 
271 PrintCallbackUserPointer(user), 
272 ExitCallback(nullptr), 
273 ExitCallbackUserPointer(nullptr), 
274 ChildProcess(0), 
275 ChildThread(0), 
276 MonitorProcessExitThread(0), 
277 MonitorProcessStdOutThread(0), 
278 MonitorProcessStdErrThread(0), 
279 StdOutRead(0), 
280 StdOutWrite(0), 
281 StdErrRead(0), 
282 StdErrWrite(0), 
283 UserData(0), 
284 ExitCode(exitCode), 
285 ClearEnvironment(true), 
286 WaitInDestructor(false), 
287 Environment(nullptr
288
289 CreateChildProcess(cmdLine, workDir); 
290 WaitForSingleObject(MonitorProcessExitThread, INFINITE); 
291
292 
293 
294tProcess::tProcess(const tString& cmd, const tString& wd, tExitCallback ec, void* ecud, tPrintCallback pc, void* pcud) : 
295 Parent(0), 
296 OutputString(nullptr), 
297 PrintCallback(pc), 
298 PrintCallbackUserPointer(pcud), 
299 ExitCallback(ec), 
300 ExitCallbackUserPointer(ecud), 
301 ChildProcess(0), 
302 ChildThread(0), 
303 MonitorProcessExitThread(0), 
304 MonitorProcessStdOutThread(0), 
305 MonitorProcessStdErrThread(0), 
306 StdOutRead(0), 
307 StdOutWrite(0), 
308 StdErrRead(0), 
309 StdErrWrite(0), 
310 UserData(0), 
311 ExitCode(nullptr), 
312 ClearEnvironment(true), 
313 WaitInDestructor(true), 
314 Environment(nullptr
315
316 CreateChildProcess(cmd, wd); 
317
318 
319 
320tProcess::tProcess(const tString& cmd, const tString& wd) : 
321 Parent(0), 
322 OutputString(nullptr), 
323 PrintCallback(nullptr), 
324 PrintCallbackUserPointer(nullptr), 
325 ExitCallback(nullptr), 
326 ExitCallbackUserPointer(nullptr), 
327 ChildProcess(0), 
328 ChildThread(0), 
329 MonitorProcessExitThread(0), 
330 MonitorProcessStdOutThread(0), 
331 MonitorProcessStdErrThread(0), 
332 StdOutRead(0), 
333 StdOutWrite(0), 
334 StdErrRead(0), 
335 StdErrWrite(0), 
336 UserData(0), 
337 ExitCode(nullptr), 
338 ClearEnvironment(true), 
339 WaitInDestructor(false), 
340 Environment(nullptr
341
342 bool detached = true
343 CreateChildProcess(cmd, wd, detached); 
344
345 
346 
347void tProcess::CreateChildProcess(const tString& cmdLine, const tString& workingDir, bool detached) 
348
349 if (ExitCode) 
350 *ExitCode = 0
351 
352 // Create the pipes we'll be using.  
353 WinSecurityAttributes secAttr; 
354 secAttr.nLength = sizeof(WinSecurityAttributes);  
355 
356 // Set the bInheritHandle flag so pipe handles are inherited. 
357 secAttr.bInheritHandle = detached ? WinFalse : WinTrue; 
358 secAttr.lpSecurityDescriptor = 0
359 
360 // By setting the pipe size to be small, we get the process output much sooner 
361 // since it can't collect in the pipe. 
362 if (!detached) 
363
364 const int suggestedPipeSizeBytes = 32
365 WinBool success = CreatePipe(&StdOutRead, &StdOutWrite, &secAttr, suggestedPipeSizeBytes); 
366 if (!success) 
367
368 if (ExitCode) 
369 *ExitCode = 1
370 throw tError("Can not create child pipe."); 
371
372 
373 success = CreatePipe(&StdErrRead, &StdErrWrite, &secAttr, suggestedPipeSizeBytes); 
374 if (!success) 
375
376 if (ExitCode) 
377 *ExitCode = 1
378 throw tError("Can not create child pipe."); 
379
380
381 
382 // Create the Child process. 
383 WinStartupInfo startup; 
384 startup.cb = sizeof(WinStartupInfo); 
385 startup.lpReserved = 0
386 startup.lpDesktop = 0
387 startup.lpTitle = 0
388 startup.dwX = 0
389 startup.dwY = 0
390 startup.dwXSize = 0
391 startup.dwYSize = 0
392 startup.dwXCountChars = 0
393 startup.dwYCountChars = 0
394 startup.dwFillAttribute = 0
395 startup.dwFlags = detached ? 0 : WinStartFUseStdHandles; 
396 startup.wShowWindow = 0
397 startup.cbReserved2 = 0
398 startup.lpReserved2 = 0
399 startup.hStdInput = 0
400 startup.hStdOutput = detached ? 0 : StdOutWrite; 
401 startup.hStdError = detached ? 0 : StdErrWrite; 
402 
403 // This struct gets filled out. 
404 WinProcessInformation procInfo; 
405 
406 // The environment block sets the environment variables for the command. Here we make sure that there are 
407 // effectively none of them if the user requested such. This ensures that the running bahaviour is identical on any 
408 // machine no matter what system env variables might be set. A null env block (Environment) means inherit from the 
409 // parent process which is what we are trying to avoid in some cases. 
410 const char* envBlock = Environment; 
411 
412 // Assign a default env block in this case. We could probably call it with "\0\0", but this seems safer as the 
413 // windows docs to not explicitely mention passing in an empty block. 
414 if (ClearEnvironment) 
415 envBlock = "PIPELINE=true\0"
416 
417 // Note that the environment block (arg 7) is allowed to contain non-UTF16 characters (ANSI according to the MS docs). 
418 #ifdef TACENT_UTF16_API_CALLS 
419 tStringUTF16 cmdLineUTF16(cmdLine); 
420 tStringUTF16 workingDirUTF16(workingDir); 
421 int success = CreateProcess(0, cmdLineUTF16.GetLPWSTR(), 0, 0, WinTrue, WinDetachedProcess, (char*)envBlock, workingDirUTF16.GetLPWSTR(), &startup, &procInfo); 
422 #else 
423 int success = CreateProcess(0, (char*)cmdLine.Chr(), 0, 0, WinTrue, WinDetachedProcess, (char*)envBlock, workingDir.Chr(), &startup, &procInfo); 
424 #endif 
425 if (!success) 
426
427 ulong lastError = GetLastError(); 
428 if (ExitCode) 
429 *ExitCode = 1
430 
431 throw tError("CreateProcess failed with %d (0x%08X). Possibly due to windows security settings or invalid working dir.", lastError, lastError); 
432
433 ChildProcess = procInfo.hProcess; 
434 ChildThread = procInfo.hThread; 
435 
436 if (detached) 
437
438 CloseHandle(ChildProcess); 
439 ChildProcess = 0
440 CloseHandle(ChildThread); 
441 ChildThread = 0
442
443 else 
444
445 // Create Monitor threads. These ones send the messages or print the output. 
446 ulong threadID; 
447 MonitorProcessExitThread = CreateThread(0, 0, tProcess::MonitorExitBridge, this, 0, &threadID); 
448 MonitorProcessStdOutThread = CreateThread(0, 0, tProcess::MonitorStdOutBridge, this, 0, &threadID); 
449 MonitorProcessStdErrThread = CreateThread(0, 0, tProcess::MonitorStdErrBridge, this, 0, &threadID); 
450
451
452 
453 
454void tProcess::Terminate() 
455
456 if (!ChildProcess) 
457 return
458 
459 uint32 exitCode = 23
460 WinBool success = TerminateProcess(ChildProcess, exitCode); 
461 
462 // Note we do NOT set ChildProcess to 0 here. The MonitorExit thread should handle this for us. 
463
464 
465 
466void tProcess::TerminateHard() 
467
468 if (!ChildProcess) 
469 return
470 
471 uint32 exitCode = 23
472 WinBool success = TerminateProcess(ChildProcess, exitCode); 
473 
474 Parent = 0
475 WaitForSingleObject(MonitorProcessExitThread, INFINITE); 
476 WaitInDestructor = false
477 
478 if (MonitorProcessExitThread) 
479
480 CloseHandle(MonitorProcessExitThread); 
481 MonitorProcessExitThread = 0
482
483 
484 if (MonitorProcessStdOutThread) 
485
486 CloseHandle(MonitorProcessStdOutThread); 
487 MonitorProcessStdOutThread = 0
488
489 
490 if (MonitorProcessStdErrThread) 
491
492 CloseHandle(MonitorProcessStdErrThread); 
493 MonitorProcessStdErrThread = 0
494
495 
496 if (StdOutRead) 
497
498 CloseHandle(StdOutRead); 
499 StdOutRead = 0
500
501 
502 if (StdOutWrite) 
503
504 CloseHandle(StdOutWrite); 
505 StdOutWrite = 0
506
507 
508 if (StdErrRead) 
509
510 CloseHandle(StdErrRead); 
511 StdErrRead = 0
512
513 
514 if (StdErrWrite) 
515
516 CloseHandle(StdErrWrite); 
517 StdErrWrite = 0
518
519
520 
521 
522tProcess::~tProcess() 
523
524 delete[] Environment; 
525 
526 // Wait for exit threads to finish before closing the handles! This destructor will block until 
527 // the process has finished what it is doing. Once unblocked, if there is a parent window, we 
528 // send a message. Note: The exit thread waits for the other threads to finish. 
529 if (WaitInDestructor) 
530 WaitForSingleObject(MonitorProcessExitThread, INFINITE); 
531 
532 if (MonitorProcessExitThread) 
533 CloseHandle(MonitorProcessExitThread); 
534 
535 if (MonitorProcessStdOutThread) 
536 CloseHandle(MonitorProcessStdOutThread); 
537 
538 if (MonitorProcessStdErrThread) 
539 CloseHandle(MonitorProcessStdErrThread); 
540 
541 if (StdOutRead) 
542 CloseHandle(StdOutRead); 
543 
544 if (StdOutWrite) 
545 CloseHandle(StdOutWrite); 
546 
547 if (StdErrRead) 
548 CloseHandle(StdErrRead); 
549 
550 if (StdErrWrite) 
551 CloseHandle(StdErrWrite); 
552
553 
554 
555void tProcess::MonitorExit() 
556
557 // Wait until the child process is finished doing its thing. 
558 WaitForSingleObject(ChildProcess, INFINITE); 
559 
560 // It's important that we set this to failure by default. It seems that 
561 // sometimes the GetExitCode function will not touch it... in particular, if 
562 // the working dir did not exist. 
563 ulong exitCode = 42
564 GetExitCodeProcess(ChildProcess, &exitCode); 
565 
566 // Now the pipe readers know that everything has stopped. 
567 CloseHandle(ChildProcess); 
568 ChildProcess = 0
569 CloseHandle(ChildThread); 
570 ChildThread = 0
571 
572 if (ExitCode) 
573 *ExitCode = exitCode; 
574 
575 if (ExitCallback) 
576 ExitCallback(ExitCallbackUserPointer, exitCode); 
577 
578 WinHandle handles[] = 
579
580 MonitorProcessStdOutThread, 
581 MonitorProcessStdErrThread, 
582 }; 
583 
584 // Wait until all output messages have been sent. 
585 WaitForMultipleObjects(2, handles, TRUE, INFINITE); 
586 
587 if (Parent) 
588 PostMessage(Parent, uint(tMessage::ProcessDone), exitCode, UserData); 
589 
590 ExitThread(0); 
591
592 
593 
594void tProcess::MonitorStdOut() 
595
596 bool lastWasNewline = false
597 while (1
598
599 ulong avail; 
600 PeekNamedPipe(StdOutRead, 0, 0, 0, &avail, 0); 
601 
602 while (avail > 0
603
604 const int bufSize = 64
605 tString buf(bufSize); 
606 
607 int numToRead = tMath::tMin(bufSize - 1, int(avail)); 
608 
609 ulong numRead; 
610 WinBool success = ReadFile(StdOutRead, buf.Text(), numToRead, &numRead, 0); 
611 if (success) 
612
613 buf.Replace('\r', ' '); 
614 
615 if (Parent) 
616 PostMessage(Parent, uint(tMessage::StdOutString), WinWParam(new tString(buf)), 0); 
617 
618 if (OutputString) 
619 *OutputString += buf; 
620 
621 if (PrintCallback) 
622 PrintCallback(PrintCallbackUserPointer, buf.Chr()); 
623 
624 // We only go to stdout if all other methods failed. 
625 if (!Parent && !OutputString && !PrintCallback) 
626 tPrintf("%s", buf.Pod()); 
627 
628 avail -= numRead; 
629
630
631 
632 if (!Parent) 
633 tFlush(stdout); 
634 
635 if (!ChildProcess) 
636 ExitThread(0); 
637 
638 // Not required... just keeps CPU overhead way down. 
639 tSystem::tSleep(10); 
640
641
642 
643 
644void tProcess::MonitorStdErr() 
645
646 while (1
647
648 ulong avail; 
649 PeekNamedPipe(StdErrRead, 0, 0, 0, &avail, 0); 
650 
651 while (avail > 0
652
653 const int bufSize = 64
654 
655 // Remember, this gets all 0s. 
656 tString buf(bufSize); 
657 
658 int numToRead = tMath::tMin(bufSize - 1, int(avail)); 
659 
660 ulong numRead; 
661 WinBool success = ReadFile(StdErrRead, buf.Text(), numToRead, &numRead, 0);  
662 if (success) 
663
664 buf.Replace('\r', ' '); 
665 
666 if (Parent) 
667 PostMessage(Parent, uint(tMessage::StdErrString), WinWParam(new tString(buf)), 0); 
668 
669 //if (OutputString) 
670 // *OutputString += buf; 
671 
672 if (PrintCallback) 
673 PrintCallback(PrintCallbackUserPointer, buf.Chr()); 
674 
675 // We only go to stdout if all other methods failed. 
676 if (!Parent && !OutputString && !PrintCallback) 
677 tPrintf("%s", buf.Pod()); 
678 
679 avail -= numRead; 
680
681
682 
683 if (!Parent) 
684 tFlush(stdout); 
685 
686 if (!ChildProcess) 
687 ExitThread(0); 
688 
689 // Not required... just keeps CPU overhead way down. 
690 tSystem::tSleep(10); 
691
692
693 
694 
695uint32 tProcess::GetEnvironmentDataLength_Ascii(void* enviro) 
696
697 char* envStr = (char*)enviro; 
698 int envIx = 0
699 
700 bool doneAll = false
701  
702 while (!doneAll) 
703
704 while (envStr[envIx++]); 
705 if (envStr[envIx] == 0
706
707 envIx++; 
708 doneAll = true
709
710
711 
712 return envIx; 
713
714 
715 
716char* tProcess::BuildNewEnvironmentData_Ascii(bool appendToExisting, int numPairs, va_list args) 
717
718 #ifdef TACENT_UTF16_API_CALLS 
719 wchar_t* oldEnv = nullptr
720 #else 
721 char* oldEnv = nullptr
722 #endif 
723 if (appendToExisting) 
724 oldEnv = ::GetEnvironmentStrings(); 
725 
726 #ifdef TACENT_UTF16_API_CALLS 
727 tString oldEnvStr((char16_t*)oldEnv); 
728 #else 
729 tString oldEnvStr(oldEnv); 
730 #endif 
731 ::FreeEnvironmentStrings(oldEnv); 
732 
733 const char pairSeparatingCharacter = '\0'
734 
735 tList<tStringItem> names(tListMode::ListOwns); 
736 tList<tStringItem> values(tListMode::ListOwns); 
737 
738 int oldSize = 0
739 if (oldEnvStr.IsValid()) 
740 oldSize = tProcess::GetEnvironmentDataLength_Ascii(oldEnvStr.Units()); 
741 
742 int newSize = 0
743 for (int p = 0; p < numPairs; ++p) 
744
745 const char* name = va_arg(args, const char*); 
746 const char* value = va_arg(args, const char*); 
747 
748 newSize += int(strlen(name)) + 1
749 newSize += int(strlen(value)) + 1
750 
751 names.Append( new tStringItem(name) ); 
752 values.Append( new tStringItem(value) ); 
753
754  
755 int totalSize = oldSize + newSize; 
756 
757 char* newenvdata = new char[totalSize]; 
758 int newDstIdx = 0
759 if (oldEnvStr.IsValid()) 
760
761 tStd::tMemcpy(newenvdata, oldEnvStr.Units(), oldSize); 
762 newDstIdx = oldSize - 1
763
764  
765 tStringItem* name = names.First(); 
766 tStringItem* value = values.First(); 
767 for (; name && value; name = name->Next(), value = value->Next()) 
768
769 int namelen = name->Length(); 
770 int valuelen = value->Length(); 
771 
772 tStd::tMemcpy(&newenvdata[newDstIdx], name->Chr(), namelen); 
773 newenvdata[newDstIdx+namelen] = '='
774 tStd::tMemcpy(&newenvdata[newDstIdx+namelen+1], value->Chr(), valuelen); 
775 newenvdata[newDstIdx+namelen+1+valuelen] = pairSeparatingCharacter; 
776 newDstIdx += namelen+1+valuelen+1
777 }  
778 newenvdata[newDstIdx] = '\0'
779 
780 return newenvdata; 
781
782 
783 
784#endif 
785