2017-05-03 2 views
0

Tout d'abord, je ne suis pas un programmeur Windows (pas même un utilisateur Windows), j'utilise le compilateur croisé sous Linux pour construire aussi Win32 et Win64. Après avoir creusé le Net (et même posé une question ici), j'ai réussi à assembler un fragment de code qui peut ouvrir une console Windows, et l'utiliser pour stdin/stdout/stderr. Cela fonctionne bien avec Win32, mais le programme se bloque sur Win64. Je suppose que le problème est la taille du type de données entier long différent, gcc même avertit à ce sujet. Cependant, étant donné que je ne connais pas le but exact et la taille de certains types d'API Windows, je ne peux pas savoir ce que je devrais changer. Sûrement, le meilleur serait une solution indépendante win32/win64. J'ai aussi essayé d'utiliser le type "HANDLE" pour lStdHandle mais ensuite il ne compile pas. Quelqu'un peut-il aider à ce sujet?Ouverture de la console Windows pour stdin/stdout/stderr pour win32 et win64 en C

int hConHandle; 
    long lStdHandle; 
    //HANDLE lStdHandle; 
    CONSOLE_SCREEN_BUFFER_INFO coninfo; 
    FILE *fp; 
    FreeConsole(); // be sure to release possible already allocated console 
    if (!AllocConsole()) { 
      ERROR_WINDOW("Cannot allocate windows console!"); 
      return; 
    } 
    SetConsoleTitle("My Nice Console"); 
    // set the screen buffer to be big enough to let us scroll text 
    GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); 
    coninfo.dwSize.Y = 1024; 
    //coninfo.dwSize.X = 100; 
    SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize); 
    // redirect unbuffered STDOUT to the console 
    lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE); 
    hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); 
    fp = _fdopen(hConHandle, "w"); 
    *stdout = *fp; 
    setvbuf(stdout, NULL, _IONBF, 0); 
    // redirect unbuffered STDIN to the console 
    lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE); 
    hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); 
    fp = _fdopen(hConHandle, "r"); 
    *stdin = *fp; 
    setvbuf(stdin, NULL, _IONBF, 0); 
    // redirect unbuffered STDERR to the console 
    lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE); 
    hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); 
    fp = _fdopen(hConHandle, "w"); 
    *stderr = *fp; 
    setvbuf(stderr, NULL, _IONBF, 0); 
    // Set Con Attributes 
    //SetConsoleTextAttribute(GetStdHandle(STD_ERROR_HANDLE), FOREGROUND_RED | FOREGROUND_INTENSITY); 
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN | FOREGROUND_INTENSITY); 
    SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); 
    SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT); 
+0

Avez-vous débogué? Où se bloque-t-il? – Peanut

+0

Il fonctionne pour ma compilation dans Visual Studio 2017. Vous devez également vérifier les valeurs de retour à partir des fonctions. – Peanut

+0

En fait, je ne peux pas déboguer, puisque je ne peux même pas essayer, je n'ai pas de fenêtres, juste demandé à quelqu'un de l'essayer. Il a été compilé sous Linux avec les fenêtres de ciblage du compilateur Mingw. L'exe 32 bits semble être OK, seulement pour 64 bits est le problème. Certes, ce n'est pas si bien que j'ai un problème, je ne peux pas trop déboguer, mais la plupart de mon projet est indépendant de la plate-forme de toute façon (win32/win64/OSX/Linux/etc), juste de petites choses comme ça. –

Répondre

1

Il s'agit d'un handle, vous devez donc utiliser le type HANDLE. Cast à INT_PTR (ou SIZE_T si votre SDK est vraiment obsolète) lorsque vous appelez _open_osfhandle, en utilisant long pouvez tronquer la valeur!

+0

