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 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, WindowHandle 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, WindowHandle 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 SECURITY_ATTRIBUTES secAttr; 
354 secAttr.nLength = sizeof(SECURITY_ATTRIBUTES);  
355 
356 // Set the bInheritHandle flag so pipe handles are inherited. 
357 secAttr.bInheritHandle = detached ? False : True; 
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 STARTUPINFO startup; 
384 startup.cb = sizeof(STARTUPINFO); 
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 : STARTF_USESTDHANDLES; 
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 PROCESS_INFORMATION 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 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 int success = CreateProcess(0, (char*)cmdLine.ConstText(), 0, 0, TRUE, DETACHED_PROCESS, envBlock, workingDir.ConstText(), &startup, &procInfo); 
418 if (!success) 
419 { 
420 ulong lastError = GetLastError(); 
421 if (ExitCode) 
422 *ExitCode = 1; 
423 
424 throw tError("CreateProcess failed with %d (0x%08X). Possibly due to windows security settings or invalid working dir.", lastError, lastError); 
425 } 
426 ChildProcess = procInfo.hProcess; 
427 ChildThread = procInfo.hThread; 
428 
429 if (detached) 
430 { 
431 CloseHandle(ChildProcess); 
432 ChildProcess = 0; 
433 CloseHandle(ChildThread); 
434 ChildThread = 0; 
435 } 
436 else 
437 { 
438 // Create Monitor threads. These ones send the messages or print the output. 
439 ulong threadID; 
440 MonitorProcessExitThread = CreateThread(0, 0, tProcess::MonitorExitBridge, this, 0, &threadID); 
441 MonitorProcessStdOutThread = CreateThread(0, 0, tProcess::MonitorStdOutBridge, this, 0, &threadID); 
442 MonitorProcessStdErrThread = CreateThread(0, 0, tProcess::MonitorStdErrBridge, this, 0, &threadID); 
443 } 
444} 
445 
446 
447void tProcess::Terminate() 
448{ 
449 if (!ChildProcess) 
450 return; 
451 
452 uint32 exitCode = 23; 
453 WinBool success = TerminateProcess(ChildProcess, exitCode); 
454 
455 // Note we do NOT set ChildProcess to 0 here. The MonitorExit thread should handle this for us. 
456} 
457 
458 
459void tProcess::TerminateHard() 
460{ 
461 if (!ChildProcess) 
462 return; 
463 
464 uint32 exitCode = 23; 
465 WinBool success = TerminateProcess(ChildProcess, exitCode); 
466 
467 Parent = 0; 
468 WaitForSingleObject(MonitorProcessExitThread, INFINITE); 
469 WaitInDestructor = false; 
470 
471 if (MonitorProcessExitThread) 
472 { 
473 CloseHandle(MonitorProcessExitThread); 
474 MonitorProcessExitThread = 0; 
475 } 
476 
477 if (MonitorProcessStdOutThread) 
478 { 
479 CloseHandle(MonitorProcessStdOutThread); 
480 MonitorProcessStdOutThread = 0; 
481 } 
482 
483 if (MonitorProcessStdErrThread) 
484 { 
485 CloseHandle(MonitorProcessStdErrThread); 
486 MonitorProcessStdErrThread = 0; 
487 } 
488 
489 if (StdOutRead) 
490 { 
491 CloseHandle(StdOutRead); 
492 StdOutRead = 0; 
493 } 
494 
495 if (StdOutWrite) 
496 { 
497 CloseHandle(StdOutWrite); 
498 StdOutWrite = 0; 
499 } 
500 
501 if (StdErrRead) 
502 { 
503 CloseHandle(StdErrRead); 
504 StdErrRead = 0; 
505 } 
506 
507 if (StdErrWrite) 
508 { 
509 CloseHandle(StdErrWrite); 
510 StdErrWrite = 0; 
511 } 
512} 
513 
514 
515tProcess::~tProcess() 
516{ 
517 delete[] Environment; 
518 
519 // Wait for exit threads to finish before closing the handles! This destructor will block until 
520 // the process has finished what it is doing. Once unblocked, if there is a parent window, we 
521 // send a message. Note: The exit thread waits for the other threads to finish. 
522 if (WaitInDestructor) 
523 WaitForSingleObject(MonitorProcessExitThread, INFINITE); 
524 
525 if (MonitorProcessExitThread) 
526 CloseHandle(MonitorProcessExitThread); 
527 
528 if (MonitorProcessStdOutThread) 
529 CloseHandle(MonitorProcessStdOutThread); 
530 
531 if (MonitorProcessStdErrThread) 
532 CloseHandle(MonitorProcessStdErrThread); 
533 
534 if (StdOutRead) 
535 CloseHandle(StdOutRead); 
536 
537 if (StdOutWrite) 
538 CloseHandle(StdOutWrite); 
539 
540 if (StdErrRead) 
541 CloseHandle(StdErrRead); 
542 
543 if (StdErrWrite) 
544 CloseHandle(StdErrWrite); 
545} 
546 
547 
548void tProcess::MonitorExit() 
549{ 
550 // Wait until the child process is finished doing its thing. 
551 WaitForSingleObject(ChildProcess, INFINITE); 
552 
553 // It's important that we set this to failure by default. It seems that 
554 // sometimes the GetExitCode function will not touch it... in particular, if 
555 // the working dir did not exist. 
556 ulong exitCode = 42; 
557 GetExitCodeProcess(ChildProcess, &exitCode); 
558 
559 // Now the pipe readers know that everything has stopped. 
560 CloseHandle(ChildProcess); 
561 ChildProcess = 0; 
562 CloseHandle(ChildThread); 
563 ChildThread = 0; 
564 
565 if (ExitCode) 
566 *ExitCode = exitCode; 
567 
568 if (ExitCallback) 
569 ExitCallback(ExitCallbackUserPointer, exitCode); 
570 
571 WinHandle handles[] = 
572 { 
573 MonitorProcessStdOutThread, 
574 MonitorProcessStdErrThread, 
575 }; 
576 
577 // Wait until all output messages have been sent. 
578 WaitForMultipleObjects(2, handles, TRUE, INFINITE); 
579 
580 if (Parent) 
581 PostMessage(Parent, uint(tMessage::ProcessDone), exitCode, UserData); 
582 
583 ExitThread(0); 
584} 
585 
586 
587void tProcess::MonitorStdOut() 
588{ 
589 bool lastWasNewline = false; 
590 while (1) 
591 { 
592 ulong avail; 
593 PeekNamedPipe(StdOutRead, 0, 0, 0, &avail, 0); 
594 
595 while (avail > 0) 
596 { 
597 const int bufSize = 64; 
598 tString buf(bufSize); 
599 
600 int numToRead = tMath::tMin(bufSize - 1, int(avail)); 
601 
602 ulong numRead; 
603 WinBool success = ReadFile(StdOutRead, buf.Text(), numToRead, &numRead, 0); 
604 if (success) 
605 { 
606 buf.Replace('\r', ' '); 
607 
608 if (Parent) 
609 PostMessage(Parent, uint(tMessage::StdOutString), WinWParam(new tString(buf)), 0); 
610 
611 if (OutputString) 
612 *OutputString += buf; 
613 
614 if (PrintCallback) 
615 PrintCallback(PrintCallbackUserPointer, buf.ConstText()); 
616 
617 // We only go to stdout if all other methods failed. 
618 if (!Parent && !OutputString && !PrintCallback) 
619 tPrintf("%s", buf.Pod()); 
620 
621 avail -= numRead; 
622 } 
623 } 
624 
625 if (!Parent) 
626 tFlush(stdout); 
627 
628 if (!ChildProcess) 
629 ExitThread(0); 
630 
631 // Not required... just keeps CPU overhead way down. 
632 tSystem::tSleep(10); 
633 } 
634} 
635 
636 
637void tProcess::MonitorStdErr() 
638{ 
639 while (1) 
640 { 
641 ulong avail; 
642 PeekNamedPipe(StdErrRead, 0, 0, 0, &avail, 0); 
643 
644 while (avail > 0) 
645 { 
646 const int bufSize = 64; 
647 
648 // Remember, this gets all 0s. 
649 tString buf(bufSize); 
650 
651 int numToRead = tMath::tMin(bufSize - 1, int(avail)); 
652 
653 ulong numRead; 
654 WinBool success = ReadFile(StdErrRead, buf.Text(), numToRead, &numRead, 0);  
655 if (success) 
656 { 
657 buf.Replace('\r', ' '); 
658 
659 if (Parent) 
660 PostMessage(Parent, uint(tMessage::StdErrString), WinWParam(new tString(buf)), 0); 
661 
662 //if (OutputString) 
663 // *OutputString += buf; 
664 
665 if (PrintCallback) 
666 PrintCallback(PrintCallbackUserPointer, buf.ConstText()); 
667 
668 // We only go to stdout if all other methods failed. 
669 if (!Parent && !OutputString && !PrintCallback) 
670 tPrintf("%s", buf.Pod()); 
671 
672 avail -= numRead; 
673 } 
674 } 
675 
676 if (!Parent) 
677 tFlush(stdout); 
678 
679 if (!ChildProcess) 
680 ExitThread(0); 
681 
682 // Not required... just keeps CPU overhead way down. 
683 tSystem::tSleep(10); 
684 } 
685} 
686 
687 
688uint32 tProcess::GetEnvironmentDataLength_Ascii(void* enviro) 
689{ 
690 char* envStr = (char*)enviro; 
691 int envIx = 0; 
692 
693 bool doneAll = false; 
694  
695 while (!doneAll) 
696 { 
697 while (envStr[envIx++]); 
698 if (envStr[envIx] == 0) 
699 { 
700 envIx++; 
701 doneAll = true; 
702 } 
703 } 
704 
705 return envIx; 
706} 
707 
708 
709char* tProcess::BuildNewEnvironmentData_Ascii(bool appendToExisting, int numPairs, va_list args) 
710{ 
711 char* oldEnviro = 0; 
712 if (appendToExisting) 
713 oldEnviro = ::GetEnvironmentStrings(); 
714 
715 const char pairSeparatingCharacter = '\0'; 
716 
717 tList<tStringItem> names(tListMode::ListOwns); 
718 tList<tStringItem> values(tListMode::ListOwns); 
719 
720 int oldSize = 0; 
721 if (oldEnviro) 
722 oldSize = tProcess::GetEnvironmentDataLength_Ascii(oldEnviro); 
723 
724 int newSize = 0; 
725 for (int p = 0; p < numPairs; ++p) 
726 { 
727 const char* name = va_arg(args, const char*); 
728 const char* value = va_arg(args, const char*); 
729 
730 newSize += int(strlen(name)) + 1; 
731 newSize += int(strlen(value)) + 1; 
732 
733 names.Append( new tStringItem(name) ); 
734 values.Append( new tStringItem(value) ); 
735 } 
736  
737 int totalSize = oldSize + newSize; 
738 
739 char* newenvdata = new char[totalSize]; 
740 int newDstIdx = 0; 
741 if (oldEnviro) 
742 { 
743 tStd::tMemcpy(newenvdata, oldEnviro, oldSize); 
744 newDstIdx = oldSize - 1; 
745 } 
746  
747 tStringItem* name = names.First(); 
748 tStringItem* value = values.First(); 
749 for (; name && value; name = name->Next(), value = value->Next()) 
750 { 
751 int namelen = name->Length(); 
752 int valuelen = value->Length(); 
753 
754 tStd::tMemcpy(&newenvdata[newDstIdx], name->ConstText(), namelen); 
755 newenvdata[newDstIdx+namelen] = '='; 
756 tStd::tMemcpy(&newenvdata[newDstIdx+namelen+1], value->ConstText(), valuelen); 
757 newenvdata[newDstIdx+namelen+1+valuelen] = pairSeparatingCharacter; 
758 newDstIdx += namelen+1+valuelen+1; 
759 }  
760 newenvdata[newDstIdx] = '\0'; 
761 
762 ::FreeEnvironmentStrings(oldEnviro); 
763 return newenvdata; 
764} 
765 
766 
767#endif 
768