6

Windows 8では、FreeConsoleに問題があります。ファイルストリームを閉じずに、stdioハンドルを閉じているようです。

これはWindows8の問題である可能性があります。または、Windowsコンソール/ GUIアプリサブシステムが処理する方法を(まったくばかげている)理解していない可能性があります。

どうしたの?

以下の最小限の例。コンパイラでテスト済み:静的にリンクされたCRTを使用して、VS2005、VS2013、VS2017。

#include <windows.h>
#include <io.h>
#include <stdio.h>

static void testHandle(FILE* file) {
  HANDLE h = (HANDLE)_get_osfhandle(fileno(file));
  DWORD flags;
  if (!GetHandleInformation(h, &flags)) {
    MessageBoxA(0, "Bogus handle!!", "TITLE", MB_OK);
  }
}

int main(int argc, char** argv)
{
  freopen("NUL", "wb", stdout); // Demonstrate the issue with NUL
  // Leave stderr as it is, to demonstrate the issue with handles
  // to the console device.

  FreeConsole();

  testHandle(stdout);
  testHandle(stderr);
}
4

2 に答える 2

5

以前の Windows 8 標準の (リダイレクトされていない) コンソール ハンドル (GetStdHandle によって返される) が、実際には疑似ハンドルであり、値が他のカーネル オブジェクト ハンドルと交差しないため、FreeConsole によって「閉じられた」後にその疑似ハンドルへの書き込みが常に失敗するという事実によって引き起こされる問題. Win8 では、MS が内部で何かを変更したため、GetStdHandle は、コンソール サブシステム ドライバー オブジェクトを参照する通常のカーネル オブジェクト ハンドルを返します (実際には、そのドライバーも Win8 でのみ登場しました)。したがって、FreeConsole はそのハンドルを閉じます。CRT が起動時に GetStdHandle を実行し、返された値を内部のどこかに保存し、std::in/out/err にアクセスする呼び出された C 関数を使用するという最も面白いこと。FreeConsole がそのハンドルを閉じたため、特別な疑似ハンドル値ではなくなったので、同じハンドル値を他の開いているカーネル オブジェクト ハンドルで再利用できます。

于 2013-05-30T12:57:04.927 に答える
1

さまざまな 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);
}
于 2017-12-15T13:47:51.607 に答える