visual-studio-2005 - studio - visual cc 2005
Comportamiento de FreeConsole en Windows 8 (2)
En Windows 8, tenemos un problema con FreeConsole. Parece cerrar los identificadores stdio, sin cerrar las secuencias de archivos.
Esto puede ser un problema de Windows 8, o podría ser que simplemente no entiendo la manera (totalmente absurda) en que el subsistema de la consola de Windows / GUI hace las cosas.
¿Que esta pasando?
Ejemplo mínimo a continuación. Probado con compiladores: VS2005, VS2013, VS2017, utilizando CRT estáticamente enlazado.
#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);
}
Problema causado por el hecho de que los controladores de consola anteriores de Windows 8 (no redirigidos) (que devolvía GetStdHandle) eran realmente pseudohandles, cuyos valores no se intersectan con otros identificadores de objetos del kernel, por lo que escribir en ese pseudohandle después de ser ''cerrado'' por FreeConsole siempre falla . En Win8, MS cambió algo adentro, por lo que GetStdHandle devuelve un identificador de objeto kernel normal que hace referencia al objeto del controlador del subsistema de consola (en realidad, ese controlador también apareció solo en Win8). Entonces, FreeConsole cierra ese identificador. Lo más curioso es que CRT hace GetStdHandle al arrancar y guarda el valor devuelto en algún lugar dentro y usa donde sea que use funciones llamadas C que acceden a std :: in / out / err. Dado que FreeConsole cerró ese identificador, y ya no es un valor pseudohandle especial, el mismo valor de identificador puede ser reutilizado por cualquier otro manejador de objeto kernel abierto, y tendrás suerte si no se trata de archivo, canalización o socket porque en este caso todos su salida de depuración irá allí :)
Después de desmontar el código de FreeConsole en diferentes versiones de Windows, resolví la causa del problema.
¡FreeConsole es una función notablemente poco sutil! Realmente cierro una gran cantidad de identificadores para usted, incluso si no es "propietario" de esos identificadores (por ejemplo, HANDLEs propiedad de las funciones de stdio).
Y, el comportamiento es diferente en Windows 7 y 8, y cambió de nuevo en 10.
Este es el dilema al encontrar una solución:
- Una vez que stdio tiene una HANDLE en el dispositivo de la consola, no hay una forma documentada para que renuncie a ese identificador, sin una llamada a CloseHandle. Puede llamar
close(1)
ofreopen(stdout)
o lo que quiera, pero si hay un descriptor de archivo abierto que hace referencia a la consola, se llamará a CloseHandle si desea cambiar a stdout a un nuevo manejador NUL después de FreeConsole. - Por otro lado, desde Windows 10 tampoco hay forma de evitar que FreeConsole llame a CloseHandle.
- El depurador de Visual Studio y el Verificador de aplicación marcan la aplicación para llamar a CloseHandle en un MANGO no válido. Y, tienen razón, en realidad no es bueno.
- Entonces, si intentas "arreglar" stdio antes de llamar a FreeConsole, FreeConsole hará un CloseHandle no válido (usando su identificador almacenado en caché, y no hay manera de decirle que ese identificador se ha ido - FreeConsole ya no verifica
GetStdHandle(STD_OUTPUT_HANDLE)
) . Y, si primero llama a FreeConsole, no hay forma de arreglar los objetos stdio sin hacer que hagan una llamada no válida a CloseHandle.
Por eliminación, concluyo que la única solución es usar una función no documentada, si las públicas simplemente no funcionan.
// 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);
}