さまざまな Windows バージョンで FreeConsole のコードを逆アセンブルした後、問題の原因を突き止めました。
FreeConsole は非常に微妙な機能です! それらのハンドルを「所有」していなくても(たとえば、stdio関数が所有するHANDLE)、私は実際にハンドルの負荷を閉じます。
また、Windows 7 と 8 で動作が異なり、10 で再び変更されました。
修正を考え出すときのジレンマは次のとおりです。
- stdio がコンソール デバイスへの HANDLE を取得すると、CloseHandle を呼び出さずにそのハンドルを放棄する方法が文書化されていません。
close(1)
orまたは任意のものを呼び出すことができますfreopen(stdout)
が、コンソールを参照する開いているファイル記述子がある場合、FreeConsole の後で stdout を新しい NUL ハンドルに切り替えたい場合は、CloseHandle が呼び出されます。
- 一方、Windows 10 以降、FreeConsole が CloseHandle を呼び出すことを回避する方法もありません。
- Visual Studio のデバッガーと Application Verifier は、無効なハンドルで 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);
}