Отправка данных в программу через stdin и ostream. (C ++)

Я хотел бы отправить данные из моей программы на C ++ на внешний конвейер, например:

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

Я понимаю, что не существует простого кросс-платформенного способа выполнить вторую строку, но есть ли способ сделать то же самое, используя что-то иное, кроме popen? Мне не нужно использовать popen, но мне нужно использовать ostream. Его нужно будет скомпилировать как минимум с clang и gcc, но желательно, чтобы он работал с любым компилятором. Я мог бы также изменить способ обращения с трубопроводом, но у меня нет источника для my_prog или other_prog.


person Drew    schedule 14.11.2015    source источник
comment
Попробуйте посмотреть boost::iostreams::file_descriptor. У него очень плохая документация, но он может вам помочь и кроссплатформенный.   -  person vladon    schedule 15.11.2015
comment
Есть ли какая-то конкретная причина, по которой вам нужно использовать ostream   -  person an earwig    schedule 17.11.2015
comment
Можете ли вы поместить весь свой ввод в fileStream перед созданием процесса, а затем передать его как ввод в my_prog, или некоторые из вводимых данных неизвестны до тех пор, пока процесс не будет создан?   -  person 1201ProgramAlarm    schedule 17.11.2015
comment
@James_Parsons В моей кодовой базе есть много другого кода, который работает с ostreams.   -  person Drew    schedule 17.11.2015
comment
@ 1201ProgramAlarm Я мог бы легко настроить свою программу так, чтобы все входные данные процесса были известны во время создания процесса. Можете ли вы предоставить код, который показывает, как подключить конвейер после того, как я это сделаю?   -  person Drew    schedule 17.11.2015
comment
Думали ли вы о создании собственного класса, реализующего std::ostream? Не уверен в сложности, но простая, не очень оптимизированная версия, как мне кажется, могла быть завершена за один день. В чем я уверен, так это в том, что вы не должны просто писать popen в своем коде: используйте класс RAII с popen в конструкторе и pclose в деструкторе.   -  person George Sovetov    schedule 17.11.2015
comment
Было бы нормально, если бы он использовал унаследованный класс std::basic_ostream? std::ostream не предназначен для наследования от; все std::basic_ * классы есть.   -  person YSC    schedule 17.11.2015
comment
Самым простым решением, которое я мог придумать, было бы реализовать пользовательский std::streambuf, который можно передать конструктору std::ostream. В результате короткого поиска в Интернете был обнаружен этот сайт, на котором, похоже, уже есть код для FILE* который можно легко настроить для работы на кросс-платформе.   -  person Simon Kraemer    schedule 17.11.2015


Ответы (4)


Просто создать буфер потока, используя 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";
}

Основная идея состоит в том, что вы можете создать новый поток, реализовав буфер потока. Вышеупомянутая реализация должна быть достаточно полной. Обработка ошибок при неполном буфере может быть улучшена, хотя наиболее вероятный случай ошибки заключается в том, что канал был закрыт, и на самом деле не так много, что можно было бы сделать.

person Dietmar Kühl    schedule 17.11.2015
comment
Спасибо, это именно то, что мне нужно. - person Drew; 18.11.2015

Мой старый ответ работал только под Windows из-за отсутствия std::filebuf ctor. Как 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;
}
person Simon Kraemer    schedule 17.11.2015
comment
Стандарт не требует, чтобы std::filebuf был реализован в терминах FILE*, хотя множественная реализация делает (или делала) это таким образом. - person Dietmar Kühl; 17.11.2015
comment
Как показано в редактировании моего ответа, g ++ в Linux также поддерживает __gnu_cxx::stdio_sync_filebuf, поэтому вы можете просто ввести popen_filebuf в __gnu_cxx::stdio_sync_filebuf<char>. - person user4815162342; 17.11.2015
comment
@ user4815162342 Это приводит к error: no matching function for call to '__gnu_cxx::stdio_filebuf<char>::stdio_filebuf(FILE*&)' - person Simon Kraemer; 17.11.2015
comment
stdio_filebuf и stdio_sync_filebuf - разные классы, и вам нужно использовать последний (как показано в комментарии) для непосредственного построения из FILE *. - person user4815162342; 17.11.2015
comment
@ user4815162342 Скорректировал ответ. Спасибо за подсказку. Теперь все намного проще. - person Simon Kraemer; 17.11.2015

Если вы заранее знаете, какие платформы вы поддерживаете, вы можете использовать расширения для конкретных платформ, чтобы создать iostream из файлового дескриптора. Например, как показано здесь, GNU libstdc ++ предоставляет __gnu_cxx::stdio_sync_filebuf, который можно использовать для создания filebuf из FILE *. Класс, который наследуется от std::iostream и инициализирует его соответствующим filebuf, протестирован с g ++ 5.2 и clang ++ 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 необходимо уничтожить вместе с потоком. В приведенном выше примере используется множественное наследование, чтобы иметь возможность инициализировать stdio_sync_filebuf (который в противном случае был бы членом) перед базовым классом iostream - см. base-from-member для получения подробной информации.

Указанный ответ также содержит эквивалентный код для iostreams MSVC ++.

person user4815162342    schedule 17.11.2015
comment
почему вы не производите от std::iostream? - person Walter; 17.11.2015
comment
@Walter Хорошее замечание, теперь я обновил ответ, чтобы унаследовать от iostream. Проблема между членами требует множественного наследования. что снижает удобочитаемость, но приобретает простоту использования для StdioStream, более чем оправдывая компромисс. - person user4815162342; 17.11.2015
comment
@ user4815162342 Почему бы не объединить наши ответы: rextester.com/PFJNUL66673 и goo.gl/NzKDeh - person Simon Kraemer; 18.11.2015
comment
@SimonKraemer Вы как бы уже сделали это, постепенно отредактировав свой исходный ответ, чтобы воспользоваться моим подходом. Теперь, когда Дитмар предоставил проверенную версию своего портативного ответа, и его ответ принят, в любом случае это спорный вопрос. - person user4815162342; 18.11.2015
comment
@ user4815162342 А, я не заметил нового ответа Дитмара. ;-) - person Simon Kraemer; 18.11.2015

Расширение моего комментария к исходному вопросу.

Вы можете сохранить весь ввод в файл на диске, а затем передать этот файл как ввод в 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.

person 1201ProgramAlarm    schedule 18.11.2015
comment
Хотя это не переносимо, я также видел способы создания процесса и использования альтернативного дескриптора вместо stdin, stdout и т. Д. С использованием собственных API. - person 1201ProgramAlarm; 18.11.2015