Généralement c'est vrai. Un 'HANDLE' pourrait être un pointeur (par exemple,' HMODULE' est l'adresse de base du module) ou une valeur spéciale comme 'INVALID_HANDLE_VALUE' ou' (HANDLE) -1' retournée par 'GetCurrentProcess'. Dans ces cas, la troncature serait mauvaise. Mais les poignées de noyau et les poignées de fenêtre régulières sont garanties pour tenir dans un «long» de 32 bits. Aussi, pour passer un handle passé à '_open_osfhandle', il est plus idiomatique d'utiliser' intptr_t' au lieu d'un typedef Windows. – eryksun

+0

@eryksun Un handle de console n'est pas toujours un handle de noyau normal, c'est un pseudo handle dans certains cas et vous ne pouvez pas supposer quoi que ce soit sur les bits. Les HWND tiennent en 32 bits parce que les applications 32 bits doivent pouvoir fonctionner sur les HWND dans les applications 64 bits, mais il n'en va pas de même pour les HANDLE. – Anders

+0

Les poignées de console dans les versions antérieures à Windows 8 sont en effet des pseudo-handles gérés par la console elle-même, mais en pratique nous savons que ce n'est pas le problème dans ce cas, indépendamment de ce que nous pouvons légitimement supposer. conhost.exe dans Windows 7, ou csrss.exe dans les versions vraiment anciennes) commence avec le jeu de base 3, 7, 11 et incrémente à partir de là lors de la duplication des poignées et la création de poignées pour les nouveaux tampons d'écran. – eryksun

1

Je ne sais pas exactement quel est le problème. Je ne suis pas configuré pour construire avec MinGW sous Linux. Cela peut être lié à un descripteur de fichier non valide si vous ne construisez pas une application de console. Dans ce cas, le CRT initialise le descripteur de fichier pour gérer le mappage en tant que valeur de handle non valide, et les flux FILE standard seront également initialisés à -1 fileno. Mais je ne vois pas de problème avec cela dans votre code.

Cependant, votre hack *stdin = *fp n'est pas portable C. Il fonctionne dans les anciennes versions de MSVC, et probablement aussi avec msvcrt.dll (un peu douteusement utilisé par MinGW faute d'un meilleur choix). Cependant, cela ne fonctionne pas avec le nouveau CRT universel. Un FILE dans le nouveau CRT est défini comme suit:

typedef struct _iobuf 
{ 
    void* _Placeholder; 
} FILE; 

assignant Donc *stdin juste ce pointeur écrase _Placeholder. La structure interne est en fait comme suit:

struct __crt_stdio_stream_data 
{ 
    union 
    { 
     FILE _public_file; 
     char* _ptr; 
    }; 

    char*   _base; 
    int    _cnt; 
    long    _flags; 
    long    _file; 
    int    _charbuf; 
    int    _bufsiz; 
    char*   _tmpfname; 
    CRITICAL_SECTION _lock; 
}; 

Donc tout ce que vous écrasez vraiment est son tampon _ptr.

La manière de rouvrir de façon portative un flux standard est via freopen. Donc, ce que je fais, qui fonctionne mais peut-être quelqu'un d'autre a une meilleure solution, est freopen le périphérique NUL, qui réinitialise le flux à un descripteur de fichier valide s'il s'agit d'une application non-console. Ensuite, utilisez _dup2 pour rediriger le descripteur de fichier sous-jacent. Par exemple:

#include <io.h> 
#include <stdio.h> 
#include <fcntl.h> 
#include <Windows.h> 

int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
      LPWSTR lpCmdLine, int nCmdShow) 
