我听了GPT的话,想少写几行代码,于是这样操作:
#include <stdio.h>
#include <Windows.h>
/// <summary>重定向标准输出流到某个文件</summary>
/// <param name="filename">
/// 文件名。如果原来的文件存在,则覆盖它;否则新建一个文件。
/// <para>如果想要将标准输出重定向到控制台,请再次使用此宏,文件名填 "con",
/// 即 Windows 的控制台文件。</para>
/// </param>
#define REDIR_STDOUT(filename) assert(freopen(filename, "w", stdout) != NULL)
// ...
case 4: // 保存链表的数据到文件。
{
REDIR_STDOUT(FILENAME);
pri_dates(&l);
REDIR_STDOUT("con");
}
break;
// ...
HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE); // 输出句柄
CONSOLE_SCREEN_BUFFER_INFO csbi;
assert(GetConsoleScreenBufferInfo(hout, &csbi)); // 断言失败,返回ERROR_ACCESS_DENIED!
pri_dates(&l); 中,l 是链表,这个调用的意思是在屏幕上打印日期链表的内容。用"con"重定向回控制台的时候还是可以在屏幕上输出内容的。后面的 Win32 API 调用部分是因为功能需要增加的。如果没有重定向的话 csbi 的信息可以正常获取,断言成功。不知道为什么无法获取 csbi 的信息,难道是 hout 获取到了非法的句柄了吗?
参考GPT和自己的思路:
根据你的描述,使用 freopen 在应用程序运行时将标准输出流重定向到文件,然后再将其重定向回控制台。然而,在还原标准输出之后,无法成功调用 GetConsoleScreenBufferInfo 函数获取控制台屏幕缓冲区信息。我们来看看可能的原因:
可能原因一:文件句柄没有正确关闭
在调用 REDIR\_STDOUT("con")
之前,你需要确保先关闭之前打开的文件句柄。否则,系统可能无法正确释放资源,导致后续系统调用出错。你可以添加 fclose(stdout) 或 _fcloseall() (关闭所有打开的文件)来关闭文件句柄。
可能原因二:GetStdHandle 函数获取的句柄值不正确
另外一个原因是,你在重新打开控制台后,GetStdHandle 获取到的控制台输出句柄可能不再是先前的句柄。这可能是由于 freopen 调用期间系统在内部重新分配句柄。如果此情况发生,可以尝试使用 GetStdHandle(STD_ERROR_HANDLE) 或 GetStdHandle(STD_INPUT_HANDLE) 获取其他句柄来获取控制台信息。如果这些方法也失败了,可能需要关闭应用程序并再次启动。
总之,建议在页面顶部添加 #include <stdlib.h> 和 #include <assert.h> 头文件,这样可以在需要时使用 exit 函数增强程序鲁棒性,使用 assert 断言来检查系统调用是否成功执行。
看我个人中心,我发布了一篇这个解答
以下答案由GPT-3.5大模型与博主波罗歌共同编写:
根据你的描述,我猜测你的程序在重定向标准输出到文件后,无法获取标准输出的句柄,导致 GetStdHandle(STD_OUTPUT_HANDLE)
返回的句柄非法。因此,GetConsoleScreenBufferInfo
调用失败并返回 ERROR_ACCESS_DENIED
错误。
在 Windows 操作系统中,标准输出的句柄通常被初始化为一个指向控制台窗口的句柄。当你通过 freopen
函数将标准输出重定向到一个文件时,标准输出的句柄将被改变,指向新创建的文件句柄。因此,在你通过 REDIR_STDOUT
宏将标准输出重定向到文件后,GetStdHandle(STD_OUTPUT_HANDLE)
返回的句柄将不再指向控制台窗口,而是指向你所创建的文件句柄。
你可以尝试在重定向标准输出之前先保存原始的标准输出句柄,之后再将标准输出重新指向原始句柄,从而使你的程序能够正常获取控制台屏幕缓冲区信息。具体来说,可以按照以下步骤进行操作:
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
assert(freopen(filename, "w", stdout) != NULL);
执行你的输出操作,输出内容将被重定向到文件中。
将标准输出重新指向原始句柄:
assert(SetStdHandle(STD_OUTPUT_HANDLE, hStdout));
HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE);
现在,你应该可以正常获取控制台屏幕缓冲区信息了:
CONSOLE_SCREEN_BUFFER_INFO csbi;
assert(GetConsoleScreenBufferInfo(hout, &csbi));
完整代码示例如下:
#include <stdio.h>
#include <Windows.h>
/// <summary>重定向标准输出流到某个文件</summary>
/// <param name="filename">
/// 文件名。如果原来的文件存在,则覆盖它;否则新建一个文件。
/// <para>如果想要将标准输出重定向到控制台,请再次使用此宏,文件名填 "con",
/// 即 Windows 的控制台文件。</para>
/// </param>
#define REDIR_STDOUT(filename) assert(freopen(filename, "w", stdout) != NULL)
int main()
{
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); // 保存原始标准输出句柄
REDIR_STDOUT("test.txt"); // 将标准输出重定向到文件
printf("Hello world!\n"); // 输出到文件中
assert(SetStdHandle(STD_OUTPUT_HANDLE, hStdout)); // 将标准输出重新指向原始句柄
HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE); // 重新获取标准输出句柄
CONSOLE_SCREEN_BUFFER_INFO csbi;
assert(GetConsoleScreenBufferInfo(hout, &csbi)); // 获取控制台屏幕缓冲区信息
return 0;
}
希望上面的解答对你有所帮助。如果还有疑问,请随时追问!
如果我的回答解决了您的问题,请采纳!
参考GPT和自己的思路:
根据你的代码和描述,无法获取 CONSOLE_SCREEN_BUFFER_INFO 的原因是因为你在使用 freopen 将标准输出流重定向到文件后,并没有正确地将其重新定向回控制台。在代码中,你使用了 REDIR_STDOUT 宏将标准输出流重定向到 FILENAME,但在完成任务后,你使用了同样的宏将标准输出流再次重定向回“控制台”,但是你传递给该宏的参数是字符串 "con",这并不是正确的控制台文件。正确的控制台文件应该使用关键字 CON,而不是字符串 "con"。因此,你应该将代码中的第 7 行修改为:
7. /// 如果想要将标准输出重定向到控制台,请再次使用此宏,文件名填 CON,即 Windows 的控制台文件。
这样,你的代码应该就能够正确地将标准输出流重定向回控制台,然后你就能够正常地获取 CONSOLE_SCREEN_BUFFER_INFO 了。
参考gpt和自己的思路,在使用 freopen 函数将标准输出流重定向到文件时,标准输出流不再指向控制台,而是指向了该文件。因此,在 pri_dates 函数中将数据打印到标准输出流时,实际上是将数据写入到了该文件。然后,在将标准输出流重新定向回控制台时,控制台的屏幕缓冲区信息并未被更新。因此,即使在调用 GetConsoleScreenBufferInfo 函数时使用了正确的句柄,也无法获取正确的信息。
如果您需要在将标准输出流重定向到文件后同时输出到控制台并获取控制台的屏幕缓冲区信息,可以考虑使用 Windows API 中的 DuplicateHandle 函数创建一个新的输出句柄,并将标准输出流和该新句柄同时作为输出流。然后,在需要获取控制台屏幕缓冲区信息时,使用新句柄调用 GetConsoleScreenBufferInfo 函数即可。
以下是修改后的代码,使用了一个变量来保存控制台窗口的句柄,并在重定向前先保存了控制台屏幕缓冲区信息,以便在重定向回控制台后恢复。
#include <stdio.h>
#include <Windows.h>
// 保存控制台屏幕缓冲区信息的结构体
typedef struct {
COORD dwSize;
COORD dwCursorPosition;
WORD wAttributes;
SMALL_RECT srWindow;
COORD dwMaximumWindowSize;
} ConsoleInfo;
// 获取并保存控制台屏幕缓冲区信息
void GetConsoleInfo(ConsoleInfo *info) {
HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleScreenBufferInfo(hout, (CONSOLE_SCREEN_BUFFER_INFO *)info);
}
// 恢复控制台屏幕缓冲区信息
void SetConsoleInfo(const ConsoleInfo *info) {
HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleScreenBufferSize(hout, info->dwSize);
SetConsoleCursorPosition(hout, info->dwCursorPosition);
SetConsoleTextAttribute(hout, info->wAttributes);
SetConsoleWindowInfo(hout, TRUE, &(info->srWindow));
SetConsoleScreenBufferSize(hout, info->dwMaximumWindowSize);
}
///
/// 重定向标准输出流到某个文件
///
/// <param name="filename">
/// 文件名。如果原来的文件存在,则覆盖它;否则新建一个文件。
/// <para>如果想要将标准输出重定向到控制台,请再次使用此宏,文件名填 "con",
/// 即 Windows 的控制台文件。</para>
/// </param>
/// <param name="info">
/// 指向保存控制台屏幕缓冲区信息的结构体的指针。
/// </param>
#define REDIR_STDOUT(filename, info) do { \
GetConsoleInfo(info); \
assert(freopen(filename, "w", stdout) != NULL); \
} while (0)
// 保存控制台屏幕缓冲区信息的全局变量
ConsoleInfo g_ConsoleInfo;
int main() {
// 重定向标准输出流到文件
REDIR_STDOUT("test.txt", &g_ConsoleInfo);
// 在文件中输出内容
printf("hello, world\n");
// 重定向标准输出流回控制台
freopen("con", "w", stdout);
// 恢复控制台屏幕缓冲区信息
SetConsoleInfo(&g_ConsoleInfo);
// 在控制台中输出内容
printf("hello, world\n");
// 获取控制台屏幕缓冲区信息
HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO csbi;
assert(GetConsoleScreenBufferInfo(hout, &csbi));
return 0;
}
参考GPT和自己的思路,在你的代码中,标准输出流被重定向到了一个文件,然后再次被重定向回控制台。这会导致之后的所有输出都会被发送到控制台,而不是标准输出。如果你希望在重定向回来之后能够正确获取输出,你需要先保存原始的标准输出流,然后在需要时将其恢复。
可以使用以下代码来保存和恢复标准输出流:
// 保存标准输出流
FILE* original_stdout = freopen("CONOUT$", "w", stdout);
// 恢复标准输出流
freopen("NUL:", "w", stdout);
fclose(stdout);
stdout = original_stdout;
其中,CONOUT$ 是 Windows 的标准输出设备,而 NUL: 是一个特殊的设备,可以将所有写入它的数据都丢弃。因此,上面的代码可以将标准输出流重定向到 NUL:,然后再将其恢复到原始的输出设备。
在你的代码中,你可以像这样使用它:
case 4: // 保存链表的数据到文件。
{
// 保存原始的标准输出流
FILE* original_stdout = freopen("CONOUT$", "w", stdout);
// 将标准输出流重定向到文件
REDIR_STDOUT(FILENAME);
pri_dates(&l);
// 恢复标准输出流
freopen("NUL:", "w", stdout);
fclose(stdout);
stdout = original_stdout;
}
break;
这样做应该就能够正确获取输出了。同时,还要注意在恢复标准输出流之前,先关闭重定向的输出流,否则会出现文件句柄泄露的问题。