web-dev-qa-db-ja.com

coutをWindowsのコンソールにリダイレクトする

比較的古いアプリケーションを使用しています。いくつかのマイナーな変更により、Visual C++ 2008でほぼ完全にビルドされます。私が気付いたことの1つは、「デバッグコンソール」が正しく機能していないことです。基本的に、以前はAllocConsole()を使用して、デバッグ出力用のコンソールを作成しました。次に、freopenを使用してstdoutにリダイレクトします。これは、CおよびC++スタイルのIOの両方で完全に機能しました。

さて、それはCスタイルのIOでのみ動作するようです。 coutなどをAllocConsole()で割り当てられたコンソールにリダイレクトする適切な方法は何ですか?

これは、以前は機能していたコードです。

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

[〜#〜] edit [〜#〜]:私に発生したことの1つは、Cスタイルを使用してオーバーフローメソッドが書き込むカスタムstreambufを作成できることですIO std::coutのデフォルトのストリームバッファをそれで置き換えます。しかし、それは警官のように思われます。2008年にこれを行う適切な方法はありますか?これはおそらくMSが見落としたものですか?

EDIT2:わかりました。それで、上で説明したアイデアを実装しました。基本的には次のようになります。

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;
}

誰でも、std::coutを栄光に輝かせることよりも優れた/よりクリーンなソリューションがありますfputc

39
Evan Teran

ポータブルソリューションを回答フォームに投稿して、受け入れられるようにしています。基本的に、私はcoutstreambufを、最終的にリダイレクトされるcファイルI/Oを使用して実装されているものに置き換えました。皆様のご意見に感謝いたします。

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;
}
19
Evan Teran

2018年2月更新:

この問題を修正する関数の最新バージョンは次のとおりです。

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();
    }
}

この関数を定義するには、次のインクルードセットが必要です。

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

簡単に言うと、この関数は、C/C++ランタイムの標準入出力ハンドルとWin32プロセスに関連付けられている現在の標準ハンドルを同期させます。 ドキュメント で述べたように、AllocConsoleはこれらのプロセスハンドルを変更するため、AllocConsoleの後にこの関数を呼び出してランタイムハンドルを更新するだけで済みます。それ以外の場合は、ラッチされたハンドルが残ります。ランタイムが初期化されたとき。基本的な使用法は次のとおりです。

// 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);

この関数はいくつかの改訂を経ているため、履歴情報や代替案に興味がある場合は、この回答の編集を確認してください。現在の答えはこの問題の最善の解決策ですが、最も柔軟性が高く、どのVisual Studioバージョンでも作業できます。

32
Roger Sanders

コンソールがデバッグ専用の場合は、OutputDebugStringA/OutputDebugStringW関数を使用できます。デバッグモードの場合、出力はVSの出力ウィンドウに送信されます。それ以外の場合は、 DebugView を使用して表示できます。

8
Dmitry Khalatov

オリジナルの場合は、sync_with_stdio(1)の例を使用できます。

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

これはc ++スタイルのI/OのVC++ 2017で動作します

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

この2つのライナーを試してください:

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

レイモンド・マルティノーは、それが「あなたが最初に行うこと」であることについて良い点を述べています。

私はリダイレクトの問題を抱えていましたが、それは今の詳細を忘れてしまいました。アプリの実行の非常に早い段階で、ランタイムは出力の方向についていくつかの決定を行い、それがアプリケーションの残りの部分に続きます。

CRTソースを介してこれを実行した後、CRT内の変数をクリアすることでこのメカニズムを覆すことができました。これにより、AllocConsoleを実行した後で、別のことを検討することができました。

明らかに、この種のものは移植可能ではなく、おそらくツールチェーンのバージョン間でさえ移植可能ではありませんが、それはあなたを助けるかもしれません。

AllocConsoleの後で、次のcoutの出力までずっと進んで、それがどこに向かっているのか、そしてその理由を調べます。

0
Will Dean

Iosライブラリには、C++ IOを標準C IOが使用しているものに再同期できるようにする関数があります:ios :: sync_with_stdio()。

ここに素敵な説明があります: http://dslweb.nwnexus.com/~ast/dload/guicon.htm

0
DarthPingu

私が知ることができることから、コンソールでの最初のアクティビティであれば、コードはVC 2005で動作するはずです。

いくつかの可能性を確認した後、コンソールを割り当てる前に何かを書き込もうとしている可能性があります。その時点でstd :: coutまたはstd :: wcoutへの書き込みは失敗し、エラーフラグをクリアしてからさらに出力を行う必要があります。

0