Donnerstag, 9. April 2015

Konsole für Win32 GUI Anwendungen

Anwendungen mit Fenstern sind ja echt schön anzuschauen und bei Spielen auch wohl der sinnvollste Einstiegspunkt, aber oft kommt man doch recht schnell an den Punkt, an dem man sich denkt: Jetzt wäre so eine Konsole echt nicht schlecht, um den Inhalt von Variable AB anzuzeigen oder mal schnell Fehlermeldung XY auszugeben (rein zu Debugzwecken versteht sich).
Leider muss man aber schon bei Projekterstellung festlegen, ob es sich um eine Konsolen- oder um eine GUI-Anwendung handelt.

Mein erster Versuch, ein bestehendes Vektoria-Projekt in ein Konsolen-Projekt umzuwandeln, indem ich einfach das SubSystem im Linker auf Console setze und den Einstiegspunkt manuell angebe, sah erstmal erstaunlich vielversprechend aus. Allerdings nur bis der erste Speicher von einer externen Bibliothek allokiert wird: Das beendete das kleine Experiment sehr deutlich mit einem Assert wegen Heapproblemen. Vermutlich kann man das ganze trotzdem irgendwie noch so hinbiegen, dass es funktioniert, aber das Ziel sollte ja eigentlich sein, möglichst einfach noch eine Konsole zu öffnen, auf der die Standardausgabe schreibt.

Nach einigem suchen bin ich dann auf einen, zugegebenermaßen schon etwas älteren, Artikel aus dem Windows Developer Journal gestoßen. Dieser enthält, neben einer Beschreibung des Vorgehens zum Öffnen einer Konsole und anschließendem Umleiten der Standard Ein- und Ausgaben auf diese, auch ein vollständiges Codebeispiel.

Hier ist der gekürzte Kerncode, um den Grundablauf (anhand von stdout) zu zeigen:

#include <windows.h>
#include <fcntl.h>
#include <io.h>
#include <fstream>

using namespace std;

void RedirectIOToConsole() {

    int hConsoleHandle;
    long lStdHandle;
    CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
    FILE* pFile;

    // allocate a console for this app
    AllocConsole();

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

    // redirect unbuffered STDOUT to the console
    lStdHandle = (long) GetStdHandle(STD_OUTPUT_HANDLE);
    hConsoleHandle = _open_osfhandle(lStdHandle, _O_TEXT);
    pFile = _fdopen(hConsoleHandle, "w");
    *stdout = *pFile;
    setvbuf(stdout, NULL, _IONBF, 0);
}

Zuerst wird eine neue Konsole mit AllocConsole erzeugt, wobei zu beachten ist, dass pro Prozess immer nur eine Konsole auf diese Weise bereitgestellt werden kann. Aber mehr als das wollen wir ja auch nicht.
Als nächstes wird der Buffer der Konsole vergrößert, sodass mehr Text angezeigt werden kann. Hierbei entspricht X der Zeichen pro Zeile und Y der Anzahl der Zeilen. Natürlich kann die Konsole noch anderweitig angepasst werden, z.B. die Zeichenfarbe, aber das ist jedem selbst überlassen.
Zum Schluss kommt dann der eigentlich interessante Teil: Hier wird zuerst das Output Handle für die Konsole geholt, mit einem C File Descriptor verknüpft und anschließend dieser wiederum mit einem Stream. Weist man diesen nun dem Standard Output Stream zu, wird jede Ausgabe, z.B. über cout, an die Konsole weitergeleitet und dort angezeigt. Setvbuf setzt nur noch fest, dass stdout unbuffered sein soll, damit alles sofort geschrieben wird.

Natürlich kann man auf diese Weise auch andere Streams wie z.B. stderr oder auch stdin umleiten. Es muss nur der ensprechende Stream und das zugehörige Handle angegeben werden (siehe Artikel).

Vermutlich gibt es noch verschiedene andere Möglichkeiten um eine einfache Ausgabenumleitung zu erreichen (vielleicht auch mit weniger C Anteilen), aber dies ist auf jeden Fall eine nicht allzu umfangreiche Variante ohne dass man auf externe Bibliotheken zugreifen muss.

Quelle: Windows Developer Journal, December 1997 (Artikel auf Andrew Tucker's Home Page)

Keine Kommentare:

Kommentar veröffentlichen