#ifndef __PIPESTREAM_H__ #define __PIPESTREAM_H__ #include #include #include #include #ifdef _WIN32 #include #else #include #include #include #include #include #include #include #endif /** * 同期モードのPipe用streambuf * * 同期モードであるため、書き込みが完了するまで制御が帰らないので注意すること */ template< class _Elem, class _Traits> class basic_PipeStreambuf : public std::basic_streambuf<_Elem, _Traits> { public: #ifdef _WIN32 typedef HANDLE handle_t; typedef PROCESS_INFORMATION pinfo_t; #else typedef int handle_t; typedef int pinfo_t; #endif protected: typedef std::basic_streambuf<_Elem, _Traits> super_t; typedef std::streamsize streamsize; typedef typename super_t::int_type int_type; handle_t p_handle[2], c_handle[2]; pinfo_t pinfo; int_type in_buf; bool in_buf_ready; public: basic_PipeStreambuf(const char *spec) throw(std::ios_base::failure) : super_t(), in_buf_ready(false) { #ifdef _WIN32 // @see http://nhiro4.ld.infoseek.co.jp/program/windows/pipe.html SECURITY_ATTRIBUTES secAttr; secAttr.nLength = sizeof(SECURITY_ATTRIBUTES); secAttr.lpSecurityDescriptor = NULL; secAttr.bInheritHandle = TRUE; // ハンドル継承 if(CreatePipe(&p_handle[0], &c_handle[0], &secAttr, 0) == 0){ throw std::ios_base::failure(std::string("Pipe error")); } if(CreatePipe(&p_handle[1], &c_handle[1], &secAttr, 0) == 0){ throw std::ios_base::failure(std::string("Pipe error")); } STARTUPINFO startInfo; memset(&startInfo, 0, sizeof(STARTUPINFO)); startInfo.cb = sizeof(STARTUPINFO); // 構造体の大きさ; startInfo.dwFlags = ( STARTF_USEFILLATTRIBUTE | STARTF_USECOUNTCHARS | STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW); startInfo.wShowWindow = SW_HIDE; // ウィンドウ表示のパラメータ; startInfo.hStdInput = c_handle[0]; // 子の入力 startInfo.hStdOutput = c_handle[1]; // 子の出力 startInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); // 子のエラー出力 if(CreateProcess( NULL, // 実行可能モジュールの名前 const_cast(spec), // コマンドラインの文字列 NULL, // セキュリティ記述子 NULL, // セキュリティ記述子 TRUE, // ハンドルの継承オプション 0, // 作成のフラグ NULL, // 新しい環境ブロック NULL, // カレントディレクトリの名前 &startInfo, // スタートアップ情報 &pinfo // プロセス情報 ) != TRUE){ throw std::ios_base::failure(std::string("Could not open: ").append(spec)); } CloseHandle(c_handle[0]); CloseHandle(c_handle[1]); #else { handle_t handle[2]; if(pipe(handle) < 0){ throw std::ios_base::failure(std::string("Pipe error")); } c_handle[0] = handle[0]; // 読み込み p_handle[0] = handle[1]; // 書き込み } { handle_t handle[2]; if(pipe(handle) < 0){ throw std::ios_base::failure(std::string("Pipe error")); } p_handle[1] = handle[0]; // 読み込み c_handle[1] = handle[1]; // 書き込み } if((pinfo = fork()) < 0){ throw std::ios_base::failure(std::string("Fork error")); } if(pinfo == 0){ // child process close(p_handle[0]); close(p_handle[1]); dup2(c_handle[0], 0); // 標準入力 dup2(c_handle[1], 1); // 標準出力 close(c_handle[0]); close(c_handle[1]); if(execlp("sh", "sh", "-c", spec, NULL) < 0){ throw std::ios_base::failure(std::string("Could not exec: ").append(spec)); //exit(1); } } close(c_handle[0]); close(c_handle[1]); #endif } virtual ~basic_PipeStreambuf() { #ifdef _WIN32 WaitForSingleObject(pinfo.hProcess, INFINITE); CloseHandle(p_handle[0]); CloseHandle(p_handle[1]); CloseHandle(c_handle[0]); CloseHandle(c_handle[1]); #else int status; wait(&status); close(p_handle[0]); close(p_handle[1]); close(c_handle[0]); close(c_handle[1]); #endif } void update_in_buf(){ in_buf_ready = true; #ifdef _WIN32 DWORD received; //static int seq_num(0); //seq_num++; //std::cerr << "update_in_buf() : " << seq_num << std::endl; if(ReadFile(p_handle[1], (LPVOID)&in_buf, 1, &received, NULL) && (received > 0)){ //std::cerr << "received!" << std::endl; return; } //std::cerr << "return update_in_buf() : " << seq_num << std::endl; #else if(read(p_handle[1], (void *)&in_buf, 1)){ return; } #endif in_buf = _Traits::eof(); } protected: /** * Get number of characters available in the sequence * * This member function (to be read s-how-many-c) is called to get an * estimate on the number of characters available in the associated * input sequence when the get pointer has reached the apparent end of * the associated sequence (still, characters may be available * after an underflow, and their count is what the return value of * this function is expected to be). * * The public member function in_avail calls this protected member * function to perform this action when the get pointer has reached * the end pointer (or when it is set to null). * * @return An estimate on the number of characters remaining to * be read in the associated character sequence after an underflow * when no read positions are available at the get pointer. */ #ifdef _WIN32 /*streamsize showmanyc(){ }*/ #else /* unistd.hの低水準I/Oはバッファリングなしのため、 * streambufの標準挙動である0返しでよい? * @see http://sato-www.cs.titech.ac.jp/pro3/slides/unix2-4.pdf * @see http://www.cplusplus.com/reference/iostream/streambuf/showmanyc/ */ #endif /** * Write character in the case of overflow * * For the purpose of the streambuf class, overflows happen when * a new character is to be written at the put pointer pptr position, * but this has reached the end pointer epptr, indicating that * apparently no room is currently available at the internal output array. * * This function is expected either to modify the pbase, pptr and epptr * pointers that define the internal output array in such a way that * room is made available for more characters, or otherwise fail. * It is also responsibility of the function to write the character * passed as argument. * * The specific behavior depends on each derived class, but it normally * attempts to write some of the characters to the controlled output * sequence to make room for more characters in the internal output array. * * This protected member function is automatically called by sputc and * sputn member functions when overflows happen. * * @param c Character to be written. * @return A value different than EOF (or traits::eof() for other traits) * signals success. If the function fails, either EOF (or traits::eof() * for other traits) is returned. */ int_type overflow(int_type c = _Traits::eof()){ //std::cerr << "overflow()" << std::endl; #ifdef _WIN32 DWORD transmitted; if((c != _Traits::eof()) && WriteFile(p_handle[0], (LPCVOID)&c, 1, &transmitted, NULL) && (transmitted > 0)){ return true; } #else if((c != _Traits::eof()) && write(p_handle[0], (const void *)&c, 1)){ return true; } #endif return (_Traits::eof()); } /** * Write sequence of characters * * Writes up to n characters from the array pointed by s to the output * sequence controlled by the stream buffer. If less than n characters * can be written to the output sequence the function stops and leaves * the put pointer pptr in the same state as if successive calls to * sputc were made until an EOF (or traits::eof() for other traits) * was returned. * * @param s pointer to the sequence of characters to be output * @param n number of character to be put * @return the number of characters written */ #ifdef _WIN32 streamsize xsputn(const _Elem *s, streamsize n){ //std::cerr << "xsputn()" << std::endl; DWORD transmitted; WriteFile(p_handle[0], s, (DWORD)n, &transmitted, NULL); return (streamsize)transmitted; } #else /* unistd.hの低水準I/Oはバッファリングなしのため、 * 複数文字を引数にとるwriteを呼び出しだところで、1文字書きだけで返ってきてしまう? * 安全のため、streambufの標準挙動であるsputc複数回呼び出しで対応 * @see http://www.cplusplus.com/reference/iostream/streambuf/xsputn/ */ /*streamsize xsputn(const _Elem *s, streamsize n){ return write(p_handle[0], (const void *)s, n); }*/ #endif /** * Get sequence of characters * * Gets up to n characters from the input sequence and stores them * in the array pointed by s. If less than n characters are available * in the input sequence the function returns all the available * characters, as if successive calls to sbumpc were made until an EOF * (or traits::eof() for other traits) was returned. * * Its default behavior in streambuf is to perform the expected behavior * by calling repeatedly the member function sbumpc. * * @param s Pointer to a block of memory where the character sequence * is to be stored. * @param n Number of characters to be gotten. This is an integer * value of type streamsize. * @return The number of characters gotten */ #ifdef _WIN32 streamsize xsgetn(_Elem *s, streamsize n){ //std::cerr << "xsgetn()" << std::endl; DWORD received; ReadFile(p_handle[1], s, (DWORD)n, &received, NULL); return (streamsize)received; } #else /* unistd.hの低水準I/Oはバッファリングなしのため、 * 複数文字を引数にとるreadを呼び出しだところで、1文字読みだけで返ってきてしまう? * streambufの標準挙動であるsbumpc複数回呼び出しで対応 * @see http://www.cplusplus.com/reference/iostream/streambuf/xsgetn/ */ /*streamsize xsgetn(_Elem *s, streamsize n){ return read(p_handle[1], (void *)s, n); }*/ #endif /** * Get character in the case of underflow * * For the purpose of the streambuf class, underflows happen when a new * character is to be read at the get pointer gptr, but this has reached * the end pointer egptr, indicating that apparently no more characters * are available in the internal input array. * * This function is expected to modify the eback, gptr and egptr pointers * that define the internal input array in such a way that if there are * more characters available in the controlled input sequence after the * location represented by streambuf::egptr, at least some of them are * made available through this internal input array and the new character * available at the get pointer's position itself is returned. Otherwise, * if there are no more characters available in the controlled input * sequence after the one represented by egptr, the function returns EOF * (or traits::eof() for other traits). * * Its default behavior in streambuf is to do nothing and return EOF * (or traits::eof() for other traits). * * This protected member function is automatically called by sgetc member * function when an underflow happens. * * The definition of the member function uflow in streambuf relies on * this member function. The behavior of uflow is the same as the one of * underflow, except that it also advances the get pointer. * * @return The new character available at the get pointer position, * if any. Otherwise, EOF (or traits::eof() for other traits) is returned. */ int_type underflow(){ //std::cerr << "underflow()" << std::endl; if(!in_buf_ready){update_in_buf();} return in_buf; } /** * Get character in the case of underflow and advance get pointer * * For the purpose of the streambuf class, underflows happen when a new * character is to be read at the get pointer gptr, but this has reached * the end pointer egptr, indicating that no more characters are apparently * available in the internal input array. * * This function is expected to modify the eback, gptr and egptr pointers * that define the internal input array in such a way that if there are * more characters available in the controlled input sequence after the * location represented by streambuf::egptr, at least some of them are * made available through this internal input array. In this case, the * function returns the new character pointed by the get pointer and * advances it one position. Otherwise, if there are no more characters * available in the controlled input sequence after the one represented by * egptr, the function returns EOF (or traits::eof() for other traits). * * Its default behavior in streambuf is to call its sibling virtual member * function underflow and return its value and advance the get pointer in case * of success, or return EOF (or traits::eof() for other traits) otherwise. * * This protected member function is automatically called by sbumpc and snextc * member functions when underflows happen. * * The behavior of this member function is the same as the one of underflow * except that this function also advances the get pointer by one position. * * @return The new character available at the current get pointer position, * if any. Otherwise, EOF (or [=traits::eof() for other traits) is returned. */ int_type uflow(){ //std::cerr << "uflow()" << std::endl; update_in_buf(); return in_buf; } }; typedef basic_PipeStreambuf > PipeStreambuf; class PipeStream : public std::iostream{ public: typedef PipeStreambuf buf_t; protected: typedef std::iostream super_t; buf_t buf; public: PipeStream(const char *spec) throw(std::ios_base::failure) : buf(spec), super_t(&buf){} ~PipeStream(){} buf_t &buffer(){return buf;} }; #endif /* __PIPESTREAM_H__ */