2015-11-14 49 views
9

我想從我的C++程序中發送數據到外部的管道,就像這樣:通過stdin和ostream將數據發送到程序。 (C++)

FILE* file = popen("my_prog -opt | other_prog", "w"); 
std::ostream fileStream = some_function(file); 
fileStream << "some data"; 

我知道有沒有簡單的,跨平臺的方式做第二線,但有沒有什麼辦法可以用popen以外的東西來完成同樣的事情?我不需要使用popen,但我確實需要使用ostream。它至少需要編譯clanggcc,但最好能用於任何編譯器。我也可以改變我如何處理管道,但我沒有my_progother_prog的來源。

+3

試着看'boost :: iostreams :: file_descriptor'。它有非常糟糕的文檔,但它可以幫助你,它是跨平臺的。 – vladon

+0

是否有任何特定原因需要*使用'ostream' –

+0

您可以在創建過程之前將所有輸入放入'fileStream'中,然後將其作爲輸入反饋給'my_prog',或者直接輸入一些未知的輸入過程創建後? – 1201ProgramAlarm

回答

7

它是直線前進使用FILE*作爲底層目的地創建一個數據流緩存器和使用這種數據流緩存器創建相應std::ostream。它大致看起來是這樣的:

#include <stdio.h> 
#include <streambuf> 
#include <ostream> 

class stdiobuf 
    : public std::streambuf { 
    enum { bufsize = 2048 }; 
    char buffer[bufsize]; 
    FILE* fp; 
    int (*close)(FILE*); 
    int overflow(int c) { 
     if (c != std::char_traits<char>::eof()) { 
      *this->pptr() = std::char_traits<char>::to_char_type(c); 
      this->pbump(1); 
     } 
     return this->sync() 
      ? std::char_traits<char>::eof() 
      : std::char_traits<char>::not_eof(c); 
    } 
    int sync() { 
     std::streamsize size(this->pptr() - this->pbase()); 
     std::streamsize done(this->fp? fwrite(this->pbase(), 1, size, this->fp): 0); 
     this->setp(this->pbase(), this->epptr()); 
     return size == done? 0: -1; 
    } 
public: 
    stdiobuf(FILE* fp, int(*close)(FILE*) = fclose) 
     : fp(fp) 
     , close(close) { 
     this->setp(this->buffer, this->buffer + (this->fp? bufsize - 1: 0)); 
    } 
    ~stdiobuf() { 
     this->sync(); 
     this->fp && this->close(this->fp); 
    } 
}; 
class opipestream 
    : private virtual stdiobuf 
    , public std::ostream { 
public: 
    opipestream(std::string const& pipe) 
     : stdiobuf(popen(pipe.c_str(), "w"), pclose) 
     , std::ios(static_cast<std::streambuf*>(this)) 
     , std::ostream(static_cast<std::streambuf*>(this)) { 
    } 
}; 

int main() 
{ 
    opipestream out("/usr/bin/sed -e 's/^/xxxx /'"); 
    out << "Hello\n"; 
    out << "world\n"; 
} 

基本思想是你可以通過實現一個流緩衝區來創建一個新的流。上面的實現應該相當完整。儘管最可能發生錯誤的情況是管道被關閉,並且沒有什麼可以做的事情,但是可以改進不完整緩衝區的錯誤處理。

+0

謝謝,這正是我所需要的。 – Drew

2

我以前的答案也只能在Windows下工作,由於缺少std::filebuf構造函數。正如user4815162342指出,替代方案可能使用__gnu_cxx::stdio_filebuf<char>

我修補的東西在一起,現在應該有Windows和Linux工作,但有一個機會,你的平臺可能不是所有的工作。

#ifdef __GNUC__ 
    #include <ext/stdio_sync_filebuf.h>  
    typedef __gnu_cxx::stdio_sync_filebuf<char> popen_filebuf; 
#elif _MSC_VER 
    #include<fstream> 
    typedef std::filebuf popen_filebuf; 
    FILE*(*popen)(const char*, const char*) = _popen; 
#else 
    static_assert(false, "popen_filebuf is not available for this platform"); 
#endif 

int main() 
{ 
    FILE* file = popen("my_prog -opt | other_prog", "w"); 

    popen_filebuf buffer(file); 
    std::ostream fileStream(&buffer); 

    fileStream << "some data"; 

    return 0; 
} 
+0

該標準並沒有強制'std :: filebuf'是以'FILE *'的形式實現的,儘管多個實現可以這樣做。 –

+0

正如我的答案的編輯所示,Linux上的g ++還支持'__gnu_cxx :: stdio_sync_filebuf',因此您可以簡單地將'popen_filebuf'輸入到'__gnu_cxx :: stdio_sync_filebuf '。 – user4815162342

+0

@ user4815162342導致錯誤:沒有匹配函數調用'__gnu_cxx :: stdio_filebuf :: stdio_filebuf(FILE *&)'' –

3

如果你事先知道你支持的平臺,您可以使用特定於平臺的擴展創建的iostream了文件描述符。例如,如here所示,GNU libstdC++提供了__gnu_cxx::stdio_sync_filebuf,其可用於從FILE *創建filebuf。從std::iostream繼承並用適當的filebuf初始化,A級,與G ++ 5.2和鐺++ 3.7 Linux上測試的,可以是這樣的:

#include <iostream> 
#include <ext/stdio_sync_filebuf.h> 

class StdioStream: 
    private __gnu_cxx::stdio_sync_filebuf<char>, public std::iostream { 
public: 
    explicit StdioStream(FILE *fp): 
    __gnu_cxx::stdio_sync_filebuf<char>(fp), std::iostream(this) { } 
}; 

int main() 
{ 
    FILE* file = popen("my_prog -opt | other_prog", "w"); 
    StdioStream fileStream(file); 
    fileStream << "some data"; 
} 

注意,不能只定義一個some_function返回一個std::ostream因爲後者有一個私有拷貝構造函數,並且因爲中間filebuf需要與流一起被銷燬。上面的例子使用多繼承來初始化(否則它會是成員)在iostream基類之前 - 詳見base-from-member

引用的answer還包含用於MSVC++輸入輸出流的等效代碼。

+0

你爲什麼不從'std :: iostream'派生? – Walter

+0

@Walter好點,我現在更新了從'iostream'繼承的答案。 [member-to-base](https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Base-from-Member)問題需要多重繼承,這會降低可讀性,但是它會爲'StdioStream ',不僅僅是爲了權衡理由。 – user4815162342

+0

@ user4815162342爲什麼不把我們的答案結合起來:http://rextester.com/PFJNUL66673和http://goo.gl/NzKDeh –

0

擴展我對原始問題的評論。

您可以保存所有輸入到磁盤上的文件,然後反饋該文件作爲輸入my_prog

ofstream fileStream("input.txt"); 
fileStream << "some data"; 
// insert other input 
fileStream.close(); 
FILE *file = popen("my_prog -opt <input.txt | other_prog", "w"); 

你可以使用其他的東西比popen方法在這裏,可能是直system電話。一旦你完成了,你想要刪除input.txt文件。

+0

雖然不是可移植的,但我也看到了使用本機API創建過程並使用替代句柄代替stdin,stdout等的方法。 – 1201ProgramAlarm