web-dev-qa-db-fra.com

Redirection de cout vers une console sous Windows

J'ai une application relativement ancienne. Après quelques modifications mineures, il est construit presque parfaitement avec Visual C++ 2008. Une chose que j’ai constatée est que ma "console de débogage" ne fonctionne pas correctement. Dans le passé, j’ai utilisé AllocConsole() pour créer une console à laquelle accéder à ma sortie de débogage. Ensuite, j’utiliserais freopen pour rediriger stdout vers elle. Cela fonctionnait parfaitement avec les E/S de style C et C++.

Maintenant, il semble que cela ne marchera qu'avec IO de style C. Quelle est la manière appropriée de rediriger des éléments tels que cout vers une console affectée avec AllocConsole()?

Voici le code qui fonctionnait auparavant:

if(AllocConsole()) {
    freopen("CONOUT$", "wt", stdout);
    SetConsoleTitle("Debug Console");
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED);
}

EDIT: une chose qui m'est venue à l'esprit est que je pouvais créer un streambuf personnalisé dont la méthode de débordement écrit en utilisant le style C IO et remplacer le tampon de flux par défaut de std::cout. Mais cela semble être une échappatoire. Y a-t-il une bonne façon de faire cela en 2008? Ou est-ce peut-être quelque chose que MS a négligé?

EDIT2 : OK, j'ai donc implémenté l'idée que j'ai énoncée ci-dessus. En gros, ça ressemble à ça:

class outbuf : public std::streambuf {
public:
    outbuf() {
        setp(0, 0);
    }

    virtual int_type overflow(int_type c = traits_type::eof()) {
        return fputc(c, stdout) == EOF ? traits_type::eof() : c;
    }
};

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {
    // create the console
    if(AllocConsole()) {
        freopen("CONOUT$", "w", stdout);
        SetConsoleTitle("Debug Console");
        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED);  
    }

    // set std::cout to use my custom streambuf
    outbuf ob;
    std::streambuf *sb = std::cout.rdbuf(&ob);

    // do some work here

    // make sure to restore the original so we don't get a crash on close!
    std::cout.rdbuf(sb);
    return 0;
}

Quelqu'un a-t-il une solution meilleure/plus propre que de simplement forcer std::cout à être une version modifiée fputc?

36
Evan Teran

Je poste une solution portable sous forme de réponse afin qu'elle puisse être acceptée. Fondamentalement, j'ai remplacé cout 's streambuf par un système implémenté à l'aide d'E/S de fichier c qui est redirigé. Merci à tous pour votre contribution.

class outbuf : public std::streambuf {
public:
    outbuf() {
        setp(0, 0);
    }

    virtual int_type overflow(int_type c = traits_type::eof()) {
        return fputc(c, stdout) == EOF ? traits_type::eof() : c;
    }
};

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {
    // create the console
    if(AllocConsole()) {
        freopen("CONOUT$", "w", stdout);
        SetConsoleTitle("Debug Console");
        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED);  
    }

    // set std::cout to use my custom streambuf
    outbuf ob;
    std::streambuf *sb = std::cout.rdbuf(&ob);

    // do some work here

    // make sure to restore the original so we don't get a crash on close!
    std::cout.rdbuf(sb);
    return 0;
}
18
Evan Teran

Mis à jour en février 2018:

Voici la dernière version d'une fonction qui résout ce problème:

void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr)
{
    // Re-initialize the C runtime "FILE" handles with clean handles bound to "nul". We do this because it has been
    // observed that the file number of our standard handle file objects can be assigned internally to a value of -2
    // when not bound to a valid target, which represents some kind of unknown internal invalid state. In this state our
    // call to "_dup2" fails, as it specifically tests to ensure that the target file number isn't equal to this value
    // before allowing the operation to continue. We can resolve this issue by first "re-opening" the target files to
    // use the "nul" device, which will place them into a valid state, after which we can redirect them to our target
    // using the "_dup2" function.
    if (bindStdIn)
    {
        FILE* dummyFile;
        freopen_s(&dummyFile, "nul", "r", stdin);
    }
    if (bindStdOut)
    {
        FILE* dummyFile;
        freopen_s(&dummyFile, "nul", "w", stdout);
    }
    if (bindStdErr)
    {
        FILE* dummyFile;
        freopen_s(&dummyFile, "nul", "w", stderr);
    }

    // Redirect unbuffered stdin from the current standard input handle
    if (bindStdIn)
    {
        HANDLE stdHandle = GetStdHandle(STD_INPUT_HANDLE);
        if(stdHandle != INVALID_HANDLE_VALUE)
        {
            int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
            if(fileDescriptor != -1)
            {
                FILE* file = _fdopen(fileDescriptor, "r");
                if(file != NULL)
                {
                    int dup2Result = _dup2(_fileno(file), _fileno(stdin));
                    if (dup2Result == 0)
                    {
                        setvbuf(stdin, NULL, _IONBF, 0);
                    }
                }
            }
        }
    }

    // Redirect unbuffered stdout to the current standard output handle
    if (bindStdOut)
    {
        HANDLE stdHandle = GetStdHandle(STD_OUTPUT_HANDLE);
        if(stdHandle != INVALID_HANDLE_VALUE)
        {
            int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
            if(fileDescriptor != -1)
            {
                FILE* file = _fdopen(fileDescriptor, "w");
                if(file != NULL)
                {
                    int dup2Result = _dup2(_fileno(file), _fileno(stdout));
                    if (dup2Result == 0)
                    {
                        setvbuf(stdout, NULL, _IONBF, 0);
                    }
                }
            }
        }
    }

    // Redirect unbuffered stderr to the current standard error handle
    if (bindStdErr)
    {
        HANDLE stdHandle = GetStdHandle(STD_ERROR_HANDLE);
        if(stdHandle != INVALID_HANDLE_VALUE)
        {
            int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
            if(fileDescriptor != -1)
            {
                FILE* file = _fdopen(fileDescriptor, "w");
                if(file != NULL)
                {
                    int dup2Result = _dup2(_fileno(file), _fileno(stderr));
                    if (dup2Result == 0)
                    {
                        setvbuf(stderr, NULL, _IONBF, 0);
                    }
                }
            }
        }
    }

    // Clear the error state for each of the C++ standard stream objects. We need to do this, as attempts to access the
    // standard streams before they refer to a valid target will cause the iostream objects to enter an error state. In
    // versions of Visual Studio after 2005, this seems to always occur during startup regardless of whether anything
    // has been read from or written to the targets or not.
    if (bindStdIn)
    {
        std::wcin.clear();
        std::cin.clear();
    }
    if (bindStdOut)
    {
        std::wcout.clear();
        std::cout.clear();
    }
    if (bindStdErr)
    {
        std::wcerr.clear();
        std::cerr.clear();
    }
}

