Icon Ressource

[C/C++/WinAPI] ANSI Escapesequenzen in der Windows 10 Console 1.2

TL;DR: Wer sich sicher ist, dass sein Programm entweder in der neuen Console V2 (seit Windows 10) oder im Windows Terminal (ist in der Windows 11 Installation dabei oder kann für Windows 10 nachinstalliert werden) ausgeführt wird, kann die Komfortfunktion
void EstablishUTF8Terminal(void);
nutzen um Ein- und Ausgaben für den Vitual Terminal Modus und UTF-8 Strings zu konditionieren. Dabei wie folgt vorgehen:
  • win10vt.c und win10vt.h in das Projekt einbinden
  • sicherstellen, dass der eigene Quellcode UTF-8 codiert und ohne Byte Order Mark gespeichert wird
  • win10vt.h includieren
  • Funktion EstablishUTF8Terminal(); zu Beginn des Programms aufrufen
  • die I/O Funktionen für char-basierte Strings verwenden, um mit UTF-8 Ein- und Ausgaben zu arbeiten.
test.c:
#include "win10vt.h"
#include <stdio.h>

int main(void) {
  EstablishUTF8Terminal();
  puts(ESC([42m) "Hello World!" ESC([0m) "\n"
       "$ £ Ω É س Я ה ह € 한 𐍈 𤭢 \n"
       "😀 👍 🙋 👀 🦋 🌹 🍀 🍞 🍺 🏠 🚗 ✈ 🌦 🌈 🎈 🎁 ⚽ 🎸 ");
}




Ansonsten ...

Warum?

Die neue Windows Console unterstützt beginnend mit Windows 10.0.10586 ANSI Escapesequenzen im Virtual Terminal Modus, beispielsweise um Text farbig auszugeben. Siehe:
Consoleanwendungen müssen die Console aber zunächst konditionieren um diese Escapesequenzen auch nutzen zu können.
Ziele dieser kleinen Lib sind:
  • Auskapseln der erforderlichen Windows API aus dem Benutzercode.
  • Konditionierung der Windows Console für den VT Modus, bzw. dessen Deaktivierung.
  • Identifikation der Standardstreams die im VT Modus verwendbar sind, um verhindern zu können, Escapesequenzen auszugeben, die nicht gerendert werden und stattdessen in ihrer Textrepräsentation verarbeitet werden.



Wie?

Zwei Dinge sind zu berücksichtigen, die völlig unabhängig voneinander existieren.
  1. Das Consolefenster besitzt einen Eingabe- und einen Ausgabepuffer. Für beide kann der VT Modus im Hostprozess konfiguriert werden, sofern die Anwendung mit einem Consolefenster verknüpft ist und es sich um das neue Windows 10 V2 Consolefenster handelt (conhost.exe ohne angegliederten cscss.exe Runtime Prozess). Ob diese Konfiguration möglich oder nicht möglich ist, gibt letztlich darüber Aufschluss und ist eines der Entscheidungskriterien ob mit Escapesequenzen gearbeitet werden kann. Die SetVTConMode Funktion ist zu diesem Zweck implementiert.
  2. Wenn das Consolefenster konfiguriert werden konnte, bedeutet das noch nicht, dass die Standardeingabe, Standardausgabe und Standardfehlerausgabe auch mit diesem verknüpft sind. Die entsprechenden Handles können durchaus auf eine Datei oder eine Pipe umgeleitet sein, wo Escapesequenzen nicht gerendert werden können. Der Rückgabewert der GetStdReferCon Funktion beinhaltet Informationen, ob das StdIn auf ConIn, und ob StdOut und StdErr auf ConOut verweisen.
Für Parameter und Rückgabewerte dieser Funktionen sind Flags in Form von Macros definiert, mit denen mit Hilfe der üblichen bitweisen Operationen zu arbeiten ist.

Darüber hinaus ist die TargetIsWindowsConsole Funktion implementiert, die Aussage darüber gibt, ob an den derzeitigen Prozess überhaupt eine Windows Console angehängt ist. Bei anderen Virtual Terminal Anwendungen ist oft davon auszugehen, dass sie VT Escapesequenzen von vorn herein unterstützen.



Der folgende Testcode zeigt beispielhaft, wie mit der Lib zu arbeiten ist ("win10vt.h" und "win10vt.c" sind in das Projekt eingebunden und finden sich im Download.):
enable_ansi_esc.c:
#include "win10vt.h"
#include <stdio.h>

int main(void) {
  // Figure out which of the standard handles refer to the console window (rather than to a file or a pipe).
  unsigned con_connected = GetStdReferCon();
  // Try to enable VT processing for the handles of the attached console window, and retrieve for which it happened to work.
  unsigned vt_enabled = SetVTConMode(VT_CONALL, VT_CONALL);

  if ((vt_enabled & VT_CONOUT) && (con_connected & VT_STDOUT)) // VT sequences can be used for stdout.
  {
    fputs(ESC([42m) "Hello Narrow World!\n" ESC([0m), stdout);
    fputws(ESCW([42m) L"Hello Wide World!\n" ESCW([0m), stdout);
  } else // VT sequences not supported for stdout.
  {
    fputs("Hello Narrow World!\n", stdout);
    fputws(L"Hello Wide World!\n", stdout);
  }

  if ((vt_enabled & VT_CONOUT) && (con_connected & VT_STDERR)) // VT sequences can be used for stderr.
    fputs(ESC([41m) "Hello Wrong World!\n" ESC([0m), stderr);
  else // VT sequences not supported for stderr.
    fputs("Hello Wrong World!\n", stderr);

  // Disable VT processing for the handles of the attached console window to be sure subsequent tests are not influenced.
  SetVTConMode(VT_CONALL, VT_CONNONE);

  return 0;
}
Statt den C Ausgabefunktionen, könnten hier auch die C++ Ausgabestreams std::cout, std::wcout und std::cerr oder die entsprechenden Windows API Funktionen wie WriteFile zum Einsatz kommen, um die Effekte zu testen. Das Resultat bliebe gleich.


Dieses Batch Script ruft die kompilierte "enable_ansi_esc.exe" unter unterschiedlichen Konditionen auf. Hier soll gezeigt werden, dass der obige C Code tatsächlich in der Lage war zu erkennen, wo VT Processing möglich ist und wo nicht:
test.bat:
@echo off &setlocal

echo(
echo(*** directly
enable_ansi_esc

echo(
echo(*** pipe (err merged)
enable_ansi_esc 2>&1 | find /v ""

echo(
echo(*** pipe
enable_ansi_esc | find /v ""

echo(
echo(*** for (err merged)
for /f "delims=" %%i in ('2^>^&1 enable_ansi_esc') do echo %%i

echo(
echo(*** for
for /f "delims=" %%i in ('enable_ansi_esc') do echo %%i

echo(
echo(*** redirect to con (err merged)
>con 2>&1 enable_ansi_esc

echo(
echo(*** redirect to con
>con enable_ansi_esc

echo(
pause

Ausgabe:
1594481673684.png


Die Ausgabe ist wie folgt zu interpretieren:
  • Wo VT Processing möglich war, erfolgt die Ausgabe farbig (grün bzw. rot). Standardfarben weiß auf schwarz dort, wo VT Prozessing nicht möglich ist.
  • Bei einer fehlerhaften Entscheidung im Programmcode würde bei den weiß auf schwarz Ausgaben der Escapecode als Text ausgegeben werden, was aber nicht der Fall ist.



Nicht mit dem eigentlichen Virtual Terminal Modus in Verbindung stehend, aber den Erwartungen an ein Terminal folgend, ist die Fähigkeit eines Terminals einigermaßen Unicode-kompatibel rendern zu können. Hier kommen weitere Abhängigkeiten hinzu. (Und hier sind wir auch bei einem weiteren Grund, warum das bei *nix Plattformen eher reibungslos funktioniert: UTF-8 ist quasi immer und überall Standard.)
Während Windows historisch, von UCS-2 kommend, auf UTF-16 als Zeichncodierung setzt und man mit den ...W() Windows API Funktionen auch recht gut damit arbeiten kann, ist die C Runtime darauf nur bedingt ausgerichtet. Wer also wchar_t basierte Strings mit den C Funktionen ein- und ausgeben will, bekommt Schwierigkeiten mit Zeichen die nicht im Zeichensatz der derzeitigen Console Codepage enthalten sind. Die Standard-Streams (stdin, stdout und stderr) müssen entsprechend konditioniert werden.
Aber auch das ist nicht mehr state-of-the-art. Microsoft macht kleine Schritte hin zu UTF-8 als Standard für Text-Interfaces. Der Standardzeichensatz der Console ist allerdings noch nicht auf UTF-8 ausgerichtet, was sich aber programmatisch einstellen lässt.
Für Unicode Unterstützung stehen die Funktionen
unsigned SetInputEncoding(unsigned id); und
unsigned SetOutputEncoding(unsigned id);
zusammen mit den Macros
VT_UTF8 und VT_UTF16
zur Verfügung.

Unicode Troubleshooting:
  • Wer UTF-8 nutzt (und ich plädiere dafür, das zu tun) sollte darauf achten, es durchgängig zu verwenden. Dazu gehört auch, dass der Programmquellcode UTF-8 codiert und ohne Byte Order Mark gespeichert wird. Nur so werden nicht-ASCII Zeichen in einem Stringliteral korrekt interpretiert. (Anderenfalls müsste jedes einzelne Byte eines Multibyte-Zeichens durch einen Escapecode dargestellt werden.) Da kann ich programmatisch allerdings nicht unterstützen :) Das muss jeder selbst in seinem Editor / seiner IDE erledigen.
  • Sollten Zeichen nicht korrekt gerendert werden, kann das am Font liegen, der solche Zeichen nicht unterstützt. Mit "Consolas" funktioniert das in der Regel recht gut. Aber im Zweifel einfach mal einen anderen Font einstellen.
  • Die Windows Console kann im Moment (Stand September 2022) noch nicht alle Zeichen rendern, die der Font unterstützt. Wer also bspw. Unterstützung für die Darstellung neuerer Smileys haben möchte, kann auf das Windows Terminal zurückgreifen. Das lässt sich auf Windows 10 als App nachinstallieren. Windows 11 wird bereits damit ausgeliefert, wo es auch als Standard Terminalanwendung konfiguriert werden kann damit Consoleanwendungen im Terminal statt in der Console ausgeführt werden.



Zuletzt noch die zu Beginn bereits genannte Komfortfunktion
void EstablishUTF8Terminal(void);
Diese versucht sowohl den VT Modus, als auch die UTF-8 Zeichenkodierung einzustellen. Nutzer dieser Funktion können aber keinen Rückschluss ziehen, ob dies erfolgreich war.


Detaillierte Beschreibungen finden sich im win10vt.h Header im Download.

  • Like
Reaktionen: Mat
Autor
german
Downloads
1.035
Aufrufe
2.703
Erstellt am
Letzte Bearbeitung
Bewertung
0,00 Stern(e) 0 Bewertung(en)

Weitere Ressourcen von german

Letzte Aktualisierungen

  1. Unicode Einstellungen hinzu.

    - 2 neue Funktionen mit denen die Ein- und Ausgabe für UTF-8 oder UTF-16 konditioniert werden...
  2. 'TargetIsWindowsConsole' Funktion hinzu

    Die Lib dient dazu, den Windows Console Host zu konditionieren. Darüber hinaus gibt es aber eine...
Oben Unten