OemToCharBuffW macht nicht das gleiche wie OemToCharBuffA und anschließender Umwandlung nach Unicode

Ein Stück Code in unserer Software behandelt Datenimport aus fremden Dateien. Nicht wenige alter DOS Programme erzeugen ja Daten im „OEM“-Zeichensatz (DBase etc.).
Wir haben in der Vergangenheit immer streng die T-Notation verwendet und eigentlich sollte dies einen vor vielen Überraschungen bewahren. Aber Pustekuchen.

Folgender Code sollte Daten aus einem OEM-Stream in einen CString umwandeln und eigentlich bin ich davon ausgegangen, dass das Ergebnis für Tabulatoren (‚\t‘) und Zeilenschaltungen („\r\n“) ergebnisneutral ist. D.h. in anderen Worten ich erwartete in dem String genauso viele Tabulatoren und Zeilenschaltungen vor wie nach der Konvertierung.
Die Umwandlung erfolgte mit solch einem Stück Code:

CString strText;
::OemToCharBuff(szText,CStrBuf(srText,len),len);

Sieht eigentlich harmlos aus. Allerdings musste ich feststellen, dass hier OemToCharBuffA und OemToCharBuffW ganz und gar nicht korrespondierende Ergebnisse liefern.
In der Unicode Version, also in der Version die OemToCharBuffW verwendet, wurden Tabulatoren zu 0x25cb L’○‘ wchar_t  und Zeilenschaltungen zu 0x266a L’♪‘ wchar_t und 0x25d9 L’◙‘ wchar_t!

Führt man jedoch zuerst eine Kovertierung in den „ANSI/Windows/8bit“-Zeichensatz durch und konvertiert anschließenend diesen ANSI-String nach Unicode, dann ist alles gut und so wie man es erwartet.

Wer Lust hat das nachzubauen kann das mit dem folgenden Code. Wichtig sind eigentlich nicht die OEM-Umlaute sondern nur die Tab- und Zeilenschaltungen:

const char szText[] = "Dies ist ein OEM TestString\r\n"
       "mit Zeilenschaltungen\tund Tabs\r\n"
       "und Umlauten:\r\n"
       "Ž=AE, ™=OE, š=šE\r\n"
       "„=ae, ”=oe, =ue, á=ss";
const size_t len = sizeof(szText);

CStringW strOut1;
::OemToCharBuffW(szText,CStrBufW(strOut1,len),len);
CStringA strOut2;
::OemToCharBuffA(szText,CStrBufA(strOut2,len),len);
CStringW strOut3(strOut2);

_ASSERT(strOut1.Compare(strOut3)==0); // Das sollte eigentlich gleich sein

PS: Getestet habe ich das auf einem Windows 7 64bit und 32bit OS.

Siehe folgende Links zu den Begriffen OEM/ANSI:
http://blogs.msdn.com/b/oldnewthing/archive/2005/10/27/485595.aspx
http://blogs.msdn.com/b/oldnewthing/archive/2005/03/08/389527.aspx

Nun haben wir auch offiziell einen neuen Standard mit C++11 (vormals C++0x)

Es ist fast an mir vorbeigegangen:

http://herbsutter.com/2011/08/12/we-have-an-international-standard-c0x-is-unanimously-approved/

Damit sind große Teile der Werkzeuge mit denen wir schon seit VS-2008 SP1 arbeiten standardisiert… 🙂
Aber ob wir uns an den Namen C++11 gewöhnen werden wage ich zu bezweifeln. Herb Sutter kann/will es anscheinend nicht.

Änderungen in den VC-Libraries des Sicherheitsupdates vom 09.08.2011

Eigentlich kann man es kein Sicherheitsupdate nennen. Ich sehe  eigentlich nur eine sicherheitsrelevante Änderung in:

  • atltransactionmanager.h (ATL)
    Hier wird das Laden der ktmw32.dll jetzt mit einer neuen Funktion AtlLoadSystemLibraryUsingFullPath durchgeführt, die in atlcore.h hinzugefügt wird. Diese Funktion lädt eine DLL nur aus dem Windows-System Verzeichnis. Damit wird Binary-Planting verhindert, aber dies betrifft eigentlich nur Windows Vista / 7 / 2008 Windows 2003 und Windows XP (Nachtrag am 18.08. siehe dazu Kommentar von Stefan Kuhr) und diejenigen die den Kernel Transaction Manager mit der ATL nutzen.

Bugfixes habe ich folgende gefunden:

  • afxtoolbarimages.cpp (MFC)
    In CPngImage::LoadFromBuffer wurde bei der Verwendung dieser Funktion in Speicherblock eines Streams nicht freigegeben (falsche Nutzung von CreateStreamOnHGlobal).
  • dbcore (MFC / ODBC)
    In CRecordset::BuildUpdateSQL in der ODBC Implementierung, wird bei Abfrage eines Cursornamens ein Puffer von 18 Zeichen Länge verwendet (MAX_CURSOR_NAME). Ist der Cursorname länger so wurde eine Exception geworfen. Jetzt wird erkannt, dass der Buffer zu klein ist und es wird ein dynamischer Buffer mit ausreichender Größe alloziert und der Name dann abgefragt.
  •  xutility (STL und auch CRT)
    In der Basisklasse der Iteratoren wurde eine Änderung gemacht. Scheinbar hat die Zuweisung eines „leeren/nicht initialisierten“ Iterators bisher einen Iterator gar nicht verändert. Der alte Iterator wurde nicht aufgelöst durch _Orphan_me. Dies bringt dann Probleme mit sich wenn der _ITERATOR_DEBUG_LEVEL mit 2 genutzt wird.
    Da dieser STL Code auch komplett in der CRT verwendet wird, hat dies auch Einfluß auf die CRT.
    Auf die Release Version aber hat diese Code-Änderung jedoch keinen Einfluss, soweit ich das erkennen kann.

Es gibt noch einige andere Dateien, die geändert wurde, aber hier haben sich nur unwichtige Kommentare geändert.

Fazit: Alles in allem ein Sicherheitspatch, der eher Bugfixes enthält, aber selbst die sind nicht sonderlich weitreichend. Und der Nutzerbereich, der mit der ATL den Kernel Transaction Manager nutzt, wird wohl eher klein sein…

PS: Die Dateien dieses Sicherheitspatches haben die Version 10.0.40219.325.
Die Dateien aus dem VS-2010 SP1 haben die Versionsnummer 10.0.30319.1.

Nachtrag 01.12.2011, MSDN Links:
Sicherheits Bullentin:   MS11-025
Knowledge Base-Artikel (KB-Artikel):  KB2565057