Pour définir cette fonction, vous aurez besoin du jeu d’inclusions suivant:

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

En un mot, cette fonction synchronise les descripteurs d'entrée/sortie/d'erreur standard du moteur d'exécution C/C++ avec les descripteurs standard actuels associés au processus Win32. Comme mentionné dans la documentation , AllocConsole modifie ces processus pour nous, il suffit donc d'appeler cette fonction après AllocConsole pour mettre à jour les descripteurs d'exécution, sinon nous nous retrouverons avec les descripteurs verrouillés lors de l'exécution a été initialisé. L'utilisation de base est la suivante:

// Allocate a console window for this process
AllocConsole();

// Update the C/C++ runtime standard input, output, and error targets to use the console window
BindCrtHandlesToStdHandles(true, true, true);

Cette fonction a fait l'objet de plusieurs révisions. Vérifiez donc les modifications apportées à cette réponse si vous êtes intéressé par des informations historiques ou des alternatives. La réponse actuelle est toutefois la meilleure solution à ce problème, offrant le maximum de flexibilité et fonctionnant avec n’importe quelle version de Visual Studio.

28
Roger Sanders

Si la console est uniquement destinée au débogage, vous pouvez simplement utiliser les fonctions OutputDebugStringA/OutputDebugStringW. Leur sortie est dirigée vers la fenêtre de sortie dans VS si vous êtes en mode débogage, sinon vous pouvez utiliser DebugView pour le voir.

8
Dmitry Khalatov

Pour l'original, vous pouvez simplement utiliser sync_with_stdio (1) Exemple:

if(AllocConsole())
{
    freopen("CONOUT$", "wt", stdout);
    freopen("CONIN$", "rt", stdin);
    SetConsoleTitle(L"Debug Console");
    std::ios::sync_with_stdio(1);
}
1
etc597

Cela fonctionne avec VC++ 2017 pour les E/S de style c ++

AllocConsole();

// use static for scope
static ofstream conout("CONOUT$", ios::out); 
// Set std::cout stream buffer to conout's buffer (aka redirect/fdreopen)
cout.rdbuf(conout.rdbuf());

cout << "Hello World" << endl;
1
Alberto

Essayez cette doublure:

    AllocConsole(); //debug console
    std::freopen_s((FILE**)stdout, "CONOUT$", "w", stdout); //just works
0
Charlie

Raymond Martineau souligne que c'est «la première chose à faire».

J'ai eu un problème de redirection, dont j'oublie les détails, car il s'est avéré que très tôt dans l'exécution de l'application, le moteur d'exécution prend des décisions concernant les directions de sortie, qui durent ensuite pour le reste de l'application.

Après avoir suivi cette opération par le biais de la source CRT, j’ai pu subvertir ce mécanisme en effaçant une variable du CRT, ce qui l’a obligé à revoir les choses une fois que j’avais fait mon AllocConsole. 

Évidemment, ce genre de choses ne sera pas portable, peut-être même entre les versions de chaînes d'outils, mais cela pourrait vous aider.

Après votre AllocConsole, descendez dans la prochaine sortie du cout et découvrez où il va et pourquoi.

0
Will Dean

La bibliothèque ios a une fonction qui vous permet de resynchroniser C++ IO avec tout ce que le standard C IO utilise: ios :: sync_with_stdio ().

Il y a une bonne explication ici: http://dslweb.nwnexus.com/~ast/dload/guicon.htm .

0
DarthPingu

D'après ce que je peux dire, votre code devrait fonctionner avec VC 2005, s'il s'agit de votre première activité avec la console. 

Après avoir vérifié quelques possibilités, vous essayez peut-être d’écrire quelque chose avant d’allouer la console. L'écriture sur std :: cout ou std :: wcout à ce stade échouera et vous devrez effacer les indicateurs d'erreur avant de générer une sortie ultérieure. 

0
Raymond Martineau