Home > Net >  Redirect cerr using rdbuf: set it once and forget it until app ends
Redirect cerr using rdbuf: set it once and forget it until app ends

Time:09-13

I am attempting to redirect cerr to a file at the start of a program, then at the end of the program set it back to normal. This is along the lines of the accepted answer of Use cout or cerr to output to console after it has been redirected to file

I am running into strange behavior in C Builder for this. The environment seems to want me to refresh the stream redirection every time the program wants to output something.

I created the following test app to demonstrate the problem. If I comment out the #define on the first line, I get memory access violations. When the #define is uncommented, it seems to work fine while the program is executing; however, in a larger program, I believe I have also gotten the same memory access violations after the application terminates (works fine during execution with that #define in there – I still need to explore that one some more to track it down).

//#define MAKE_WORK

#include <vcl.h>
#pragma hdrstop

#include <fstream>
#include "TestRdbuf.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
std::streambuf* origBuff;

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
    std::ofstream oFile("ErrorLog.txt");
    origBuff = std::cerr.rdbuf();
    std::cerr.rdbuf(oFile.rdbuf());

    std::cerr << "Started: " << Now() << std::endl;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
#ifdef MAKE_WORK
    std::ofstream oFile("ErrorLog.txt", std::ios_base::app);
    std::cerr.rdbuf(oFile.rdbuf());
#endif

    std::cerr << "Closing:" << Now() << std::endl;
    std::cerr.rdbuf(origBuff);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
#ifdef MAKE_WORK
    std::ofstream oFile("ErrorLog.txt", std::ios_base::app);
    std::cerr.rdbuf(oFile.rdbuf());
#endif

    std::cerr << "Debug log time: " << Now() << std::endl;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::btnShowFileClick(TObject *Sender)
{
    TMemo * Memo = new TMemo(pnlBody);
    Memo->Parent = pnlBody;
    Memo->Align = TAlign::alClient;
    Memo->Font->Size = 12;
    Memo->Lines->LoadFromFile("ErrorLog.txt");
    Memo->ReadOnly = true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
#ifdef MAKE_WORK
    std::ofstream oFile("ErrorLog.txt", std::ios_base::app);
    std::cerr.rdbuf(oFile.rdbuf());
#endif

    std::cerr << "Exit clicked: " << Now() << std::endl;

    Application->Terminate();
}
//---------------------------------------------------------------------------

CodePudding user response:

First off, DO NOT use the Form's OnCreate event in C . It is a Delphi idiom that introduces undefined behavior in C , as it can be triggered before the Form's constructor, so use the actual constructor instead. Likewise, the Form's OnDestroy event can be triggered after the Form's destructor, so use the actual destructor instead.

Second, more importantly, oFile is a local variable. You are setting cerr to share the same streambuf object as oFile, but then oFile goes out of scope afterwards and is destroyed, which also destroys its streambuf object, leaving cerr holding a dangling pointer to invalid memory, hence the Access Violations.

You need to keep the ofstream object alive while cerr is sharing its streambuf object, eg:

#include <vcl.h>
#pragma hdrstop

#include <fstream>
#include "TestRdbuf.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
std::ofstream oFile;
std::streambuf* origBuff;

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    oFile.open("ErrorLog.txt");

    origBuff = std::cerr.rdbuf();
    std::cerr.rdbuf(oFile.rdbuf());

    std::cerr << "Started: " << Now() << std::endl;
}
//---------------------------------------------------------------------------
__fastcall TForm1::~TForm1()
{
    std::cerr << "Closing:" << Now() << std::endl;
    std::cerr.rdbuf(origBuff);

    oFile.close();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    std::cerr << "Debug log time: " << Now() << std::endl;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::btnShowFileClick(TObject *Sender)
{
    TMemo *Memo = new TMemo(pnlBody);
    Memo->Parent = pnlBody;
    Memo->Align = TAlign::alClient;
    Memo->Font->Size = 12;
    Memo->Lines->LoadFromFile("ErrorLog.txt");
    Memo->ReadOnly = true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
    std::cerr << "Exit clicked: " << Now() << std::endl;
    Application->Terminate();
}
//---------------------------------------------------------------------------
  • Related