WindowsCharactersAndStrings

From HerzbubeWiki
Jump to navigation Jump to search

This page contains German notes about the character and string handling on Windows.

TODO: Translate and beautify this page.


Referencen


Summary

  • Es gibt 3 unterschiedliche Character Types
    • SBCS = Single-Byte Character Set
    • MBCS = Multibyte Character Set
    • Unicode
  • Das Windows API unterscheidet dagegen nur 2 Varianten: ANSI vs. Unicode
    • Viele Funktionen werden mittels Preprocessor entweder auf eine "W" oder eine "A" Variante umdefiniert, z.B.
SetDlgItemTextW
SetDlgItemTextA$
    • Die "W" Variante verarbeitet Unicode (W=Wide)
    • Die "A" Variante verarbeitet SBCS und MBCS (A=ANSI)
    • Als Entwickler verwendet man den Funktionsnamen ohne Suffix, dieser wird dann basierend auf dem Preprocessor Makro UNICODE umdefiniert
    • Das Makro wird automatisch definiert, wenn man in den VC++ Projektsettings Unicode w‰hlt
  • TCHAR
    • Dieser Datentyp (typedef) verwendet den Character Type, der in den Projektsettings gew‰hlt wurde
    • Hier geht es im wesentlichen um die Unterscheidung zwischen Unicode/Non-Unicode
  • _T()
    • Dieses Preprocessor Makro definiert String Literals von dem Character Type, der in den Projektsettings gew‰hlt wurde
    • Hier geht es im wesentlichen um die Unterscheidung zwischen Unicode/Non-Unicode
  • _tcsXXX()
    • Diese Preprocessor Makros werden zu den Funktionen aufgelˆst, die zu den Projektsettings passen
    • Hier wird zwischen Unicode/MBCS/SBCS unterschieden; Beispiel:
      • Unicode = wcsrchr()
      • MBCS = _mbsrchr()
      • SBCS = strrchr()
  • Preprocessor Makros
    • UNICODE = Applikation verwendet Unicode API; TCHAR verwendet Wide Characters
    • _MBCS = Applikation verwendet ANSI API; TCHAR verwendet normale Characters; Multibyte Support ist eingeschaltet
    • Weder UNICODE noch _MBCS = Applikation verwendet ANSI API; TCHAR verwendet normale Characters; Multibyte Support ist ausgeschaltet


Multibyte character sets (MBCS)

  • http://msdn.microsoft.com/en-us/library/5z097dxa.aspx (Support for Multibyte Character Sets (MBCSs))
  • http://msdn.microsoft.com/en-us/library/4bb3e64h.aspx (Single-Byte and Multibyte Character Sets)
  • http://msdn.microsoft.com/en-us/library/zz3x65c7.aspx (MBCS Programming Tips)
  • Streng genommen wird der Begriff "Character Set" von Microsoft in diesem Kontext falsch verwendet
    • Die Definition eines Character Sets sagt nichts dar¸ber aus, wieviele Bytes f¸r dessen Abbildung benˆtigt werden
    • Die Anzahl Bytes wird erst durch das Encoding bestimmt, das f¸r die Abbildung des Character Sets verwendet wird
    • Aus diesem Grund verwende ich ab jetzt den Begriff "MBCS Encoding"
  • MBCS Encodings werden verwendet, um Zeichens‰tze abzubilden, die nicht Platz innerhalb eines einzelnen Bytes (8 Bit) haben
  • Zeichen in einem MBCS kˆnnen 1 oder 2 Byte lang sein, MBCS Strings bestehen also aus Zeichen unterschiedlicher L‰nge
  • Besteht ein Zeichen aus 2 Bytes, so hat das erste Byte (das "Lead Byte") einen speziellen Wert, um anzuzeigen, dass noch ein zweites Zeichen folgt
    • Die verwendete Codepage bestimmt, welche Werte aus einem Byte ein Lead Byte machen
    • Unterschiedliche Codepages kˆnnen unterschiedliche Wertebereiche definieren
  • DBCS
    • DBCS = Double-Byte Character Set
    • DBCS ist in der Theori nur ein mˆglicher Typ von MBCS
    • In der Praxis wird DBCS aber offenbar h‰ufig als Synonym zu MBCS verwendet, da es unter Windows kein MBCS gibt, das Characters >2 Bytes hat
  • Welches Encoding effektiv verwendet wird, xxx
  • Um eine Applikation mit MBCS Support zu builden, muss das Preprocessor Makro _MBCS definiert sein
  • Die Single-Byte Funktionen strcpy(), sprintf(), etc. haben entsprechende MBCS Pendants: _mbscpy() etc.


