2015-11-14 19 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