Как прочитать стандартную ошибку библиотеки?

У меня есть приложение Qt/C++, использующее библиотеку C++.

Эта библиотека имеет механизм журнала, который записывает строковые сообщения в стандартную ошибку.

Теперь я хотел бы иметь возможность перенаправлять эти сообщения на панель в моем инструменте Qt. Я хотел бы избежать изменения библиотеки, потому что она принята многими другими клиентами. Любая идея, как получить во время выполнения эти сообщения?

Имея вместо этого возможность изменить его, что может быть хорошей практикой для переноса этих сообщений в приложение?


person Stefano    schedule 08.02.2012    source источник


Ответы (4)


Это очень плохой дизайн библиотеки. Тем не мение...

Как пишет стандартная ошибка. Если он выводится на std::cerr, вы можете изменить streambuf, используемый std::cerr, например:

std::filebuf logStream;
if ( ~logStream.open( "logfile.txt" ) )
    //  Error handling...
std::streambuf* originalCErrStream = std::cerr.rdbuf();
std::cerr.rdbuf( &logStream );
//  Processing here, with calls to library
std::cerr.rdbuf( originalCErrStream );  //  Using RAII would be better.

Только не забудьте восстановить оригинальный streambuf; оставлять std::cerr указывающим на filebuf, который был уничтожен, не очень хорошая идея.

Если они используют FILE*, есть функция freopen в C (и путем включения в C++), которую вы можете использовать.

Если они используют вывод системного уровня (write в Unix, WriteFile в Windows), то вам придется использовать код системного уровня для изменения вывода. (open для нового файла, close для fd STDERR_FILENO и dup2 для установки STDERR_FILENO для использования вновь открытого файла в Unix. Я не уверен, что это возможно в Windows, возможно, что-то с ReOpenFile или комбинацией CloseHandle с последующим CreateFile.)

РЕДАКТИРОВАТЬ:

Я только что заметил, что вы действительно хотите вывести в окно Qt. Это означает, что вам, вероятно, нужна строка, а не файл. Если библиотека использует std::cerr, вы можете использовать std::stringbuf вместо std::filebuf; на самом деле вы можете захотеть создать свой собственный streambuf, чтобы получать вызовы на sync (который обычно будет вызываться после каждого << на std::cerr). Если библиотека использует один из других методов, единственное, что я могу придумать, это периодически читать файл, чтобы посмотреть, не было ли что-то добавлено. (Я бы использовал для этого read() в Unix, ReadFile() в Windows, чтобы быть уверенным в возможности отличить чтение нулевых байтов из-за того, что с момента последнего чтения ничего не было записано, и из-за состояния ошибки. FILE* и функции iostream рассматривать чтение нулевых байтов как конец файла и не читать дальше.)

person James Kanze    schedule 08.02.2012
comment
библиотека выводит следующее сообщение: std::cerr ‹‹ os.str().c_str() ‹‹ std::endl; - person Stefano; 08.02.2012
comment
я никогда не использую stringbuf... что мне не очень понятно, так это то, как мое приложение узнает, что что-то было отправлено... есть ли у меня какое-либо событие? - person Stefano; 08.02.2012
comment
@Stefano Если вы выводите в stringbuf, вам придется использовать какой-то опрос. Я обычно использую streambuf в таких случаях. (std::cerr инициализируется так, что каждый << приводит к вызову sync в потоковом буфере.) - person James Kanze; 08.02.2012

запись в stderr на самом деле является системным вызовом:

write(2, "blahblah ...");

вы можете перенаправить файловый дескриптор номер 2 на что угодно (файл, канал, сокет):

close(2);                        // close old stderr
int redirect_target = open(...); // open a file where you want to redirect to
                                 // or use pipe, socket whatever you like
dup2(redirect_target, 2);        // copy the redirect_target fd to fd number 2
close(redirect_target);

в вашей ситуации вам понадобится труба.

close(2);
int pipefd[2];
pipe2(pipefd);
dup2(pipefd[1], 2);
close(pipefd[1]);

тогда все записи в stderr можно получить, прочитав pipe[0]:

read(pipe[0], buffer, ...);
person Zang MingJie    schedule 08.02.2012
comment
Обратите внимание, что очень легко получить взаимоблокировку при записи и чтении из одного и того же канала в одном и том же потоке. read должен находиться в цикле в другом потоке (или другом процессе), чтобы это было надежным. - person James Kanze; 08.02.2012
comment
И, конечно же, это чистый Unix. Я не думаю, что его можно использовать под Windows (но, возможно... Windows предоставляет большую часть интерфейса Unix). - person James Kanze; 08.02.2012
comment
и, вероятно, мне следует создать поток с выбором для ожидания сообщений без блокировки моего приложения... - person Stefano; 08.02.2012

Если они используют вызовы std::cerr, вы можете перенаправить их на std::ostringstream.

#include <iostream>
#include <sstream>

class cerr_redirector
{
public:
    cerr_redirector(std::ostream& os)
        :backup_(std::cerr.rdbuf())
         ,sbuf_(os.rdbuf())
    {
        std::cerr.rdbuf(sbuf_);
    }

    ~cerr_redirector()
    {
        std::cerr.rdbuf(backup_);
    }

private:
    cerr_redirector();
    cerr_redirector(const cerr_redirector& copy);
    cerr_redirector& operator =(const cerr_redirector& assign);

    std::streambuf* backup_;
    std::streambuf* sbuf_;
};

Вы можете захватить вывод, используя:

std::ostringstream os;
cerr_redirector red(os);
std::cerr << "This is written to the stream" << std::endl;

std::cout не пострадает:

std::cout << "This is written to stdout" << std::endl;

Итак, вы можете проверить, работает ли ваш захват:

std::cout << "and now: " << os.str() << std::endl;

Или просто добавьте содержимое os.str() в окно Qt.

Демонстрация на ideone.

person Johnsyweb    schedule 08.02.2012
comment
Очень простое решение... моя проблема в том, как я узнаю на стороне клиента, что что-то было написано? Должен ли я продолжать опрос или у меня может быть какой-либо триггер? - person Stefano; 08.02.2012

Здесь я нашел полную реализацию того, что мне нужно...

Спасибо всем за помощь! :)

Будет загружать DLL динамически согласовать свой stderr с основным приложением? Если да, то как...?

person Stefano    schedule 09.02.2012