Unicode

  • Funktionen: http://msdn.microsoft.com/en-us/library/cc500321.aspx
  • Unicode im allgemeinen: http://msdn.microsoft.com/en-us/library/dd374081.aspx
  • Grunds‰tzliche Infos zur Organisation von Unicode Codepoints
    • Referenzen
    • Alle im Unicode mˆglichen Codepoints sind auf 17 verschiedenen Ebenen (Planes) verteilt
    • Jede Ebene/Plane hat 65535 Zeichen
    • Um einen Codepoint eindeutig zu identifizieren, wird ihm ein Hex-Pr‰fix [0-9A-F] vorangestellt
      • U+24321 befindet sich in Plane 2
      • U+F4321 befindet sich in Plane 15
      • U+104321 befindet sich in Plane 16 (Pr‰fix wird hier zweistellig)
      • U+4321 befindet sich in Plane 0 (Pr‰fix wird weggelassen, Plane ist implizit 0)
    • Die erste Ebene (Plane 0) ist die sogenannte "Basic Multilingual Plane" (BMP)
    • Die nachfolgenden Ebenen werden zusammengefasst unter dem Begriff "Supplementary Planes"
    • Die BMP Codepoints im Bereich U+D800 bis U+DFFF stellen keine Zeichen dar, sondern sind reserviert f¸r UTF-16, um sogenannte "surrogate pairs" zu bilden; siehe UTF-16 weiter unten
  • UTF-16 Informationen
    • Referenzen
    • UTF-16 ist ein Unicode Character Encoding; es lˆst das ‰ltere Encoding UCS-2 ab
      • UCS-2 verwendete Zeichen mit einer fixen L‰nge von 2 Bytes und konnte deshalb keine Zeichen jenseits der BMP abbilden
      • Der Wert eines UCS-2 Zeichens entsprach einfach dem Codepoint des Zeichens
    • UTF-16 ist im Gegensatz zu UCS-2 in der Lage, den gesamten Unicode Zeichenbereich abzudecken
    • Die Zeichen der BMP werden wie in UCS-2 einfach mit dem Wert ihres Codepoint abgebildet
    • Die Zeichen jenseits der BMP wird mit Hilfe von sogenannten "Surrogate Pairs" abgebildet
      • Um Surrogate Pairs zu bilden werden die Codepoints U+D800..U+DFFF aus der BMP verwendet
      • Diese Codepoints sind keine Zeichen sondern werden vom Unicode-Standard explizit f¸r den UTF-16 Support reserviert
      • Hat der erste 16-Bit Wert eines UTF-16 Zeichens den Wert eines dieser speziellen Codepoints, so wird ein zweiter 16-Bit Wert angeh‰ngt, um das endg¸ltige Zeichen zu bilden
      • Die beiden 16-Bit Werte zusammen werden als "Surrogate Pair" bezeichnet
    • Die Reihenfolge der Bytes innerhalb eines 16-Bit Werts h‰ngt von der Endianness der Maschine ab, auf dem die Verarbeitung stattfindet
    • Um explizit anzuzeigen, welche Endianness eine Zeichenfolge verwendet, kann die Zeichenfolge mit einer Byte Order Mark (BOM) eingeleitet werden
  • Seit Windows NT verwendet Windows intern nur noch Unicode (native Unicode encoding)
  • Unicode Zeichen sind dabei 16 Bit breite Zeichen, das Encoding ist UTF-16, die Byte Order ist per Default Little-Endian
  • 16-Bit Werte vs. "Zeichen"
    • Wo nicht explizit etwas anderes beschrieben ist arbeiten Funktionen wie z.B. strlen() mit der Anzahl 16-Bit Werte
    • Einerseits w‰re das Ber¸cksichtigen von Surrogate Pairs vermutlich zu aufw‰ndig
    • Andererseits gibt es den Begriff "Zeichen" (bzw. "Character") im traditionellen Sinn im Unicode Standard gar nicht, da Codepoints auch unsichtbare und kombinierbare "Zeichen" umfassen
    • Wenn also die Dokumentation von strlen() etc. von "Zeichen" spricht, kommt das tendentiell aus der ASCII-ƒra, wo ein "Zeichen" immer auch gleichzeitig 1 Byte entsprach; in UTF-16 wird dagegen mit 2-Byte Einheiten gearbeitet
  • Um eine Applikation mit Unicode Support zu builden, muss das Preprocessor Makro _UNICODE definiert sein


TCHAR, OLECHAR, BSTR, VARIANT, CString

TCHAR

  • http://msdn.microsoft.com/en-us/library/cc842072.aspx
  • Der Artikel verwendet die Bezeichnungen ANSI und DBCS, meint damit aber eigentlich SBCS und MBCS
  • TCHAR repr‰sentiert ein Zeichen, entweder ein SBCS, MBCS oder Unicode Zeichen
  • F¸r SBCS und MBCS ist TCHAR ein typedef auf char

