From: <as...@su...> - 2001-11-27 15:11:47
|
I am writing a program for Windows that is serving as a graphical front end for three different command line programs. I am using the function CreateProcess() to start the command line program with my own stdin pipe, and one output pipe for capturing stdout/stderr. I then start a new thread with _beginthread() to capture the output to a string. Once I fill the string I do a WaitForSingleObject() on a semaphore until my main program reads the string for processing in my GUI. My application is in C++, and here are some code snippets from my program to better explain what I'm doing (I appologize in advance for the length): class CBaseThread //This is the main thread class { public: // These are the basic methods of the thread class, other // methods and data are not given in the snippet CBaseThread (void); virtual ~CBaseThread (void); virtual void start (void); virtual void stop (void); // Redefine to implement thread Function virtual void initialize (void); virtual void theThreadFunc (void) = 0; protected: static void theThreadFuncRedirector (void* pParam); }; // here are what theThreadFuncRedirector and start methods do void CBaseThread::theThreadFuncRedirector (void* pParam) { reinterpret_cast<CBaseThread*>(pParam)->theThreadFunc(); } void CBaseThread::start (void) { _beginthread (theThreadFuncRedirector, 0, (void*)this); } // This is my process class; it derives from CBaseThread class CWinProcess : public CBaseThread { public: CWinProcess(); ~CWinProcess(); int createCommandWithPipes(char* commandString); int getStringText(char* destinationString, int* length); HANDLE getProcessEventHandle(); HANDLE getCompleteEventHandle(); protected: void theThreadFunc (void); HANDLE m_processEvent; HANDLE m_completeEvent; HANDLE m_stdoutReadHandle; HANDLE m_stdinWriteHandle; HANDLE m_threadSemaphore; char m_readBuffer[BUFFER_SIZE]; int m_charsRead; }; // This is the CWinProcess Constructor CWinProcess::CWinProcess() : CBaseThread() { SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; m_stdoutReadHandle = NULL; m_stdinWriteHandle = NULL; m_charsRead = 0; strcpy(m_readBuffer, ""); m_processEvent = CreateEvent(&sa, FALSE, FALSE, "WinProcessEvent"); m_completeEvent = CreateEvent(&sa, FALSE, FALSE, "WinProcessCompleteEvent"); m_threadSemaphore = CreateSemaphore(&sa, 0, 1, "WinProcessSemaphore"); } // This is the method createCommandWithPipes // I've left my comments in to note what I'm doing int CWinProcess::createCommandWithPipes(char* commandString) { // Handles for creating the pipes HANDLE hTempStdoutRead; HANDLE hTempStdinWrite; HANDLE hStdinRead; HANDLE hStdoutWrite; // Other variables for the child process and // file i/o stuff SECURITY_ATTRIBUTES secatt; PROCESS_INFORMATION procinfo; STARTUPINFO startinfo; char command[1024]; // get the command string strcpy(command, commandString); // Set up the SECURITY_ATTRIBUTES for the stdout/stderr pipe secatt.nLength = sizeof(SECURITY_ATTRIBUTES); secatt.bInheritHandle = TRUE; secatt.lpSecurityDescriptor = NULL; // Create a pipe for sending the child stdin CreatePipe(&hStdinRead, &hTempStdinWrite, &secatt, 0); // Create a pipe for getting the child stdout/stderr CreatePipe(&hTempStdoutRead, &hStdoutWrite, &secatt, 0); // Duplicate the write end of the stdin handle, so the handle // isn't inherited when the child process is created. DuplicateHandle(GetCurrentProcess(), hTempStdinWrite, GetCurrentProcess(), &m_stdinWriteHandle, 0, FALSE, DUPLICATE_SAME_ACCESS); // Duplicate the read end of the handle, so funny stuff // doesn't happen when reading later. DuplicateHandle(GetCurrentProcess(), hTempStdoutRead, GetCurrentProcess(), &m_stdoutReadHandle, 0, FALSE, DUPLICATE_SAME_ACCESS); // close the temporary write stdin handle CloseHandle(hTempStdinWrite); // Close the temporary read handle CloseHandle(hTempStdoutRead); // Setup the PROCESS_INFORMATION variable for the // child process ZeroMemory(&procinfo, sizeof(PROCESS_INFORMATION)); // Setup the STARTUPINFO variable for the // child process ZeroMemory(&startinfo, sizeof(STARTUPINFO)); startinfo.cb = sizeof(STARTUPINFO); startinfo.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES; startinfo.wShowWindow = SW_HIDE; startinfo.hStdInput = hStdinRead; startinfo.hStdOutput = hStdoutWrite; startinfo.hStdError = hStdoutWrite; // Create the child process to execute cdrecord CreateProcess(NULL, command, NULL, NULL, TRUE, DETACHED_PROCESS, NULL, NULL, &startinfo, &procinfo); // close the write end of the stdout/stderr pipe, CloseHandle(hStdinRead); // so we can read from the pipe CloseHandle(hStdoutWrite); CloseHandle(procinfo.hThread); CloseHandle(procinfo.hProcess); // start the thread to process I/O start(); // Success! return 0; } // Here is the thread function run when start() is called void CWinProcess::theThreadFunc(void) { // variables for the read BOOL bOk; // close the input handle CloseHandle(m_stdinWriteHandle); // loop until we're done getting output for(;;) { // Read from the process' output pipe bOk = ReadFile(m_stdoutReadHandle, m_readBuffer, BUFFER_SIZE, (DWORD*)(&m_charsRead), NULL); // if ReadFile() didn't return ok, or if it read zero chars, // we're done, so break out of the loop if (!bOk || m_charsRead == 0) { break; } // fire the event that another process/thread/whatever // can read the string m_readBuffer if (!SetEvent(m_processEvent)) { m_status = THREAD_ERROR; } // Now wait for the string to be read before we overwrite it WaitForSingleObject(m_threadSemaphore, INFINITE); // Reset the values of m_charsRead and m_readBuffer, so we // know they can't corrupt anything m_charsRead = 0; strcpy(m_readBuffer, ""); } // close the process' output handle CloseHandle(m_stdoutReadHandle); // fire the event that the thread is done processing // I/O from the process. if (!SetEvent(m_completeEvent)) { m_status = THREAD_ERROR; } // we're done m_status = COMPLETED; } // This is my method for reading the output string int CWinProcess::getStringText(char* destinationString, int* length) { strcpy(destinationString, m_readBuffer); *length = m_charsRead; ReleaseSemaphore(m_threadSemaphore, 1, NULL); return 0; } My main program class has a method that calls createCommandWithPipes(), like so: CMyClass::SomeThread() { ... createCommandWithPipes(commandLineString); ... } The class also has a method that waits for the signal given by the thread function at SetEvent(m_processEvent) CMyClass::ProcessOutput() { ... getStringText(textBuffer, &textLength); ... } Hopefully, to most people, this all looks valid. Now here is my problem... I'm developing my program with multiple compilers, so that when I release the source code people can compile it on several different Windows Compilers, such as VC++, The free Borland C++ Compiler, Mingw, etc. My program works fine when I compile under the free Borland Compiler. However, I get problems when I compile under VC++ and Mingw. My program will compile and run, and the command line programs all work if their output is to stdout. However, when two of the programs output to stderr, I get a memory read or write error when I'm displaying the output of the commands in a text window. This only happens with two of the three command line programs, and only when they output to stderr. The third program will also output to stderr, but it works fine each time. This problem is really baffling me, and I'm trying to get some more eyes on what's going on to see if anyone has had a problem like this before, or if they know why this would be happening. I'd really like to be able to target VC++ and Mingw, but if my code doesn't work properly on either compiler I'll have to stick with Borland. Thanks in advance for reading this far and for any help you can give. I appologize in again for the lengthy message. Thanks. Adam |