我有一个相对较旧的应用程序。通过一些小的改动,它几乎完全用Visual C ++ 2008构建。我注意到的一件事是我的“调试控制台”工作不正常。基本上在过去,我使用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);
}
编辑:我遇到的一件事是我可以创建一个自定义streambuf,其溢出方法使用C样式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
?
答案 0 :(得分:30)
以下是解决此问题的最新版功能:
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进程关联的当前标准句柄同步。正如the documentation中提到的,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版本。
答案 1 :(得分:19)
我正在以答案形式发布便携式解决方案,因此可以接受。基本上,我将cout
的{{1}}替换为使用c文件I / O实现的,最终被重定向。感谢大家的意见。
streambuf
答案 2 :(得分:8)
如果控制台仅用于调试,则可以使用OutputDebugStringA
/ OutputDebugStringW
函数。如果您处于调试模式,它们的输出将定向到VS中的“输出”窗口,否则您可以使用DebugView来查看它。
答案 3 :(得分:1)
原作你可以使用sync_with_stdio(1) 例如:
if(AllocConsole())
{
freopen("CONOUT$", "wt", stdout);
freopen("CONIN$", "rt", stdin);
SetConsoleTitle(L"Debug Console");
std::ios::sync_with_stdio(1);
}
答案 4 :(得分:1)
这适用于VC ++ 2017 for c ++风格的I / O
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;
答案 5 :(得分:0)
ios库具有一个功能,允许您将C ++ IO重新同步到标准C IO使用的任何内容:ios :: sync_with_stdio()。
这里有一个很好的解释:http://dslweb.nwnexus.com/~ast/dload/guicon.htm。
答案 6 :(得分:0)
据我所知,你的代码应该适用于VC 2005,如果它是你在控制台上的第一个活动。
在检查了几种可能性之后,您可能会在分配控制台之前尝试编写内容。此时写入std :: cout或std :: wcout将失败,您需要在进一步输出之前清除错误标志。
答案 7 :(得分:0)
我遇到了重定向问题,我忘记了现在的细节,结果发现在应用程序执行的早期,运行时会对输出方向作出一些决定,然后这些决定将持续到应用程序的其余部分。 / p>
通过CRT源跟进之后,我能够通过清除CRT中的变量来破坏这种机制,这使我在完成AllocConsole后再看一下这些内容。
显然,这种东西不是可移植的,甚至可能跨工具链版本,但它可能会帮助你。
在你的AllocConsole之后,一步一步到下一个cout输出,找出它的去向和原因。
答案 8 :(得分:0)
试试这2个班轮:
AllocConsole(); //debug console
std::freopen_s((FILE**)stdout, "CONOUT$", "w", stdout); //just works
答案 9 :(得分:0)
我不知道,但是关于为什么发生这种情况,freopen("CONOUT$", "w", stdout);
可能不会将过程参数块(NtCurrentPeb()->ProcessParameters->StandardOutput
)中的stdout句柄重定向到对CSRSS / Conhost的LPC调用返回的任何内容响应对进程的附加控制台(NtCurrentPeb()->ProcessParameters->ConsoleHandle
)的stdout句柄的请求。它可能只是进行LPC调用,然后将句柄分配给FILE * stdout
全局变量。 C ++ cout根本不使用FILE * stdout
,并且对于标准句柄而言,可能仍未与PEB同步。
答案 10 :(得分:-1)
我不确定我是否完全理解这个问题,但是如果你想简单地将数据吐出到控制台以用于诊断目的..为什么不尝试使用System :: Diagnostics :: Process :: Execute()方法或者该命名空间中的一些方法?
如果不相关则提前道歉