Main Page | Class List | File List | Class Members

command_line_program.cpp

Go to the documentation of this file.
00001 
00002 
00003 
00004 
00005 //ok -- so this is a hack of the MFC code on codeguru, which is in turn claims 
00006 //to be a hack of some MSDN example code -- though the article never made it
00007 //clear /what/ example it came from (my best guess is INHERIT.C)..
00008 //http://www.codeguru.com/Cpp/misc/misc/article.php/c277
00009 
00010 //a broad warning to those of you who may want to port this code to your own projects:
00011 //there is, as best as i can tell, no way to do something like this without using 
00012 //multithreading, as well as the (arcane and poorly designed) win32 process 
00013 //mannagement API.  if this worries you, 
00014 //stop here and go take a long hard look at _popen() -- chances are, it's good 
00015 //enough for whatever you are trying to do.
00016 
00017 //(10/23/05) The phantom window problem has finally been fixed:
00018 //      Using DETACHED_PROCESS, as in the codeguru demo, caused
00019 //      grandchildren of your programs to make their own window,
00020 //      (not the effect I had wanted; the phantom windows could become 
00021 //      really annoying in GUI programs that had a lot of grandchildren).
00022 //      After a moderatly painful hunt through MSDN, I discovered that changing 
00023 //      dwCreationFlags to CREATE_NO_WINDOW fixs the issue.  Notice that this
00024 //  is a major win over _popen(), which, when used in a gui program, will creates 
00025 //      phantom windows for both children
00026 //      and grandchildren.
00027 
00028 //(7/22/06) I've made various fixes and updates, the most notable being the new
00029 //  pipe_read() function, which is less prone to hanging at odd times than the 
00030 //  origional implementation, and the complete_reading() functions, which can
00031 //  be used to ensure that all the output of a given process will, in fact, be
00032 //  read.
00033 
00034 #pragma warning(disable: 4355) // 'this' used in base member initializer list 
00035 #pragma warning(disable: 4800) // conversion from int to bool
00036 #pragma warning(disable: 4267) // conversion from unsigned to signed
00037 #pragma warning(disable: 4244) // conversion to int from double
00038 
00039 #include <windows.h> 
00040 #include <atlbase.h>
00041 #include <stdio.h> 
00042 #include <stdlib.h> 
00043 #include <string.h> 
00044 #include <sven/fs_addons.h>
00045 
00046 #include <stdexcept>
00047 #include <iostream>
00048 #include <string>
00049 #include <sstream>
00050 #include <stdexcept>
00051 
00052 #include <Winuser.h>
00053 
00054 #include <string>
00055 #include <sven_ui/commands.h>
00056 #include <sven/except_macros.h>
00057 #include <sven/string_addons.h>
00058 
00059 //disable warnings which some skimming of the boost forums suggests
00060 //are mostly unavoidable results of msvc having crappy template handling.
00061 #pragma warning(disable: 4275) 
00062 #pragma warning(disable: 4251) 
00063 
00064 #include <boost/thread/mutex.hpp>
00065 #include <boost/thread/thread.hpp>
00066 #include <boost/thread/xtime.hpp>
00067 #include <boost/thread/condition.hpp>
00068 
00069 #include <sven_ui/command_line_program.h>
00070 #include <sven/message_boxes.h>
00071 #include <sven/timer.h>
00072 #include <com_helpers.h>
00073 
00074 
00075 using namespace std;
00076 using namespace sven;
00077 
00078 namespace sven_ui { 
00079 
00081 void thread_sleep(double seconds) {
00082         boost::xtime xt;
00083         boost::xtime_get(&xt, boost::TIME_UTC);
00084 
00085         //while thread::sleep takes a time in an integer number of nanoseconds,
00086         //i prefer a floating point second representation.
00087         xt.nsec += pow(10.0,9.0)*seconds; 
00088         boost::thread::sleep(xt); 
00089 }
00090 
00091 namespace cmdline_prog_imp {
00092 
00093 size_t tstrlen(TCHAR * str) {
00094 #ifdef _UNICODE
00095         return wcslen(str);
00096 #else 
00097         return strlen(str);
00098 #endif
00099 }
00100 
00109 bool pipe_read(HANDLE pipe, const int BUFSIZE, TCHAR chBuf[]) {
00110 
00111         DWORD dwRead,dwRead2,avail,bytes_left;
00112         if(!PeekNamedPipe (pipe,chBuf,BUFSIZE,&dwRead,&avail,&bytes_left))
00113                 return false;
00114         if(!bytes_left && !dwRead) return false;
00115         if( !ReadFile( pipe, chBuf, 
00116                 dwRead, &dwRead2, NULL) || dwRead == 0) {
00117                 return false;                   
00118         }
00119 
00120         chBuf[dwRead2]='\0';
00121         return true;
00122 }
00123 
00124 
00125 
00126 
00127 struct CommandLineProgram {
00128 
00129         bool dying;     
00130 
00131         boost::mutex read_mutex;
00132         ostringstream read_buf;
00133         
00134         boost::mutex read_error_mutex;
00135         ostringstream read_error_buf;
00136 
00137         boost::mutex write_mutex;
00138         ostringstream write_buf;
00139 
00140         boost::thread * cmd_watch_thread;
00141         boost::thread * cmd_watch_error_thread;
00142         boost::thread * cmd_write_thread;
00143 
00144         HANDLE hChildStdinRd, hChildStdinWr, hChildStdinWrDup, 
00145                 hChildStdoutRd, hChildStdoutWr, hChildStdoutRdDup, 
00146                 hChildStderrorRd, hChildStderrorWr, hChildStderrorRdDup, 
00147                 hSaveStdin, hSaveStdout, hSaveStderror;  
00148         DWORD dwProcessId;
00149 
00155         double write_tick;
00156         
00162         double read_tick;
00163 
00166         double finish_tick;
00167 
00171         double finish_timeout;
00172 
00173 
00176         bool seperate_std_error;
00177 
00180         bool writeable;
00181 
00182         fs::wpath starting_dir; 
00183 
00184         /*
00185         //
00186         // functions and variables used to notify the parent that the
00187         // write thread is done.
00188         //
00189         boost::mutex write_dead_mutex;
00190         bool write_is_dead;
00191         bool write_dead() {
00192                 boost::mutex::scoped_lock lock(write_dead_mutex);
00193                 return write_is_dead;
00194         }
00195         void make_write_dead() {
00196                 boost::mutex::scoped_lock lock(write_dead_mutex);
00197                 write_is_dead=true;
00198         }*/
00199 
00200 
00201         struct read_function {
00202                 CommandLineProgram * parent;
00203 
00204                 read_function(CommandLineProgram * parent) : parent(parent) {}
00205 
00206                 void complete_reading() {
00207                         
00208                         const int BUFSIZE = 32;
00209                         TCHAR chBuf[BUFSIZE+1];
00210 
00211                         while(true) {
00212 
00213                                 if(!pipe_read(parent->hChildStdoutRdDup, BUFSIZE,chBuf)) break;
00214                                 
00215                                 if(tstrlen(chBuf)) {
00216                                         boost::mutex::scoped_lock lock(parent->read_mutex);
00217                                         parent->read_buf << chBuf;
00218                                 }
00219 
00220                                 thread_sleep(parent->read_tick);                
00221                         }
00222                 }
00223 
00224 
00225 
00226                 void operator()() {
00227                 
00228                         const int BUFSIZE =32;
00229 
00230                         DWORD dwRead;
00231                         TCHAR chBuf[BUFSIZE+1]; 
00232 
00233                         //cout << "starting read.." << endl;
00234                         while(true) { 
00235 
00236                                 if(pipe_read(parent->hChildStdoutRdDup, BUFSIZE, chBuf)) {
00237                                         if(tstrlen(chBuf)) {
00238                                                 boost::mutex::scoped_lock lock(parent->read_mutex);
00239                                                 parent->read_buf << chBuf;
00240                                         }
00241                                 }
00242 
00243                                 {
00244                                         boost::mutex::scoped_lock lock(parent->write_mutex);
00245                                         if(parent->dying) {
00246                                                 break;
00247                                         }
00248                                 }
00249 
00250                                 if(parent->read_tick) thread_sleep(parent->read_tick);
00251                         
00252                         }
00253 
00254                         complete_reading() ;
00255                 }
00256         };
00257 
00258         struct read_error_function {
00259                 CommandLineProgram * parent;
00260 
00261                 read_error_function(CommandLineProgram * parent) : parent(parent) {}
00262 
00263 
00264                 void complete_reading() {
00265                         
00266                         const int BUFSIZE = 32;
00267                         TCHAR chBuf[BUFSIZE+1];
00268 
00269                         while(true) {
00270 
00271                                 boost::mutex::scoped_lock lock(parent->read_error_mutex);
00272                                 if(!pipe_read(parent->hChildStderrorRdDup, BUFSIZE,chBuf)) break;
00273                                 
00274                                 if(tstrlen(chBuf)) {
00275                                         boost::mutex::scoped_lock lock(parent->read_error_mutex);
00276                                         parent->read_error_buf << chBuf;
00277                                 }
00278 
00279                                 thread_sleep(parent->read_tick);                
00280                         }
00281                 }
00282 
00283                 void operator()() {
00284                 
00285                         const int BUFSIZE =32;
00286 
00287                         DWORD dwRead;
00288                         TCHAR chBuf[BUFSIZE+1]; 
00289 
00290                         //cout << "starting read.." << endl;
00291                         while(true) { 
00292 
00293                                 if(pipe_read(parent->hChildStderrorRdDup, BUFSIZE, chBuf)) {
00294                                         if(tstrlen(chBuf)) {
00295                                                 boost::mutex::scoped_lock lock(parent->read_error_mutex);
00296                                                 parent->read_error_buf << chBuf;
00297                                         }
00298                                 }
00299 
00300                                 {
00301                                         boost::mutex::scoped_lock lock(parent->write_mutex);
00302                                         if(parent->dying) {
00303                                                 break;
00304                                         }
00305                                 }
00306 
00307                                 if(parent->read_tick) thread_sleep(parent->read_tick);
00308                         
00309                         }
00310 
00311                         complete_reading() ;
00312                 }
00313 
00314         };
00315 
00316         struct write_function {
00317                 CommandLineProgram * parent;
00318                 write_function(CommandLineProgram * parent) : parent(parent) {}
00319 
00320                 void operator()() {
00321 
00322                         while(true) {
00323                                 DWORD dwWritten;
00324 
00325                                 string to_write;
00326                                 to_write="";
00327                                 {
00328                                         boost::mutex::scoped_lock lock(parent->write_mutex);
00329                                         to_write=parent->write_buf.str();
00330                                         parent->write_buf.str("");
00331                                 }
00332                                 if(to_write.size()) {
00333 
00334                                         bool bSuccess = 
00335                                                 WriteFile( parent->hChildStdinWrDup, to_write.c_str(),to_write.size(), &dwWritten, NULL );
00336                                         
00337                                         
00338                                         if(!bSuccess) {
00339                                                 DWORD write_error= GetLastError();
00340 
00341                                                 sven::error_msg(fmt("Could not write \"%s\" to thread.\n%s") % to_write % com_help::format_error_string(write_error));
00342                                                  
00343                                                 //terminate this thread
00344                                                 break;
00345                                         }
00346 
00347                                         
00348                                 }
00349 
00350                                 if(parent->write_tick) thread_sleep(parent->write_tick);
00351                                 
00352                                 {
00353                                         boost::mutex::scoped_lock lock(parent->write_mutex);
00354                                         if(parent->dying) break;
00355                                 }                       
00356                         }
00357 
00358                 }
00359         };
00360 
00361         read_function reader;
00362         read_error_function read_error_er;
00363         write_function writer;
00364 
00365         CommandLineProgram() 
00366                 : reader(this), writer(this), read_error_er(this), 
00367                 
00368                 write_tick(.05), read_tick(.01), 
00369                 finish_tick(.03), finish_timeout(0), 
00370 
00371                 seperate_std_error(false), writeable(true),
00372                 dying(false)
00373         {}
00374 
00375         boost::mutex handle_mutex;
00376 
00377 
00378 
00379         void create_process(cstring name) {
00380 
00381                 USES_CONVERSION;
00382 
00383                 SECURITY_ATTRIBUTES saAttr;
00384                 BOOL fSuccess;
00385 
00386                 // Set the bInheritHandle flag so pipe handles are inherited.
00387                 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
00388                 saAttr.bInheritHandle = TRUE;
00389                 saAttr.lpSecurityDescriptor = NULL;
00390 
00391                 // The steps for redirecting child process's STDOUT:
00392                 // 1. Save current STDOUT, to be restored later.
00393                 // 2. Create anonymous pipe to be STDOUT for child process.
00394                 // 3. Set STDOUT of the parent process to be write handle to
00395                 // the pipe, so it is inherited by the child process.
00396                 // 4. Create a noninheritable duplicate of the read handle and
00397                 // close the inheritable read handle.
00398 
00399                 // Save the handle to the current STDOUT.
00400                 hSaveStdout = GetStdHandle(STD_OUTPUT_HANDLE);
00401 
00402                 // Create a pipe for the child process's STDOUT.
00403                 if( !CreatePipe( &hChildStdoutRd, &hChildStdoutWr, &saAttr, 0) )
00404                 {
00405                         throw std::runtime_error("Stdout pipe creation failed");
00406                 }
00407 
00408                 // Set a write handle to the pipe to be STDOUT.
00409                 if( !SetStdHandle(STD_OUTPUT_HANDLE, hChildStdoutWr) )
00410                 {
00411                         throw std::runtime_error("Redirecting STDOUT failed");
00412                 }
00413 
00414                 // Create noninheritable read handle and close the inheritable read handle.
00415                 fSuccess = DuplicateHandle( GetCurrentProcess(), hChildStdoutRd,
00416                 GetCurrentProcess(), &hChildStdoutRdDup ,
00417                 0, FALSE,
00418                 DUPLICATE_SAME_ACCESS );
00419                 if( !fSuccess )
00420                 {
00421                 throw std::runtime_error("DuplicateHandle failed");
00422                 }
00423                 CloseHandle( hChildStdoutRd );
00424 
00425                 if(seperate_std_error) {
00426                         // Save the handle to the current STDERR.
00427                         hSaveStderror = GetStdHandle(STD_ERROR_HANDLE);
00428 
00429                         // Create a pipe for the child process's STDOUT.
00430                         if( !CreatePipe( &hChildStderrorRd, &hChildStderrorWr, &saAttr, 0) )
00431                         {
00432                                 throw std::runtime_error("Stderror pipe creation failed");
00433                         }
00434 
00435                         // Set a write handle to the pipe to be STDOUT.
00436                         if( !SetStdHandle(STD_ERROR_HANDLE, hChildStderrorWr) )
00437                         {
00438                                 throw std::runtime_error("Redirecting STDERROR failed");
00439                         }
00440 
00441                         // Create noninheritable read handle and close the inheritable read handle.
00442                         fSuccess = DuplicateHandle( GetCurrentProcess(), hChildStderrorRd,
00443                         GetCurrentProcess(), &hChildStderrorRdDup ,
00444                         0, FALSE,
00445                         DUPLICATE_SAME_ACCESS );
00446                         if( !fSuccess )
00447                         {
00448                         throw std::runtime_error("DuplicateHandle failed");
00449                         }
00450                         CloseHandle( hChildStderrorRd );
00451                 }
00452 
00453 
00454                 // The steps for redirecting child process's STDIN:
00455                 // 1. Save current STDIN, to be restored later.
00456                 // 2. Create anonymous pipe to be STDIN for child process.
00457                 // 3. Set STDIN of the parent to be the read handle to the
00458                 // pipe, so it is inherited by the child process.
00459                 // 4. Create a noninheritable duplicate of the write handle,
00460                 // and close the inheritable write handle.
00461 
00462                 // Save the handle to the current STDIN.
00463                 hSaveStdin = GetStdHandle(STD_INPUT_HANDLE);
00464 
00465                 // Create a pipe for the child process's STDIN.
00466                 if( !CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0) )
00467                         throw std::runtime_error("Stdin pipe creation failed");
00468 
00469                 
00470                 // Set a read handle to the pipe to be STDIN.
00471                 if( !SetStdHandle(STD_INPUT_HANDLE, hChildStdinRd) )
00472                         throw std::runtime_error("Redirecting Stdin failed");
00473                 
00474                 // Duplicate the write handle to the pipe so it is not inherited.
00475                 fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdinWr,
00476                 GetCurrentProcess(), &hChildStdinWrDup,
00477                 0, FALSE, // not inherited
00478                 DUPLICATE_SAME_ACCESS );
00479                 if( !fSuccess )
00480                         throw std::runtime_error("DuplicateHandle failed");
00481                 
00482                 CloseHandle(hChildStdinWr);
00483 
00485                 //prepare to start the process....              
00486                 PROCESS_INFORMATION piProcInfo;
00487                 STARTUPINFOW siStartInfo;
00488 
00489                 // Set up members of STARTUPINFO structure.
00490                 ZeroMemory( &siStartInfo, sizeof(STARTUPINFOW) );
00491                 siStartInfo.cb = sizeof(STARTUPINFOW);
00492 
00493 //              siStartInfo.wShowWindow = SW_HIDE;
00494                 siStartInfo.dwFlags = STARTF_USESTDHANDLES;
00495                 siStartInfo.hStdInput = hChildStdinRd;
00496                 siStartInfo.hStdOutput = hChildStdoutWr;
00497                 siStartInfo.hStdError = seperate_std_error ?
00498                                                         hChildStderrorWr : hChildStdoutWr;
00499 
00500                 wchar_t * cmd_name=new wchar_t[name.size()+1];
00501                 swprintf(cmd_name,L"%s",a2w(name).c_str());
00502 
00503                 wstring absolute_starting_dir = (fs::wpath(get_current_directory_w(), fs::native) / starting_dir).native_directory_string();
00504                 if(!starting_dir.empty()) sven_ui::report("starting dir is : " + qwp(w2a(absolute_starting_dir)) );
00505                 
00506                 // a dirty hack to make unicode conversion to TCHAR's work
00507                 // in the two cases i care about.
00508         /*      #ifdef _UNICODE
00509                 #define mA2T(str) A2T(str)
00510                 #else
00511                 #define mA2T(str) (str)
00512                 #endif
00513         */                      
00514                 // Create the child process.
00515                 BOOL ret = CreateProcessW( NULL,
00516                 cmd_name, // applicatin name
00517                 NULL, // process security attributes
00518                 NULL, // primary thread security attributes
00519                 TRUE, // handles are inherited
00520                 //DETACHED_PROCESS //was the code project default, proved troublesome
00521                 CREATE_NO_WINDOW /*| HIGH_PRIORITY_CLASS*/, // creation flags
00522                 NULL, //use the parent's enviornment
00523                 absolute_starting_dir.empty() ? NULL : absolute_starting_dir.c_str(), // set the path
00524                 &siStartInfo, // STARTUPINFO pointer
00525                 &piProcInfo); // receives PROCESS_INFORMATION
00526                 delete [] cmd_name;
00527 
00528                 if( ret )
00529                 dwProcessId = piProcInfo.dwProcessId;
00530                 if(!ret) RT_ERROR_ARGS("CreateChildProcess failed with cmd = %s", sven::qw(w2a(cmd_name)));
00531                 
00532 
00533                 // After process creation, restore the saved STDIN and STDOUT.
00534                 if( !SetStdHandle(STD_INPUT_HANDLE, hSaveStdin) )
00535                         throw std::runtime_error("Re-redirecting Stdin failed" );
00536 
00537                 if( !SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdout) )
00538                         throw std::runtime_error("Re-redirecting Stdout failed");
00539 
00540                 if( seperate_std_error && !SetStdHandle(STD_ERROR_HANDLE, hSaveStderror) )
00541                         throw std::runtime_error("Re-redirecting Stderror failed");
00542         
00543                 cmd_watch_thread=new boost::thread(reader);
00544                 if(seperate_std_error )
00545                         cmd_watch_error_thread=new boost::thread(read_error_er);
00546                 cmd_write_thread=new boost::thread(writer);
00547 
00548         }
00549 
00550 
00551         void write_line(string s) {
00552                 if(!writeable) {
00553                         RT_ERROR_ARGS("can't write \"%s\" to process, because the process has not been declared as writeable.",s);
00554                 }
00555                 boost::mutex::scoped_lock lock(write_mutex);
00556                 write_buf << s << endl;
00557         }
00558 
00559         string read() {
00560                 boost::mutex::scoped_lock lock(read_mutex);
00561                 string rval = read_buf.str();
00562                 sven::delete_carriage_returns(rval);
00563                 read_buf.str("");
00564                 return rval;
00565         }
00566         string read_error() {
00567                 boost::mutex::scoped_lock lock(read_error_mutex);
00568                 string rval = read_error_buf.str();
00569                 sven::delete_carriage_returns(rval);
00570                 read_error_buf.str("");
00571                 return rval;
00572         }
00573 
00574 
00575         ~CommandLineProgram() {
00576                 //i think this should kill the threads and the process...
00577         
00578                 //ExitProcess();
00579 
00580                 //write_line("exit");
00581                 //thread_sleep_sec(10*(write_tick + read_tick));
00582 
00583                 //      terminate_threads();
00584         }
00585 
00586         //need a "process is dead" function...
00587         bool process_is_dead() {
00588                 DWORD status;
00589                 HANDLE hProcess=OpenProcess(PROCESS_QUERY_INFORMATION,FALSE,dwProcessId);
00590                 if(!hProcess) throw std::runtime_error("tried to query idle for a process that doesn't seem to exist at all...");
00591                 GetExitCodeProcess(hProcess,&status);
00592                 CloseHandle( hProcess );
00593                 return status!=STILL_ACTIVE;
00594         }
00595 
00596         void terminate_threads() {
00597                 
00598                 {
00599                                 boost::mutex::scoped_lock lock2(read_mutex);
00600                                 boost::mutex::scoped_lock lock3(write_mutex);
00601                                 dying =true;
00602                 }
00603 
00604 
00605                 //for reasons that i completly fail to understand,
00606                 //joing the write thread seems to prevent scope badness,
00607                 //while joining the watch thread seems to provoke a crash.
00608                 //(for reasons that i still fail completly to understand, this seems to fix it...)
00609 
00610                 cmd_watch_thread->join();
00611                 if(seperate_std_error) cmd_watch_error_thread->join();
00612                 cmd_write_thread->join();
00613 
00614                 delete cmd_watch_thread;
00615                 if(seperate_std_error) delete cmd_watch_error_thread;
00616                 delete cmd_write_thread;
00617 
00618 
00619 
00620                 {
00621                         boost::mutex::scoped_lock lock(handle_mutex);   
00622                 
00623                         if(!process_is_dead()) {
00624                                 HANDLE hProcess = OpenProcess( PROCESS_TERMINATE, FALSE, dwProcessId );
00625                                 if( hProcess )
00626                                 {
00627                                         //cout << "had to do a kill" << endl;
00628                                         TerminateProcess( hProcess,0 );
00629                                 }
00630                         }
00631 
00632                         CloseHandle( hChildStdinRd);
00633                         CloseHandle( hChildStdoutWr);
00634                         CloseHandle( hChildStdinWrDup );
00635                         CloseHandle( hChildStdoutRdDup );
00636 
00637                         if(seperate_std_error) {        
00638                                 CloseHandle( hChildStderrorWr);
00639                                 CloseHandle( hChildStderrorRdDup );
00640                         }
00641                 }
00642         }
00643 
00644         void wait_for_complete() {
00645                 if(finish_timeout>0) {
00646                         sven::timer t;
00647                         while(!process_is_dead()) {
00648                                 thread_sleep(finish_tick);
00649                                 if(t.elapsed()>finish_timeout) RT_ERROR_ARGS("process did not complete within specified time (%s).", t.human_readable());
00650                         }
00651                 }
00652                 else {
00653                         while(!process_is_dead()) thread_sleep(finish_tick);
00654                 }
00655         }
00656 
00657         void finish() {
00658                 wait_for_complete();
00659                 terminate_threads();
00660         }
00661 };
00662 
00663 }
00664 
00665 // for this to work, we need to be sure that the initilization of
00666 // wrapped will take place prior to the initilization of the double 
00667 // references (read_tick, write_tick, et alius).
00668 //
00669 // according to the 2003 C++ standard ( ISO/IEC 14882:2003(E) )
00670 // section 12.6.2:5, the order in which the initilizers will be called
00671 // is termined by the position of their declaration in the class.
00672 //
00673 CommandLineProgram::CommandLineProgram() : 
00674         wrapped(new cmdline_prog_imp::CommandLineProgram()),
00675         #define REF(x) x(wrapped->x)
00676         REF(write_tick), REF(read_tick), REF(finish_tick), REF(finish_timeout), 
00677         REF(seperate_std_error), REF(writeable), REF(starting_dir)
00678         #undef REF
00679 {}
00680 
00681 CommandLineProgram::~CommandLineProgram() { delete wrapped;}
00682 
00683 void CommandLineProgram::write_line(cstring s) { wrapped->write_line(s); }
00684 
00685 string CommandLineProgram::read() { return wrapped->read(); }
00686 string CommandLineProgram::read_error() { return wrapped->read_error(); }
00687 
00688 bool  CommandLineProgram::process_is_dead() { return wrapped->process_is_dead(); }
00689 
00690 void CommandLineProgram::finish() { return wrapped->finish(); }
00691 void CommandLineProgram::create_process(cstring name) { return wrapped->create_process(name); }
00692 void CommandLineProgram::terminate_threads() { wrapped->terminate_threads(); }
00693 
00694 }

Generated on Mon Mar 17 12:13:59 2008 for Command Line Demo by  doxygen 1.3.9.1