例如:
void MyHandler( const char* data );
//<<Magical redirection code>>
printf( "test" );
std::cout << "test" << std::endl;
//MyHandler should have been called with "test" twice, at this point
- 我该如何实现这种/类似的行为?
void MyHandler( const char* data );
//<<Magical redirection code>>
printf( "test" );
std::cout << "test" << std::endl;
//MyHandler should have been called with "test" twice, at this point
@Konrad Rudolph 是正确的,对于cout/cerr/clog来说,你完全可以轻松地做到这一点。你甚至不需要自己实现streambuf,只需使用一个ostringstream即可。
// Redirect cout.
streambuf* oldCoutStreamBuf = cout.rdbuf();
ostringstream strCout;
cout.rdbuf( strCout.rdbuf() );
// This goes to the string stream.
cout << "Hello, World!" << endl;
// Restore old cout.
cout.rdbuf( oldCoutStreamBuf );
// Will output our Hello World! from above.
cout << strCout.str();
同样的方法适用于cerr和clog,但是据我的经验,这并不适用于stdout/stderr的所有情况,因此printf无法在那里输出。 cout输出到stdout,但重定向cout并不能重定向所有的stdout。至少,在我的经验中是这样的。当有内容被写入标准输出流时,调用回调函数是很困难的:这需要覆盖低级别、系统特定的函数 (在 POSIX 系统上,至少需要覆盖 write
函数,而它的调用方式可能取决于标准库的实现,因此在例如 glibc 和 musl 之间可能会有所不同)。
但是,根据你确切的需求,你可以在 C++ 中通过直接操作 C++ 流缓冲区而无需诉诸于底层 OS 特定的函数来解决这个问题。
为此,您需要创建自己的std::streambuf
实现,即自己的流缓冲区。
一旦你拥有了它,你可以通过切换缓冲区来重定向 std::cout
流:
auto buf = callback_streambuf(MyHandler);
auto pold_buffer = std::cout.rdbuf(&buf);
std::cout << "Hello" << std::cout;
// Restore original buffer:
std::cout.rdbuf(pold_buffer);
然而,这个实现不会确切地调用你的回调函数两次。相反,调用次数将取决于几个因素,但通常不会取决于流插入的数量(<<
),没有办法避免这一点!
对于上面的特定示例,回调函数被调用一次,数据为"Hello\n"
。
以下简单的实现演示了如何让streambuf调用你的处理程序:
class callback_streambuf : public std::streambuf {
public:
callback_streambuf(std::function<void(char const*, std::streamsize)> callback) : callback(callback) {}
protected:
std::streamsize xsputn(char_type const* s, std::streamsize count) {
callback(s, count);
return count;
}
private:
std::function<void(char const*, std::streamsize)> callback;
};
这个实现有一些注意事项。例如,当尝试将其用作输入流时,它会出问题。它没有覆盖overflow
(因为我认为这从未被调用过,虽然我在互联网上找到了矛盾的信息;无论如何,添加overflow
将是微不足道的)。我没有实现同步,因此回调将从多个线程并发调用。此外,由于回调没有返回成功状态,所以没有错误处理。我还必须更改回调的签名为
void MyHandler(char const* data, std::streamsize count);
由于data
不是字符串而是原始的char
缓冲区,所以第二个参数是必需的,没有办法从内在上确定它的长度,而且MyHandler
如果不知道数据的长度,则无法处理数据。
std::cout
的流重定向到他们的处理程序。我的代码正是这样做的。它不会重定向底层操作系统、流、真实性,但通常情况下这并不是必要的,而且更难以实现,并且不是平台无关的。 - Konrad Rudolphdup
系统调用解决stdout的解决方案。但是,再次强调,这当然不是平台无关的,也没有这样的解决方案存在。 - Konrad Rudolph可以通过取消引用其指针来禁用stdin/stdout:
FILE fp_old = *stdout; // preserve the original stdout
*stdout = *fopen("/dev/null","w"); // redirect stdout to null
HObject m_ObjPOS = NewLibraryObject(); // call some library which prints unwanted stdout
*stdout=fp_old; // restore stdout
/dev/null
或您指定的任何文件。要接收和进一步处理数据可能需要使用管道。还要记得关闭替换文件。 - MKroehnertMyHandler
,但我认为这是@Charx所能接近的。我给它加1分。 - Jim Lewisstd::cout
对象有一个固定的含义,即输出到标准输出流。您的程序用户可以控制标准输出连接到何处,而不是您。您可以决定是将数据写入文件、标准输出还是其他任何输出流。因此,在代码中,您可以切换要写入的流。
再次强调,向标准输出流写入的目的是为了使用户能够灵活选择输出的位置。您不应该重定向标准输出;这是用户应该拥有自由做出的事情。
另一件事是,您不应该在C++程序中混合使用C IO和C++ IO。选择要使用的IO库,并坚持使用它。
话虽如此,在C++中,通过对std::basic_istream<>
的模板参数进行处理函数模板化,可以优雅地切换用于接收输入的流。然后,该函数将从输入流中独立地读取其输入,无论实际工作的流类型如何。以下是一个示例:
#include<iostream>
#include<fstream>
#include<string>
template<class Ch, class Tr>
void dodge_this(std::basic_istream<Ch, Tr>& in)
{
// in is an input stream. read from it as you read from std::cin.
}
int main(int argc, char* argv[])
{
if( std::string(argv[1]) == "cin" ) {
dodge_this(std::cin);
} else if( std::string(argv[1]) == "file" ) {
std::ifstream file("input.txt");
dodge_this(file);
} else {
dodge_this(dev_null_stream); // i just made that up. you get the idea.
}
}
main()
函数将从一个依赖于程序的第一个命令行参数的源中重定向输入到 dodge_this()
。 - wilhelmtell//-------------------------------- DlgStringbuf Definition -----------------------
class DlgStringbuf : public std::stringbuf
{
public:
DlgStringbuf(void) : _hwndDlg(NULL), _editControlID(0), _accum(""), _lineNum(0) {}
void SetDlg(HWND dlg, int editControlID)
{ _hwndDlg = dlg; _editControlID = editControlID; }
void Clear(void)
{ _accum.clear(); _lineNum = 0; }
protected:
virtual std::streamsize xsputn(const char* s, std::streamsize num)
{
std::mutex m;
std::lock_guard<std::mutex> lg(m);
// Prepend with the line number
std::string str(s, (const uint32_t)num);
str = std::to_string(_lineNum) + ": " + str + "\r\n";
// Accumulate the latest text to the front
_accum = str + _accum;
// Write to the Win32 dialog edit control.
if(_hwndDlg != NULL)
SetDlgItemTextW(_hwndDlg, _editControlID, (LPCWSTR)(std::wstring(_accum.begin(), _accum.end())).c_str());
_lineNum++;
return(num);
}//end xsputn.
private:
std::string _accum;
HWND _hwndDlg;
int _editControlID;
uint32_t _lineNum;
};//end DlgStringbuf.
//-------------------------------- DlgStream Definition ------------------------------
class DlgStream : public std::ostream
{
public:
DlgStream(void) : std::ostream(&_sbuff) {}
void SetDlg(HWND dlg, int editControlID)
{ _sbuff.SetDlg(dlg, editControlID); }
void Clear(void)
{ _sbuff.Clear(); }
private:
DlgStringbuf _sbuff;
};
在WinMain中,在对话框及其编辑控件创建之后的某个地方:
// Redirect all cout usage to the activity dlg box.
// Save output buffer of the stream - use unique pointer with deleter that ensures to restore
// the original output buffer at the end of the program.
auto del = [&](streambuf* p) { cout.rdbuf(p); };
unique_ptr<streambuf, decltype(del)> origBuffer(cout.rdbuf(), del);
// Redirect the output to the dlg stream.
_dlgStream.SetDlg(hwndActivityDlg, IDC_EDIT_ACTIVITY);
_dlgStream.copyfmt(cout);
cout.rdbuf(_dlgStream.rdbuf());
cout << "this is from cout";
char buf[1024];
sprintf(buf, "test");
MyHandler(buf);
还有一些依赖于平台的snprintf和其他几个函数
printf
调用,只是将它们重定向。 - user142162
stdout
的输出重定向到您自己的FILE
实例。然后,您可以使用管道接收数据。链接到提到的答案https://dev59.com/_m445IYBdhLWcg3wia2V#21136532 - MKroehnert