_T()

  • Ein Makro, um String Literals vom Character Type unabh‰ngig zu machen
  • Wird das Projekt mit Unicode Support kompiliert, so ist _T("foo") definiert als L"foo"
  • Ohne Unicode Support: _T("foo") -> "foo"
  • Alternativen zu _T() sind: TEXT(), _TEXT() und __TEXT()

OLECHAR und OLESTR

  • OLECHAR wird in Automation Interfaces (COM) verwendet
  • Normalerweise ist OLECHAR definiert als wchar_t, d.h. Unicode
  • Historisch kˆnnte man auch char daraus machen, in der Praxis wird das aber nicht mehr gemacht
  • OLECHAR wird f¸r die Definition von BSTR verwendet
  • OLESTR() ist ein Makro, um String Literals vom Type OLECHAR zu erhalten (faktisch wird einfach der Prefix L"" hinzugef¸gt)

BSTR

  • http://msdn.microsoft.com/en-us/library/ms221069.aspx
  • http://www.codeproject.com/KB/string/bstrsproject1.aspx (Mini-Projekt mit Beispielen zum Konvertieren zwischen String-Typen)
  • BSTR = Basic String, oder Binary String
  • Dieser Datentyp wird von/f¸r COM verwendet; BSTR muss in Interfaces verwendet werden, auf die von Visual Basic oder Java Applets aus zugegriffen wird
  • Der Inhalt eines BSTR setzt sich folgendermassen zusammen
    • Length Prefix, 4-Byte Integer = Anzahl Bytes nach dem Prefix und ohne Terminator
    • String = Soviele Bytes wie im Length Prefix angegeben. Encoding = Unicode (d.h. UTF-16 bzw. wide/double-byte characters)
    • Terminator = 2 Null Characters (0x00)
  • Ein BSTR ist ein *Pointer*, der auf das erste Zeichen des Strings zeigt, *nicht* auf den Length Prefix
    • Ein Grund f¸r diese Idiotie ist, dass man auf diese Art ein BSTR an eine Funktion ¸bergeben kann, die ein LPCOLESTR oder LPCWSTR Argument hat
    • Das umgekehrte funktioniert dagegen nat¸rlich nicht: Wird ein LPCWSTR an eine Funktion ¸bergeben, die ein BSTR will, fehlt der Length Prefix
    • Die Rettung ist der Wrapper _bstr_t, siehe weiter unten
  • BSTR Objekte m¸ssen mit Hilfe von speziellen Memory Allocation Funktionen angelegt und zerstˆrt werden
    • Zum Beispiel
BSTR foo = SysAllocString(L"bar");
SysFreeString(foo);
    • Das folgende Beispiel kompiliert zwar, funktioniert zur Laufzeit aber *nicht* richtig!!!!!!!
BSTR foo = L"bar";
    • Dementsprechend kˆnnen einer Funktion, die ein BSTR Argument hat, auch nicht einfach normale Unicode (wchar) Strings ¸bergeben werden!!!
  • BSTR ist definiert wie folgt
typedef OLECHAR FAR * BSTR;        (in OLEAuto.h)
typedef WCHAR  OLECHAR;            (in WTypes.h)
  • _bstr_t
    • http://msdn.microsoft.com/en-us/library/zthfhkd6.aspx
    • Die Klasse _bstr_t ist ein Wrapper um einen BSTR String
    • Die Klasse hat mehrere Constructors, mit denen auf einfache Weise andere String-Typen konvertiert werden kˆnnen (Multibyte, Unicode, Variant)
    • Die Klasse implementiert Reference Counting, um bei Kopien nicht immer den vollen String kopieren zu m¸ssen
    • Die Klasse ist ein "sauberer" Wrapper, d.h. es wird kein Pointer auf den internen BSTR heraugegeben
      • Jedenfalls offiziell
      • Inoffiziell sind auch Operatoren implementiert, die non-const Pointer liefern
      • Diese d¸rfen aber nicht verwendet werden
    • Das bedeutet, dass _bstr_t nicht verwendet werden kann, um ein [out] Argument in einem COM Aufruf zu "beliefern"
  • CComBSTR
    • Ebenfalls ein Wrapper f¸r BSTR, diesmal aber von ATL geliefert
    • CComBSTR gibt Zugriff auf den gekapselten BSTR, man kann also ein [out] Argument in einem COM Aufruf "beliefern"

VARIANT

  • Dies ist eine Wrapper Klasse f¸r verschiedene Datentypen
  • Enth‰lt VARIANT einen String, so wird der String als BSTR gespeichert
  • _variant_t: Wrapper um VARIANT
    • Um an den BSTR Wert zu gelangen, kann das Property "bstrVal" verwendet werden: variantVariable.bstrVal
  • CComVariant: Noch ein Wrapper um VARIANT, diesmal aber von ATL geliefert
  • COleVariant: Noch ein ATL Wrapper um VARIANT, diesmal aber mit Constructor, der ein CString Objekt akzeptiert (und sonst ein bisschen anders ist)

