問題描述
我有一個相對較舊的應用程序.通過一些細微的更改,它幾乎可以完美地與 Visual C++ 2008 一起構建.我注意到的一件事是我的調試控制臺"工作不太正常.基本上在過去,我使用 AllocConsole()
為我的調試輸出創建一個控制臺.然后我會使用 freopen
將 stdout
重定向到它.這與 C 和 C++ 風格的 IO 完美配合.
I have an application which is a relatively old. Through some minor changes, it builds nearly perfectly with Visual C++ 2008. One thing that I've noticed is that my "debug console" isn't quite working right. Basically in the past, I've use AllocConsole()
to create a console for my debug output to go to. Then I would use freopen
to redirect stdout
to it. This worked perfectly with both C and C++ style IO.
現在,它似乎只適用于 C 風格的 IO.將 cout
之類的東西重定向到使用 AllocConsole()
分配的控制臺的正確方法是什么?
Now, it seems that it will only work with C style IO. What is the proper way to redirect things like cout
to a console allocated with AllocConsole()
?
這是曾經可以工作的代碼:
Here's the code which used to work:
if(AllocConsole()) {
freopen("CONOUT$", "wt", stdout);
SetConsoleTitle("Debug Console");
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED);
}
編輯:我想到的一件事是我可以制作一個自定義的流緩沖,其溢出方法使用 C 風格的 IO 寫入并替換 std::cout
的默認值流緩沖區.但這似乎是一種逃避.2008 年有沒有合適的方法來做到這一點?或者這可能是 MS 忽略的東西?
EDIT: one thing which occurred to me is that I could make a custom streambuf whose overflow method writes using C style IO and replace std::cout
's default stream buffer with it. But that seems like a cop-out. Is there a proper way to do this in 2008? Or is this perhaps something that MS overlooked?
EDIT2:好的,我已經實現了我上面闡述的想法.基本上它看起來像這樣:
EDIT2: OK, so I've made an implementaiton of the idea I spelled out above. Basically it looks like this:
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
之外,還有人有更好/更簡潔的解決方案嗎?
Anyone have a better/cleaner solution than just forcing std::cout
to be a glorified fputc
?
推薦答案
2018 年 2 月更新:
這是修復此問題的函數的最新版本:
Updated Feb 2018:
Here is the latest version of a function which fixes this problem:
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();
}
}
為了定義這個函數,你需要以下一組包含:
In order to define this function, you'll need the following set of includes:
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#include <iostream>
簡而言之,此函數將 C/C++ 運行時標準輸入/輸出/錯誤句柄與與 Win32 進程關聯的當前標準句柄同步.如文檔中所述,AllocConsole 為我們更改了這些進程句柄,因此所需要做的就是在 AllocConsole 之后調用這個函數來更新運行時句柄,否則我們將剩下運行時初始化時鎖存的句柄.基本用法如下:
In a nutshell, this function synchronizes the C/C++ runtime standard input/output/error handles with the current standard handles associated with the Win32 process. As mentioned in the documentation, AllocConsole changes these process handles for us, so all that's required is to call this function after AllocConsole to update the runtime handles, otherwise we'll be left with the handles that were latched when the runtime was initialized. Basic usage is as follows:
// 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 版本.
This function has gone through several revisions, so check the edits to this answer if you're interested in historical information or alternatives. The current answer is the best solution to this problem however, giving the most flexibility and working on any Visual Studio version.
這篇關于將 cout 重定向到 Windows 中的控制臺的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!