在不同的 Windows 版本上拆解 FreeConsole 的代码后,我找出了问题的原因。
FreeConsole 是一个非常简单的功能!我确实为您关闭了大量句柄,即使它不“拥有”这些句柄(例如,stdio 函数拥有的句柄)。
而且,Windows 7 和 8 中的行为有所不同,并在 10 中再次更改。
这是提出修复程序时的困境:
- 一旦 stdio 有一个到控制台设备的句柄,没有记录的方法可以让它放弃该句柄,而无需调用 CloseHandle。你可以调用
close(1)
orfreopen(stdout)
或者你喜欢的任何东西,但是如果有一个打开的文件描述符指向控制台,如果你想在 FreeConsole 之后将 stdout 切换到新的 NUL 句柄,则会调用 CloseHandle。
- 另一方面,由于 Windows 10 也无法避免 FreeConsole 调用 CloseHandle。
- Visual Studio 的调试器和应用程序验证程序将应用标记为在无效 HANDLE 上调用 CloseHandle。而且,他们是对的,这真的不好。
- 因此,如果您在调用 FreeConsole 之前尝试“修复”stdio,那么 FreeConsole 将执行无效的 CloseHandle(使用其缓存的句柄,并且无法告诉它句柄已消失 - FreeConsole 不再检查
GetStdHandle(STD_OUTPUT_HANDLE)
)。而且,如果您先调用 FreeConsole,则无法修复 stdio 对象而不导致它们对 CloseHandle 进行无效调用。
通过消除,我得出结论,唯一的解决方案是使用未记录的函数,如果公共函数不起作用。
// The undocumented bit!
extern "C" int __cdecl _free_osfhnd(int const fh);
static HANDLE closeFdButNotHandle(int fd) {
HANDLE h = (HANDLE)_get_osfhandle(fd);
_free_osfhnd(fd); // Prevent CloseHandle happening in close()
close(fd);
return h;
}
static bool valid(HANDLE h) {
SetLastError(0);
return GetFileType(h) != FILE_TYPE_UNKNOWN || GetLastError() == 0;
}
static void openNull(int fd, DWORD flags) {
int newFd;
// Yet another Microsoft bug! (I've reported four in this code...)
// They have confirmed a bug in dup2 in Visual Studio 2013, fixed
// in Visual Studio 2017. If dup2 is called with fd == newFd, the
// CRT lock is corrupted, hence the check here before calling dup2.
if (!_tsopen_s(&newFd, _T("NUL"), flags, _SH_DENYNO, 0) &&
fd != newFd)
dup2(newFd, fd);
if (fd != newFd) close(newFd);
}
void doFreeConsole() {
// stderr, stdin are similar - left to the reader. You probably
// also want to add code (as we have) to detect when the handle
// is FILE_TYPE_DISK/FILE_TYPE_PIPE and leave the stdio FILE
// alone if it's actually pointing to disk/pipe.
HANDLE stdoutHandle = closeFdButNotHandle(fileno(stdout));
FreeConsole(); // error checking left to the reader
// If FreeConsole *didn't* close the handle then do so now.
// Has a race condition, but all of this code does so hey.
if (valid(stdoutHandle)) CloseHandle(stdoutHandle);
openNull(stdoutRestore, _O_BINARY | _O_RDONLY);
}