CString

  • http://msdn.microsoft.com/en-us/library/ms174288.aspx (Using CString)
  • CString ist der MFC-Wrapper f¸r TCHAR Strings
  • CString enth‰lt Support f¸r alle Character Types (SBCS, MBCS, Unicode)
  • CString definiert einige Konversions-Operatoren, weshalb man die Klasse ziemlich freiz¸gig einsetzen kann:
CString myString = "foo";
const char* pch = myString;
LPCSTR pch = static_cast<LPCSTR>(myString);   // sollte man nat¸rlich nicht benutzen, auch wenn hier ein Operator existiert
LPCSTR pch = myString;                        // viel schˆner: hier sieht man sofort, dass irgendein Konversions-Operator am Werk sein muss
char ch1 = myString[1];
TCHAR ch2 = myString[1];
BSTR bs1 = myString.AllocSysString();   // anschliessend SysFreeString(bs1)
BSTR bs2; myString.SetSysString(&bs2);  // anschliessend SysFreeString(bs2)
  • Um _bstr_t in einen CString zu konvertieren
_bstr_t bString = "foo";
CString cString1 = static_cast<LPCSTR>(bString);
CString cString2 = static_cast<const TCHAR*>(bString);  // etwas nachhaltiger, da bei Unicode Support automatisch wchar verwendet wird
CString cString3 = bString.GetBSTR();                   // eher nicht verwenden, da man sonst in den Wrapper _bstr_t hineinschaut
  • Es wird empfohlen, f¸r Funktionsparameter den Type LPCSTR zu w‰hlen, weil man dann alle Arten von Strings ¸bergeben kann (literal string, CString, TCHAR array, etc.)
  • Ich bin nicht ganz dieser Meinung - wichtiger f¸r mich ist das Kriterium, ob man in der Schnittstelle einen MFC Type haben will oder nicht
  • LPCSTR finde ich weiterhin ¸bel, weil man darauf keine Forward Declaration machen kann, da es sich um einen typedef handelt.

LPCTSTR etc.

  • LPCTSTR ist der folgende typedef (wobei CONST ein Makro auf const ist, und CHAR ein typedef von char):
typedef CONST CHAR *LPCSTR, *PCSTR;
  • Es existieren eine Unmenge weiterer typedefs und Preprocessor Makros


Conversion between Unicode and MBCS

ATL Conversion Macros

  • http://msdn.microsoft.com/en-us/library/87zae4a3.aspx
  • Die Makros kˆnnen auch in Nicht-ATL Projekten verwendet werden, da sie keine Abh‰ngigkeiten vom Rest von ATL haben (z.B. keine globale Variable _Module)
  • Es gibt alte und neue Makros
    • Alt: ATL 3.0
    • Neu: ATL 7.0
  • Die neuen Makros sind vorzuziehen; eine ‹bersichtstabelle auf der oben referenzierten Seite zeigt die Vorteile
  • Um die Makros zu verwenden
    • Include
#include <atlbase.h>  // evt. nur bei ATL 7.0 Makros noetig?
#include <atlconv.h>
  • Bei den alten ATL 3.0 Makros muss am Anfang der Funktion, die konvertieren will, zus‰tzlich das folgende Makro auf einer eigenen Zeile erscheinen
USES_CONVERSION
  • Ist der Ziel-Type = BSTR, sollte CComBSTR f¸r die Konversion verwendet werden
  • Der Speicher f¸r den Ziel-String wird automatisch angelegt und auch wieder freigegeben
    • Bei den alten ATL 3.0 Makros passiert das erst am Ende der Funktion
    • Bei den neuen ATL 7.0 Makros bereits, wenn die angelegte Variable out-of-scope geht
  • Beispiel neue ATL 7.0 Makros
    • CA2CT destinationString(sourceString); --> konvertiert von Ansi nach TCHAR
    • Bei den ATL 7.0 Makros sorgt der Cast Operator jeweils daf¸r, dass der Ziel-String den gew¸nschten Typ hat
  • Beispiele alte ATL 3.0 Makros
    • LPCTSTR destinationString = A2T(sourceString);
    • W2A(L"foo") = Konvertiert von Unicode nach MBCS
    • T2CW("foo") = Konvertiert von TCHAR nach Constant Unicode
  • Die Makros nutzen folgende Nomenklatur
    • A = ANSI, ein MBCS String bzw. char*
    • W = Wide, ein Unicode String bzw. wchar_t*
    • T = TCHAR*
    • OLE = OLECHAR* (in der Praxis gleichbedeutend mit "W")
    • BSTR = BSTR (nur als Ziel-Type einsetzbar)
    • C = Der String Type ist const

TODO: Gibt es noch andere Mˆglichkeiten?