最近把一个控制台运行的项目迁移到qt时,考虑到之前项目中有大量通过std::cout打印出的日志。所以想在迁移到qt时用TextEdit做一个日志输出栏,将原来std::cout打印出的日志输出到这里,效果见下图:
而且想对原有的代码影响尽量小,所以考虑是否可以通过改动代码,把std::cout的输出给重定向到QTextEdit中
背景中说的重定向这个词其实不是特别准确,正常的重定向其实是从输出到stdout改成输出到其他的文件描述符,而这里把std::cout的内容输出到QTextEdit,与其说是重定向,不如用拦截std::cout的输出过程描述更为恰当。所以需要看一下std::cout在源码中到底是如何实现的。
众所周知,std::cout的定义是在iostream里,该头文件极为精简:
#ifndef _GLIBCXX_IOSTREAM
#define _GLIBCXX_IOSTREAM 1
#pragma GCC system_header
#include
#include
#include
namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
/**
* @name Standard Stream Objects
*
* The <iostream> header declares the eight standard stream
* objects. For other declarations, see
* http://gcc.gnu.org/onlinedocs/libstdc++/manual/io.html
* and the @link iosfwd I/O forward declarations @endlink
*
* They are required by default to cooperate with the global C
* library's @c FILE streams, and to be available during program
* startup and termination. For more information, see the section of the
* manual linked to above.
*/
//@{
extern istream cin; /// Linked to standard input
extern ostream cout; /// Linked to standard output
extern ostream cerr; /// Linked to standard error (unbuffered)
extern ostream clog; /// Linked to standard error (buffered)
#ifdef _GLIBCXX_USE_WCHAR_T
extern wistream wcin; /// Linked to standard input
extern wostream wcout; /// Linked to standard output
extern wostream wcerr; /// Linked to standard error (unbuffered)
extern wostream wclog; /// Linked to standard error (buffered)
#endif
//@}
// For construction of filebuffers for cout, cin, cerr, clog et. al.
static ios_base::Init __ioinit;
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace
#endif /* _GLIBCXX_IOSTREAM */
可以看出iosteam中只是对std::cout做了一个extern的声明,具体如何去定义的还是去看操作系统链接的libstdc++中的实现。
以gcc中libstdc++的实现为例,std::cout在gcc/libstdc+±v3/src/c++98/ios_init.cc(链接)中进行初始化
ios_base::Init::Init()
{
if (__gnu_cxx::__exchange_and_add_dispatch(&_S_refcount, 1) == 0)
{
// Standard streams default to synced with "C" operations.
_S_synced_with_stdio = true;
new (&buf_cout_sync) stdio_sync_filebuf<char>(stdout);
new (&buf_cin_sync) stdio_sync_filebuf<char>(stdin);
new (&buf_cerr_sync) stdio_sync_filebuf<char>(stderr);
// The standard streams are constructed once only and never
// destroyed.
new (&cout) ostream(&buf_cout_sync);
new (&cin) istream(&buf_cin_sync);
new (&cerr) ostream(&buf_cerr_sync);
new (&clog) ostream(&buf_cerr_sync);
}
...
}
从源码上看,gcc其实是通一个stdio_sync_filebuf的对象,实现了一个通过文件描述符进行io的buffer,不论是std::cout还是std::cin,std::cerr,都通过对应的文件描述符与buffer绑定,然后通过这个buffer来构造ostream或者istream,所以这个buffer至关重要,他的定义位于gcc/libstdc+±v3/include/ext/stdio_sync_filebuf.h(链接),简化版的代码(删掉wchar支持和c++11特性)如下所示
#ifndef _STDIO_SYNC_FILEBUF_H
#define _STDIO_SYNC_FILEBUF_H 1
#pragma GCC system_header
#include // GNU extensions are currently omitted
#include
#include
#include // For __c_file
#include // For __exchange、
namespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
/**
* @brief Provides a layer of compatibility for C.
* @ingroup io
*
* This GNU extension provides extensions for working with standard
* C FILE*'s. It must be instantiated by the user with the type of
* character used in the file stream, e.g., stdio_filebuf.
*/
template<typename _CharT, typename _Traits = std::char_traits<_CharT> >
class stdio_sync_filebuf : public std::basic_streambuf<_CharT, _Traits>
{
public:
// Types:
typedef _CharT char_type;
typedef _Traits traits_type;
typedef typename traits_type::int_type int_type;
typedef typename traits_type::pos_type pos_type;
typedef typename traits_type::off_type off_type;
private:
typedef std::basic_streambuf<_CharT, _Traits> __streambuf_type;
// Underlying stdio FILE
std::__c_file* _M_file;
// Last character gotten. This is used when pbackfail is
// called from basic_streambuf::sungetc()
int_type _M_unget_buf;
public:
explicit
stdio_sync_filebuf(std::__c_file* __f)
: _M_file(__f), _M_unget_buf(traits_type::eof())
{ }
/**
* @return The underlying FILE*.
*
* This function can be used to access the underlying C file pointer.
* Note that there is no way for the library to track what you do
* with the file, so be careful.
*/
std::__c_file*
file() { return this->_M_file; }
protected:
int_type
syncgetc();
int_type
syncungetc(int_type __c);
int_type
syncputc(int_type __c);
virtual int_type
underflow()
{
int_type __c = this->syncgetc();
return this->syncungetc(__c);
}
virtual int_type
uflow()
{
// Store the gotten character in case we need to unget it.
_M_unget_buf = this->syncgetc();
return _M_unget_buf;
}
virtual int_type
pbackfail(int_type __c = traits_type::eof())
{
int_type __ret;
const int_type __eof = traits_type::eof();
// Check if the unget or putback was requested
if (traits_type::eq_int_type(__c, __eof)) // unget
{
if (!traits_type::eq_int_type(_M_unget_buf, __eof))
__ret = this->syncungetc(_M_unget_buf);
else // buffer invalid, fail.
__ret = __eof;
}
else // putback
__ret = this->syncungetc(__c);
// The buffered character is no longer valid, discard it.
_M_unget_buf = __eof;
return __ret;
}
virtual std::streamsize
xsgetn(char_type* __s, std::streamsize __n);
virtual int_type
overflow(int_type __c = traits_type::eof())
{
int_type __ret;
if (traits_type::eq_int_type(__c, traits_type::eof()))
{
if (std::fflush(_M_file))
__ret = traits_type::eof();
else
__ret = traits_type::not_eof(__c);
}
else
__ret = this->syncputc(__c);
return __ret;
}
virtual std::streamsize
xsputn(const char_type* __s, std::streamsize __n);
virtual int
sync()
{ return std::fflush(_M_file); }
virtual std::streampos
seekoff(std::streamoff __off, std::ios_base::seekdir __dir,
std::ios_base::openmode = std::ios_base::in | std::ios_base::out)
{
std::streampos __ret(std::streamoff(-1));
int __whence;
if (__dir == std::ios_base::beg)
__whence = SEEK_SET;
else if (__dir == std::ios_base::cur)
__whence = SEEK_CUR;
else
__whence = SEEK_END;
#ifdef _GLIBCXX_USE_LFS
if (!fseeko64(_M_file, __off, __whence))
__ret = std::streampos(ftello64(_M_file));
#else
if (!fseek(_M_file, __off, __whence))
__ret = std::streampos(std::ftell(_M_file));
#endif
return __ret;
}
virtual std::streampos
seekpos(std::streampos __pos,
std::ios_base::openmode __mode =
std::ios_base::in | std::ios_base::out)
{ return seekoff(std::streamoff(__pos), std::ios_base::beg, __mode); }
};
template<>
inline stdio_sync_filebuf<char>::int_type
stdio_sync_filebuf<char>::syncgetc()
{ return std::getc(_M_file); }
template<>
inline stdio_sync_filebuf<char>::int_type
stdio_sync_filebuf<char>::syncungetc(int_type __c)
{ return std::ungetc(__c, _M_file); }
template<>
inline stdio_sync_filebuf<char>::int_type
stdio_sync_filebuf<char>::syncputc(int_type __c)
{ return std::putc(__c, _M_file); }
template<>
inline std::streamsize
stdio_sync_filebuf<char>::xsgetn(char* __s, std::streamsize __n)
{
std::streamsize __ret = std::fread(__s, 1, __n, _M_file);
if (__ret > 0)
_M_unget_buf = traits_type::to_int_type(__s[__ret - 1]);
else
_M_unget_buf = traits_type::eof();
return __ret;
}
template<>
inline std::streamsize
stdio_sync_filebuf<char>::xsputn(const char* __s, std::streamsize __n)
{ return std::fwrite(__s, 1, __n, _M_file); }
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace
#endif
这个类继承自std::basic_streambuf,和output相关的函数只重写了overflow,xsputn,sync和seekoff,而seekoff和sync和本文的关联不大,所以不做赘述。xsputn的实现看上去十分简洁,用fwrite把buffer输出到标准输出对应的文件描述符中。而overflow的作用是给定一个字符,判断是否输出到了eof,顺带将这个字符输出,在gcc中的逻辑是:假如字符是eof,根据fflush的返回值判断是否返回eof,假如不是eof,直接用fputc打印当前字符。
从上面的分析可知,可以仿照stdio_sync_filebuf的实现,声明一个类继承自std::basic_streambuf,并且重写xsputn和overflow,然后用上述的这个类重新构造std::cout,便达到可以拦截标准输出的目的。源码如下:
qtStreamBuf.h
#ifndef QTSTREAMBUF_H
#define QTSTREAMBUF_H
#include
#include // For __c_file
class MainWindow;
using _CharT=char;
using _Traits = std::char_traits<_CharT>;
class qtStreamBuf : public std::basic_streambuf<_CharT, _Traits>
{
public:
typedef _CharT char_type;
typedef _Traits traits_type;
typedef typename traits_type::int_type int_type;
private:
MainWindow* window;
int_type syncput(int_type c);
public:
qtStreamBuf(MainWindow* window)
: window(window)
{ }
protected:
virtual int_type overflow(int_type __c = traits_type::eof())
{
int_type __ret;
if (traits_type::eq_int_type(__c, traits_type::eof()))
{
__ret = traits_type::not_eof(__c);
}
else
__ret = syncput(__c);
return __ret;
}
virtual std::streamsize xsputn(const char_type* __s, std::streamsize __n);
};
#endif
qtStreamBuf.cpp
#include "qtstreambuf.h"
#include "mainwindow.h"
std::streamsize qtStreamBuf::xsputn(const char* __s, std::streamsize __n)
{
std::string s(__s,__n);
window->Append(QString::fromStdString(s));
return __n;
}
qtStreamBuf::int_type qtStreamBuf::syncput(qtStreamBuf::int_type c) {
window->Append(QString(char(c)));
return c;
}
此时,只需要在MainWindow中定义一个Append方法,把std::cout中的输出追加到QTextEdit中
void MainWindow::Append(const QString &text)
{
static QString textbuf;
textbuf+=text;
ui->logText->setText(textbuf);
}
在MainWindow构造时hook一下std::cout,便可以将其重定向到QTextEdit中
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
buffer=std::make_shared<qtStreamBuf>(this);
new (&std::cout) std::ostream(buffer.get());
....
}
``