c ++ - Hvordan kan jeg omdirigere stdout til noget synligt display i et Windows-program?

Indlæg af Hanne Mølgaard Plasc

Problem



Jeg har adgang til et tredjepartsbibliotek, der gør 'gode ting'. Det udsteder status og fremskridt meddelelser til stdout. I en konsol-applikation kan jeg se disse meddelelser helt fint. I en Windows-applikation går de bare til spanden.


Er der en ret simpel måde at omdirigere stdout og stderr til en tekstkontrol eller et andet synligt sted. Ideelt set ville dette ikke kræve nogen genkompilering af tredjepartskoden. Det ville bare opfange dampene på et lavt niveau. Jeg kan lide en løsning, hvor jeg bare # inkluderer overskriften, kalder initialiseringsfunktionen og link biblioteket som i ...


#include "redirectStdFiles.h"

void function(args...)
{
  TextControl* text = new TextControl(args...);
  initializeRedirectLibrary(text, ...);

  printf("Message that will show up in the TextControl
");
  std::cout << "Another message that also shows up in TextControl
";
}


Endnu bedre ville være, hvis det brugte en grænseflade, som jeg kunne tilsidesætte, så den ikke er bundet til et bestemt GUI-bibliotek.


class StdFilesRedirector
{
  public:
    writeStdout(std::string const& message) = 0;
    writeStderr(std::string const& errorMessage) = 0;
    readStdin(std::string &putReadStringHere) = 0;
};


Drømmer jeg bare? Eller ved nogen af ​​noget, der kan gøre noget som dette?


Rediger efter to svar: Jeg synes at bruge freopen til at omdirigere filerne er et godt første skridt. For en komplet løsning skulle der være en ny tråd oprettet for at læse filen og vise output. For debugging ville det være nok at lave en 'hale -f' i et cygwin-skalvindue. For en mere poleret ansøgning ... Hvilket er hvad jeg vil skrive ... der ville være noget ekstra arbejde for at skabe tråden mv.

Bedste reference


Du skal oprette pipe (med CreatePipe ()) og derefter vedhæfte stdout til det s skrive ende med SetStdHandle (), så kan du læse fra pipens læste ende med ReadFile () og sætte tekst du kommer derfra hvor som helst du vil . [24] [25] [26]

Andre referencer 1


Du kan omdirigere stdout, stderr og stdin ved hjælp af freopen. [27]


Fra ovenstående link:


/* freopen example: redirecting stdout */
#include <stdio.h>

int main ()
{
  freopen ("myfile.txt","w",stdout);
  printf ("This sentence is redirected to a file.");
  fclose (stdout);
  return 0;
}


Du kan også køre dit program via kommandoprompt som sådan:


a.exe > stdout.txt 2> stderr.txt

Andre referencer 2


Du søger nok efter noget i den retning:


    #define OUT\_BUFF\_SIZE 512

    int main(int argc, char* argv[])
    {
        printf("1: stdout
");

        StdOutRedirect stdoutRedirect(512);
        stdoutRedirect.Start();
        printf("2: redirected stdout
");
        stdoutRedirect.Stop();

        printf("3: stdout
");

        stdoutRedirect.Start();
        printf("4: redirected stdout
");
        stdoutRedirect.Stop();

        printf("5: stdout
");

        char szBuffer[OUT\_BUFF\_SIZE];
        int nOutRead = stdoutRedirect.GetBuffer(szBuffer,OUT\_BUFF\_SIZE);
        if(nOutRead)
            printf("Redirected outputs: 
\%s
",szBuffer);

        return 0;
    }


Denne klasse vil gøre det:


#include <windows.h>

#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <iostream>

#ifndef \_USE\_OLD\_IOSTREAMS
using namespace std;
#endif

#define READ\_FD 0
#define WRITE\_FD 1

#define CHECK(a) if ((a)!= 0) return -1;

class StdOutRedirect
{
    public:
        StdOutRedirect(int bufferSize);
        ~StdOutRedirect();

        int Start();
        int Stop();
        int GetBuffer(char *buffer, int size);

    private:
        int fdStdOutPipe[2];
        int fdStdOut;
};

StdOutRedirect::~StdOutRedirect()
{
    \_close(fdStdOut);
    \_close(fdStdOutPipe[WRITE\_FD]);
    \_close(fdStdOutPipe[READ\_FD]);
}
StdOutRedirect::StdOutRedirect(int bufferSize)
{
    if (\_pipe(fdStdOutPipe, bufferSize, O\_TEXT)!=0)
    {
        //treat error eventually
    }
    fdStdOut = \_dup(\_fileno(stdout));
}

int StdOutRedirect::Start()
{
    fflush( stdout );
    CHECK(\_dup2(fdStdOutPipe[WRITE\_FD], \_fileno(stdout)));
    ios::sync\_with\_stdio();
    setvbuf( stdout, NULL, \_IONBF, 0 ); // absolutely needed
    return 0;
}

int StdOutRedirect::Stop()
{
    CHECK(\_dup2(fdStdOut, \_fileno(stdout)));
    ios::sync\_with\_stdio();
    return 0;
}

int StdOutRedirect::GetBuffer(char *buffer, int size)
{
    int nOutRead = \_read(fdStdOutPipe[READ\_FD], buffer, size);
    buffer[nOutRead] = '';
    return nOutRead;
}


Her er resultatet:


1: stdout
3: stdout
5: stdout
Redirected outputs:
2: redirected stdout
4: redirected stdout

Andre referencer 3


Når du opretter en proces ved hjælp af CreateProcess () kan du vælge en HANDLE, som stdout og stderr skal skrives til. Denne HANDLE kan være en fil, som du leder output til. [28]


Dette vil lade dig bruge koden uden at genkompilere den. Bare udfør det og i stedet for at bruge system() eller hvad ikke, brug CreateProcess().


Den HANDLE du giver til CreateProcess() kan også være den af ​​et rør, du har oprettet, og så kan du læse fra røret og gøre noget andet med dataene.

Andre referencer 4


Du kunne gøre noget som dette med cout eller cerr:


// open a file stream
ofstream out("filename");
// save cout's stream buffer
streambuf *sb = cout.rdbuf();
// point cout's stream buffer to that of the open file
cout.rdbuf(out.rdbuf());
// now you can print to file by writing to cout
cout << "Hello, world!";
// restore cout's buffer back
cout.rdbuf(sb);


Eller du kan gøre det med en std::stringstream eller en anden klasse afledt fra std::ostream.


For at omdirigere stdout skal du genåbne filhåndtaget. Denne tråd har nogle ideer af denne art. [29]

Andre referencer 5


Her sætter vi et nyt indgangssted consoleMain, der tilsidesætter din egen.



  1. Bestem adgangspunktet for din ansøgning. I VisualStudio skal du vælge Projektegenskaber/Linker/Advanced/Entry Point . Lad os kalde det defaultMain.

  2. Et sted i din kildekode erklærer det oprindelige indtastningssted (så vi kan kæde til det) og det nye indgangspunkt. Begge skal erklæres extern "C" for at forhindre navngivning.


    extern "C"
    {
      int defaultMain (void);
      int consoleMain (void);
    }
    

  3. Gennemfør indgangspunktfunktionen.


    \_\_declspec(noinline) int consoleMain (void)
    {
      // \_\_debugbreak(); // Break into the program right at the entry point!
      AllocConsole();    // Create a new console
      freopen("CON", "w", stdout);
      freopen("CON", "w", stderr);
      freopen("CON", "r", stdin); // Note: "r", not "w".
      return defaultMain();
    }
    

  4. Tilføj din testkode et eller andet sted, f.eks. i en knap, klik på handling.


    fwprintf(stdout, L"This is a test to stdout
    ");
    fwprintf(stderr, L"This is a test to stderr
    ");
    cout<<"Enter an Integer Number Followed by ENTER to Continue" << endl;
    \_flushall();
    int i = 0;
    int Result = wscanf( L"\%d", &i);
    printf ("Read \%d from console. Result = \%d
    ", i, Result);
    

  5. Indstil consoleMain som det nye indgangspunkt ( Projektegenskaber/Linker/Avanceret/Indgangspunkt ).


Andre referencer 6


Takket være gamedev-linket i svaret fra greyfade kunne jeg skrive og teste dette enkle stykke kode [30]


AllocConsole();

*stdout = *\_tfdopen(\_open\_osfhandle((intptr\_t) GetStdHandle(STD\_OUTPUT\_HANDLE), \_O\_WRONLY), \_T("a"));
*stderr = *\_tfdopen(\_open\_osfhandle((intptr\_t) GetStdHandle(STD\_ERROR\_HANDLE), \_O\_WRONLY), \_T("a"));
*stdin = *\_tfdopen(\_open\_osfhandle((intptr\_t) GetStdHandle(STD\_INPUT\_HANDLE), \_O\_WRONLY), \_T("r"));


printf("A printf to stdout
");
std::cout << "A << to std::cout
";
std::cerr << "A << to std::cerr
";
std::string input;
std::cin >> input;

std::cout << "value read from std::cin is " << input << std::endl;


Det virker og er tilstrækkeligt til debugging. At få teksten til et mere attraktivt GUI-element ville tage lidt mere arbejde.

Andre referencer 7


Dette er hvad jeg gør:



  1. CreatePipe ().

  2. CreateProcess () med håndtaget fra CreatePipe () bruges som stdout til den nye proces.

  3. Opret en timer eller en tråd, der kalder ReadFile () på det håndtag, og lægger dataene i en tekstboks eller whatnot.