//int wmain(int argc, wchar_t **argv) 
{ 
    int fdStd; 
    HANDLE hStd; 
    CONSOLE_SCREEN_BUFFER_INFO coninfo; 

    printf("Goodbye, World!\n"); 

    /* ensure references to current console are flushed and closed 
    * before freeing the console. To get things set up in case we're 
    * not a console application, first re-open the std streams to 
    * NUL with no buffering, and close invalid file descriptors 
    * 0, 1, and 2. The std streams will be redirected to the console 
    * once it's created. */ 

    if (_get_osfhandle(0) < 0) 
     _close(0); 
    freopen("//./NUL", "r", stdin); 
    setvbuf(stdin, NULL, _IONBF, 0); 
    if (_get_osfhandle(1) < 0) 
     _close(1); 
    freopen("//./NUL", "w", stdout); 
    setvbuf(stdout, NULL, _IONBF, 0); 
    if (_get_osfhandle(2) < 0) 
     _close(2); 
    freopen("//./NUL", "w", stderr); 
    setvbuf(stderr, NULL, _IONBF, 0); 

    FreeConsole(); 

    if (!AllocConsole()) { 
     //ERROR_WINDOW("Cannot allocate windows console!"); 
     return 1; 
    } 
    SetConsoleTitle("My Nice Console"); 

    // set the screen buffer to be big enough to let us scroll text 
    GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); 
    coninfo.dwSize.Y = 1024; 
    //coninfo.dwSize.X = 100; 
    SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize); 

    // redirect unbuffered STDIN to the console 
    hStd = GetStdHandle(STD_INPUT_HANDLE); 
    fdStd = _open_osfhandle((intptr_t)hStd, _O_TEXT); 
    _dup2(fdStd, fileno(stdin)); 
    SetStdHandle(STD_INPUT_HANDLE, (HANDLE)_get_osfhandle(fileno(stdin))); 
    _close(fdStd); 

    // redirect unbuffered STDOUT to the console 
    hStd = GetStdHandle(STD_OUTPUT_HANDLE); 
    fdStd = _open_osfhandle((intptr_t)hStd, _O_TEXT); 
    _dup2(fdStd, fileno(stdout)); 
    SetStdHandle(STD_OUTPUT_HANDLE, (HANDLE)_get_osfhandle(fileno(stdout))); 
    _close(fdStd); 

    // redirect unbuffered STDERR to the console 
    hStd = GetStdHandle(STD_ERROR_HANDLE); 
    fdStd = _open_osfhandle((intptr_t)hStd, _O_TEXT); 
    _dup2(fdStd, fileno(stderr)); 
    SetStdHandle(STD_ERROR_HANDLE, (HANDLE)_get_osfhandle(fileno(stderr))); 
    _close(fdStd); 

    // Set Con Attributes 
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 
     FOREGROUND_GREEN | FOREGROUND_INTENSITY); 
    SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), 
     ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); 
    SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 
     ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT); 

    printf("Hello, World!\n"); 

    Sleep(10000); 
    return 0; 
} 
+0

Merci beaucoup! Il semble que le problème soit résolu avec l'utilisation du type HANDLE et la conversion en INT_PTR. Cela a aidé à mon problème avec ma solution existante. Cependant s'il vous plaît ne me méprenez pas, peut-être votre solution serait encore mieux que mon proposé (en fait pas mon travail, mais ce que je pouvais deviner et mettre sur le Net ...), mais je suis actuellement heureux qu'au moins certaines lignes modifiées seulement cela fonctionne bien :) –

+0

@ LGBGáborLénárt, votre code tout simplement ne fonctionnera pas lors de la construction avec VS 2015+ pour le nouveau CRT, et vous devez le réparer pour être portable plutôt que de compter sur les détails d'implémentation interne de 'FILE' pointeurs, qui sont censés être opaques en C. Même si vous n'accédez pas directement aux champs, en supposant que vous pouvez faire '* stdin = * fp', tout est aussi faux. – eryksun

+0

En ce qui concerne le problème 'HANDLE', la solution n'a pas de sens pour moi. Toutes les poignées dans ce cas correspondent à un «long». Je ne l'écrirais pas comme vous l'avez fait en premier lieu, mais je pense que cela devrait fonctionner. Je vais essayer avec MSVC et essayer de comprendre où ça pourrait aller mal. Si c'était mon propre problème, je ne serais pas satisfait jusqu'à ce que je puisse diagnostiquer exactement où il se passe mal dans un débogueur. – eryksun