_UNICODE versus UNICODE und so manches Eigentümliche

Wann nimmt man eigentlich was?
Jedesmal wenn ich Code ansehe finde ich mal einen

#ifdef _UNICODE

dann mal wieder einen

#ifdef UNICODE

Wann eigentlich was?
Die Frage wurde schon oft beantwortet (siehe The Old New Thing : TEXT vs. _TEXT vs. _T, and UNICODE vs. _UNICODE).

In Kurzform:
UNICODE wird in den Windows Header-Dateien verwendet.
_UNICODE in der C-Runtime (CRT).

Interessanterweise gibt es nur den Define _MBCS aber nicht den Define MBCS. Anhand der Namensgebung kann man sehen, dass _MBCS aus/für die CRT nur Einfluss hat. Die Windows API sagt einfach, wenn nicht Unicode dann ist es MBCS!

Daraus ergeben sich einige interessante Abhängigkeiten: 

_T ist abhängig vom Define _UNICODE genauso wie _TEXT (also der CRT)
TEXT ist wiederum abhängig vom Define UNICODE.

Jetzt wird es aber ganz lustig:
Die Datei tchar.h gehört zur CRT. Und TCHAR ist eigentlich kein Define aus tchar.h und der CRT. Dort wird nämlich eigentlich _TCHAR definiert und sollte TCHAR schon definiert sein (durch die windows.h), dann wird TCHAR nicht mehr umdefiniert.

Ist also nur UNICODE definiert und nicht _UNICODE, dann muss der folgende Code einen Compiler Fehler auslösen, wenn windows.h vor der tchar.h inkludiert wird. Erwartungsgemäß meldet der folgende Code einen Compiler-Fehler, obwohl dieser perfekt und korrekt aussieht:

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
int _tmain(int argc, _TCHAR* argv[])
{
 const TCHAR szText[] = „“;
  // Must fail if UNICODE is defined and not _UNICODE, and windows.h included first
 _tcslen(szText);
 return 0;
}

In VC6 gab es noch keine Projekteinstellung für Unicode. Man musste also beide Defines setzen, als Präprozessor Definition. Andernfalls hatte man eine lustige Code Mischung und lief schnell mal in ein Problem wie oben.

Der Schalter in VC-2005 und VC-2003 für

  • „Use Unicode Character Set“ setzt beide Defines _UNICODE und UNICODE
  • „Use Multi-Byte Character Set“ setzt nur _MBCS.
  • „Not set“ setzt Erwartungsgemäß keinen der Defines und man kann das obige Beispiel und den Fehler ausprobieren.

Projektintern verwende ich grundsätzlich immer intern nur den define UNICODE um zu prüfen welche Art von Projekt hier läuft. Und ich achte darauf, dass die windows.h bzw. die afxwin.h Datei immer als erstes inkludiert werden. Dann ist TCHAR entsprechend konform für die Windows API gesetzt. Die CRT ist für mich meistens nur Beiwerk und ich vermeide Ihre Verwendung eigentlich wo ich kann.

An kritischen Stellen baue ich auch gerne den folgenden Test ein um ganz auf Nummer sicher zu gehen:

#if defined(UNICODE) ^ defined(_UNICODE)
#error Inconsitent UNICODE and _UNICODE definition
#endif

Mit dem XOR-Operator wird hier ein Fehler ausgelöst wenn nur einer der beiden Defines gesetzt ist.

Siehe auch die MSDN INFO: UNICODE and _UNICODE Needed to Compile for Unicode

Der kleine Unterschied zwischen CStatic::SetIcon und CWnd::SetIcon

So schnell legt man sich rein. Da hat man ein CStatic in einem Dialog. Man besorgt sich mit GetDlgItem das entsprechende Fenster Objekt und führt SetIcon durch. Also etwa so

GetDlgItem(IDC_MYSTATIC)->SetIcon(hIcon);

Ergebnis: 🙄  Nichts passiert.

Nach etwas Debuggen wird der Fall klar. SetIcon ist nicht virtuell und wie bei allen CWnd und anderen Fenstern nichts anderes als eine Abkürzung/Wrapper für ein SendMessage. Mit dem kleinen Unterschied, dass CWnd:SetIcon WM_SETICON sendet, während CStatic::SetIcon ein STM_SETICON versendet.

GetDlgItem liefert aber (solange kein Subclass durchgeführt wird) immer einen CWnd-Zeiger. Also wird statt der erwarteten STM_SETICON Nachricht eine WM_SETICON Nachricht verwendet, die aber CStatic nicht interessiert.
SetIcon wird nur von Top-Level Fenstern verarbeitet.

BTW: Das Gleiche kann einem auch mit CButton::SetIcon passieren. Nein BM_SETICON gibt es nicht, aber es gibt die Nachricht BM_SETIMAGE, und der kann man eine Bitmap (Wrappername CButton::SetBitmap) oder eben ein Icon übergeben (Wrappername CButton::SetIcon). Auch hier lauert die gleiche Falle!

❗ Ein typischer Designfehler in einer Library! Hier wurde offensichtlich die konsistente Namensgebung in der MFC über das Verwechselungsproblem gestellt. In einer abgeleiteten Klasse sollte niemals eine Funktion gleichen Namen mit gleicher Signatur und unterschiedlicher Funktion existieren.

Konfusion durch DS_SHELLFONT == (DS_FIXEDSYS | DS_SETFONT)

Ich habe in einem größeren Projekt nach einem Problem gesucht, dass nach der Umstellung auf UNICODE auftrat. Durch Erzeugen von Dialogen mit älteren Visual Studio Editionen wurden manche Dialoge mit dem nachfolgenden Font Eintrag erzeugt:

FONT 8, „MS Sans Serif“, 0, 0, 0x1

Die Folge war eine inkorrekte Anzeige wenn UNICODE Zeichensequenzen eingegeben wurden.
Richtig wäre der folgende Eintrag, wie ich es haben wollte

FONT 8, „MS Shell Dlg“, 0, 0, 0x0

in Verbindung mit den Flags DS_SETFONT und DS_SHELLFONT.
Im Resource-Editor findet sich dazu eine Extra Eigenschaft „Use System Font“ direkt unter der „Font (Size)“ Eigenschaft.

Was steht nun in der Ressourcen Datei, wenn man „Use System Font“ anklickt?

🙄 STYLE DS_SETFONT | DS_FIXEDSYS | …

Und die Doku in der MSDN sagt zu DS_FIXEDSYS:
Causes the dialog box to use the SYSTEM_FIXED_FONT instead of the default SYSTEM_FONT. This is a monospace font compatible with the System font in 16-bit versions of Windows earlier than 3.0.

😮 Habe ich hier einen Bug im Ressoure-Editor entdeckt? Was soll ein altes Windows 3.0 Flag in meinem 32bit Programm?

Die Antwort lautet: Nein!
Das ganze klärt sich auf, wenn man die Definitionen dieser Werte ansieht:

#define DS_SETFONT 0x40L /* User specified font for Dlg controls */
#define DS_FIXEDSYS 0x0008L
#define DS_SHELLFONT (DS_SETFONT | DS_FIXEDSYS)

Es ist nur einfach verwirrend, weil der Resource Editor aus der Eigenschaft „Use System Font“ und dem Font „MS Shell Dlg“ die Flags DS_SETFONT | DS_FIXEDSYS im STYLE Eintrag macht und die Bits einzeln auflöst.
Es ist natürlich alles OK, aber wirklich sehr verwirrend, aber ich wiederhole mich.

Vista beendet Programme auch wenn WM_QUERYENDSESSION FALSE returniert

Jeder kennt die nette Nachricht WM_QUERYENDSESSION die einem mitteilt, dass das System herunter gefahren wird, oder der Benutzer sich abmeldet.

Und wir alle sind auch einfach gewöhnt FALSE zu returnieren wenn wir das nicht wollen und damit waren wir sicher das WM_ENDSESSION mit FALSE gesendet wird und das System nicht herunterfährt. Ja! Richtig gelesen waren!

Das hat sich mit Vista nun auch geändert. Wird im lParam Bit ENDSESSION_CLOSEAPP (0x1) gesetzt, dann spielt es keine Rolle wie das Programm es gerne hätte. Die Applikation soll und muss beendet werden. D.h. die Applikation muss auch mit dem Zustand zurecht kommen, dass die Daten nicht gesichert sind, bzw. eben mit dem Zustand, der uns veranlasst hat FALSE zu returnieren.
Dies war bisher ausgeschlossen. Deshalb sollte man sich über das Verhalten seines Programmes an dieser Stelle mal Gedanken machen.

Für MFC Anwender noch eine kleine Herausforderung denn OnQueryEndSession wird von der MFC ohne Parameter aufgerufen und ohne das Vista SDK ist natürlich ENDSESSION_CLOSEAPP nicht definiert

#ifndef ENDSESSION_CLOSEAPP
#define ENDSESSION_CLOSEAPP 0x1
#endif

BOOL CMainFrame::OnQueryEndSession()
{
// Need to get the lParam value for this message to
// determine the reason of the shutdown
LPARAM lParam = AfxGetCurrentMessage()->lParam;
if (lParam & ENDSESSION_CLOSEAPP)
{
  // We are forced to exit here, the user want to close the
  // application, even if we loose data
  OnForcedShutdown():
  return TRUE;
}
else
  // do the default
  return __super::OnQueryEndSession();
}

BTW: Eine normale MFC Applikation berücksichtigt dies nicht korrekt. Es wird der Status von allen Dokumenten geprüft und evtl. einen entsprechender Dialog angezeigt, wenn eines der Dokumente nicht gespeichert ist. Wird aber die Anfrage mit ENDSESSION_CLOSEAPP gesendet, dann muss die Antwort in 5 Sekunden erfolgen. Einen Dialog anzuzeigen bzw. Frage zu stellen wie es die Standardimplementierung macht, passt hier nicht!

Man sollte sich unbedingt hier die Vista Änderungen zu Gemüte führen und gegebenenfalls auch ShutdownBlockReasonCreate implementieren.

Weitere Infos zu den Änderungen findet sich in der aktuellen MSDN:

Anmerkung: Jeder der sich mit der Vista-Zertifizierung auseinandergesetzt hat wird erkannt haben, dass es sich bei dem hier beschriebenen Verhalten um den Test Case 30 handelt, bzw. die Requirements, Reliability 3.1!

Ermitteln ob ein Prozess „als Administrator“ läuft oder nicht

Es gibt einigen Code im Netz, der ermittelt ob man administrative Rechte hat oder eben nicht.
Dazu wurden zwei Methoden meistens verwendet. Die meisten prüften ob im aktuellen Token der vordefinierte SID für den Administrator vorhanden war. Oder man verwendete NetUserGetInfo.
Besagte klassische Methoden sind auf Felix Kasza’s Win32-MPV Seiten (noch)* nachzulesen.

Leider berücksichtigt dieser Code nicht, dass es unter Vista ein sogenanntes Restricted Token geben kann. Theoretisch ist dies schon seit Windows 2000 und auch unter XP möglich. Nur richtig problematisch wird dies unter Vista, denn diese Restricted Token werden hier auch entsprechend verwendet.
Meldet man sich als Admin unter Vista an, dann meldet der Beispielcode, den man unter dem obigen Link findet, immer, dass der Benutzer Administrative Rechte hat! Egal ob nun der Prozess mit angehobenen oder ohne angehobene Rechte gestartet wurde.

Will also ein Programm das ein Trustinfo Manifest mit asInvoker hat wissen, ob es nun evtl. doch Als Administrator ausgeführt wird oder nicht, muss man das Restricted Token berücksichtigen. Dies macht der Code der in dem überarbeiteten KB-Artikel 118626 veröffentlicht wurde. Dieser Artikel hat in der Vergangenheit, die gleiche Methode verwendet die auch Felix Kasza’s (noch)* veröffentlicht.
Der neue Code den wir nun in dem KB-Artikel finden baut eine DACL auf mit der ACE des lokalen Admins, diese wird dann mit der Funktion AccessCheck gegen das aktuelle Token geprüft. Dieser Code ist nicht trivial und ist gut dokumentiert. Man sollte ihn sich wirklich mal komplett durchlesen. 

Das sehr interessante an diesem Code ist auch, dass er mit und ohne Trustinfo Manifest funktioniert. Er liefert TRUE wenn der Prozess mit administrativen Rechten läuft und FALSE wenn eben nicht.

Für was ist so etwas gut?
Nun man kann in seinem Programm auf diese Weise direkt ermitteln ob bestimmte Funktionen die z.B. schreibend auf die HKLM zugreifen erlaubt sind oder nicht. Sicher man könnte auch ein „Probe-Schreiben“, aber der in dem KB-Artikel beschriebene Weg ist mit Sicherheit eleganter.
Wobei das „Probe-Schreiben“ auch ein Trustinfo Manifest voraussetzt.

BTW: Der Code in dem KB-Artikel hat (noch)* einen lästigen Fehler. Er definiert ACCESS_READ und ACCESS_WRITE also const DWORD. Das ist unnötig mit den aktuellen Headern. Die Fehlermeldungen sind nicht ganz einfach zu verstehen. Am Besten einfach die beiden Zeilen auskommentieren.

*(noch) beschreibt den Zustand der entsprechenden Artikel, als ich diesen Beitrag schrieb. Das mag sich ändern, weil ich bereits entsprechende Emails über die Probleme mit den Beispielen geschrieben habe.

CreateStreamOnHGlobal und GlobalAlloc

Man könnte diesen Artikel auch den folgenden Titel „Was passieren kann, wenn man die Dokumentation nicht richtig liest!“ geben. 😈

Dieser Code

HGLOBAL hMem = ::GlobalAlloc(GMEM_MOVEABLE,iMaximumSize);
if (!hMem)
    AfxThrowMemoryException();
LPVOID pImage = ::GlobalLock(hMem);
int iSize = FillBufferWithData(pImage);
::GlobalUnlock(hMem);
CComPtr<IStream> spStream;
HRESULT hr = ::CreateStreamOnHGlobal(hMem,FALSE,&spStream);
if (SUCCEEDED(hr))
{
    // Limit the stream to its real size
    ULARGE_INTEGER ulSize;
    ulSize.QuadPart = iSize;
    VERIFY(SUCCEEDED(spStream->SetSize(ulSize)));
    // Do whatever has to be done
   DoSomethingWithStream(spStream);
}
// Release Stream
spStream = NULL;
// Free memory WITH A RANDOM CRASH
::GobalFree(hMem);

sieht ganz normal aus. Eigentümlicher Weise passiert es manchmal, genauer gesagt recht selten, dass der GlobalFree fehlschlägt. Der Application Verifier meldet einen inkorrekten Heap Block. Aber wie das. Der Block wird mit GlobalAlloc allokiert an CreateStreamOnHGlobal übergeben und freigegeben nachdem der Stream entsorgt wurde.

Was ist die Ursache für das Problem? Nach einigem Prüfen und Tests kam ich dahinter, dass offensichtlich nach einer Reallocation des Speichers mein Heap-Block ungültig wird.
Erstaunlich ist, dass SetSize hier nicht den Speicherbedarf vergrößert sondern nur den Stream auf die korrekte Größe verkleinert! Dennoch… unter bestimmten Umständen wird hier eine Reallocation vorgenommen.
Alles Rumexperimentieren nützt nichts. Der Fehler oder das Problem in diesem Stück Code wird auch nicht vom stundenlangen Ansehen und Debuggen nicht klarer.

Also nochmal richtig RTFM (Read the fine MSDN) und dort finden wir diesen netten Absatz:
hGlobal [in] Memory handle allocated by the GlobalAlloc function. The handle must be allocated as ➡ movable and nondiscardable.

Und nun wird klar wo der Fehler liegt. Der Speicherblock der mit GMEM_FIXED alloziert wurde kann nicht realloziert und vergrößert werden. CreateStreamOnHGlobal reagiert nicht mit einem Fehler, wenn man den Block mit GMEM_FIXED allokiert. Aber das hat später evtl. einen sehr schwierig zu lokalisierenden Fehler zur Folge.

Also flink die Allokation in GMEM_MOVEABLE geändert und siehe da. Das Handle bleibt erhalten, der GlobalFree schlägt nicht mehr fehl.

Die Dokumentation ist an dieser Stelle aber auch wirklich nicht sehr auffällig. 😐
Deshalb habe ich einen Verbesserungsvorschlag dazu gemacht:
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=260866

Anmerkung: Der Ursprüngliche Code verwendete CHeapPtr<BYTE,CGlobalAllocator>, weil ich es liebe wenn Destruktoren aufräumen. Der CGlobalAllocator verwendet auch immer GMEM_FIXED, was das ganze noch etwas unübersichtlicher gemacht hat, denn wer denkt schon dran wie eine solche Wrapper-Klasse Speicher allokiert.

BTW: Auch hier führte der Application Verifier auf die korrekte Spur.

UAC und CreateNamedPipe mit lpSecurityAttributes==NULL

Das Leben steckt einfach voller Überraschungen. Da hat man einen Service der im LocalSystem Account läuft. Mit diesem Service wird über Named Pipes kommuniziert.

Das Programm, das diesen Service steuert soll nur für Administratoren verfügbar sein und greift lesend und schreibend auf diese Pipe zu. Der Admin nutzt dieses Programm oft, muss man dazu sagen.

In der Vergangenheit war da nicht viel zu tun. Einfach lpSecurityAttributes auf NULL setzen und OK. Der Admin hat dann einfach das Steuerungsprogramm gestartet und war glücklich.
Nun unter Vista läuft aber das Steuerungsprogramm nicht mehr mit administrativen Privilegien.

  • Ist das Programm nicht Vista aware (also hat kein Trustinfo Manifest), dann hat man keinen Zugriff auf die Pipe. Den in diesem Fall wird der Admin Token ausgefiltert!
  • Ist es Vista aware und man setzt asInvoker als Trustinfo, dann hat man auch keinen Zugriff. Logo.
  • Nur requireAdministrator führt zum gewünschten Ziel. Natürlich wieder mit der (manchmal) lästigen Meldung für ein Programm das häufig benutzt wird.


❗ Also liebe Programmierer Gemeinde! Bitte die SECURITY_ATTRIBUTES richtig ausfüllen mit einer DACL die eben nicht mehr nur den lokalen Admin enthält.  Diese bei CreateNamedPipe verwenden und dann kann das Programm auch normal mit asInvoker gestartet werden.

Ach ja! Und ehe ich es vergessen ein NULL DACL ist hier nicht im Sinne des Erfinders… 🙂

😐 Insgesamt ärgerlich:
Man möchte es dem Benutzer möglichst einfach machen Programme zu nutzen. Der Vista-Admin-Privileg Dialog stört da manchmal (nicht immer). Also was macht man? Man setzt die Rechte für die Names Pipes und andere Objekte dieser Welt herunter, weil der Admin Token eben nicht mehr Bestandteil der Rechte ist für Programme die mit asInvoker gestartet werden.
Die Folge: Die Programme werden mit weniger Sicherheitsschranken gebaut, als zuvor.
War das im Sinne des Erfinders?

BTW: Da man sich mittlerweile schon blind angewöhnt hat, diesen Dialog zu bestätigen kann man sich fragen ob da noch ein Sicherheitsaspekt verbleibt, weil keiner mehr liest, welches Programm eigentlich administrative Rechte haben will.

Notwendigkeit von Manifesten für DLL’s mit VC-2005

Durch eine Anfrage in meiner Lieblingsgruppe microsoft.public.de.vc bin ich auf folgende interessante Frage gestoßen: Benötigt eine DLL die mit VC-2005 erzeugt wurde ein Manifest?

Nun wie viele Antworten im Leben lässt sich dies nicht eindeutig mit Ja oder Nein beantworten.

Nein! Es wird kein Manifest benötigt, wenn die CRT statisch gelinkt wird. In diesem Fall wird auch kein Manifest benötigt zumindest nicht für die CRT, was nicht heißt, dass nicht auch andere Assemblies per Manifest angebunden werden müssen.

Nein! Wenn die EXE-Datei bereits über ein Manifest verfügt und eine CRT-DLL mit gleichem Namen lädt, hier also z.B. die MSVCR80.DLL. In diesem Fall kann man sich ein Manifest sparen. Es kann aber hier spannend werden, weil die entsprechende DLL natürlich so nicht beeinflussen kann dir 8.0 CRT als SP1 oder RTM zu nutzen. Aber vermutlich ist das sowieso egal.

Ja! Die DLL benötigt ein Manifest, wenn ein beliebiges Programm die DLL nutzt. Zum Beispiel eines, das die CRT statisch linkt, oder das mit einer älteren VC Version erzeugt wurde. In diesem Fall muss die DLL zwingend ein Manifest haben. Und jetzt wird es noch strenger. Wird die DLL per LoadLibrary geladen, dann muss dieses Manifest sogar embedded sein! 🙄
Externe Manifeste bei DLLs werden nur beim impliziten Laden berücksichtigt. Wird eine DLL mit LoadLibrary geladen, dann werden nur eingebette Manifeste berücksichtigt.

❗ Falle: Hat die DLL kein Manifest wird immer garantiert, dass die CRT Version von EXE und DLL passen. Man kann also gefahrlos Speicher in der DLL allozieren und in der EXE freigeben und umgekehrt. Oder eben auch CRT Objekte austauschen. Nutzt die DLL ein eigenes Manifest besteht die Möglichekit, dass EXE und DLL eine unterschiedliche CRT verwenden! Und noch spannender wird es wenn eine EXE mit CRTx eine DLL1 benutzt die per Manifest CRTy verwendet und diese nun eine DLL ohne Manifest lädt… Ja es wird die CRT der EXE verwendet…

Ja ja. Wir haben dank der Manifeste nun keine DLL Hölle mehr. Wir haben eine Manifest Hölle 😀

Meine Empfehlung daher:

  1. Wenn es eigenständige kleine Module sind, dann statisch gegen die CRT linken.
  2. Ansonsten immer ein Manifest einbetten, wenn die DLL eigenständig genutzt wird!
  3. Wird die DLL im Kontext einer bestimmten EXE(s) Datei verwendet, sollte nur die EXE(s) ein  Manifest haben.
  4. In jedem Fall darauf achten, dass gleiche CRT Versionen verwendet werden. RTM, SP1 und irgendwann wahrscheinlich SP2 werden hier Konflikte möglich machen.
  5. MFC Extension DLLs müssen zwingend mit der selben MFC Version (RTM oder SP1) verwendet werden!

Stoff zum weiterlesen:
Isolated Applications and Side-by-side Assemblies

Besonders lesenswert hier der Beitrag von Nicola Dudar:
How to Debug ‚The System cannot Execute the specified program‘ message

Button + Accelerator + ShowWindow(SW_HIDE) – EnableWindow(FALSE) = Falle

Da hat man einen Multifunktionalen Dialog. Einer der Schalter in dem Dialog heißt Delete. Und das D ist als Accelerator mit einem & versehen. Gemäß einer internen Rechteverwaltung haben manche Nutzer nicht das Recht diesen Button zu benutzen. Der Programmierer (nicht ich ;-)) hat in diesem Fall einfach den Schalter mit ShowWindow(SW_HIDE) verborgen. Ein weiterer Test ob die Rechte wirklich gegeben sind entfiel im OnBtDelete Handler.

Nun stellte sich aber heraus, dass es manche Nutzer geschafft haben, dennoch Einträge zu löschen.

Nun der Grund ist einfach. Solange der Button nicht mit EnableWindow(FALSE) auch disabled wird, kann man mit ALT+D, also Drücken der ALT-Taste und des Accelerators diesen Schalter auslösen.

Jo. So einfach hat man ein Userinterface gebastelt, mit dem man sich hereinlegen kann.

Mark Russinovich, David A. Solomon: Microsoft Windows Internals. Windows 2000, Windows XP und Windows Server 2003

„Windows Internals“ von Mark. E. Russinovich, und A. Solomon ist ein Muss für jeden Windows Entwickler, der ein Stück weiter hinter die Kulissen von Windows sehen will oder auch muss. Es ist nicht nur ein fundiertes und gut gegliedertes Werk mit viel Tiefgang in die Hintergrundtechnologie die Windows ausmacht. Manches, was man als Programmierer bisher einfach nur so hin programmierte, wird erst verstanden wenn man dieses Buch gelesen hat. Das Buch eignet sich als Nachschlagewerk wie als „Lesebuch“ um seinen Horizont zu erweitern. Mithilfe der Tools, Werkzeuge und Erklärungen für den Debugger wird man auch in die Lage versetzt schwierigere Probleme und Fehler in eigenen Programmen auf die Spur zu kommen. Aber Achtung: Leichte Kost ist dieses Buch nicht! Es wendet sich an den erfahrenen Programmierer und möchte diesem weitere Einblicke und Kenntnisse vermitteln. Und dieser Aufgabe wird das Buch zu 100% gerecht. Das Buch lohnt sich auch unbedingt für jeden der die dritte Ausgabe von „Inside Microsoft Windows 2000“ im Schrank stehen hat, denn hier werden auch 64bit Technologie, Terminalserver, Hyperthreading angesprochen und die Unterschiede in den Prozessor spezifischen Implementierungen gegeben. Behandelt werden die Technologien der Plattformen Windows 2000, Windows XP und Windows 2003. NT4 findet nur noch in der Einleitung eine Erwähnung. Gegenüber der dritten Ausgabe sind mehr und bessere Beispiele vorhanden und auch die Tools selber, mit denen man das Windows System erkunden kann sind besser erklärt. Die Übersetzung ist erfreulich gut, und englische Fachbegriffe wurden an den Stellen belassen, an denen sie auch Sinn machen. Es wurde weder übermäßig verdeutscht noch zu viel im englischen belassen. Man stellt sich hinterher also nicht die Frage, ob ein rein englisches Buch besser gewesen wäre. Da ich normalerweise fast immer nur „Originale“ kaufe und lese, und mehr durch Zufall auf diese deutsche Ausgabe gestoßen bin, musste ich einiges an meinen Vorurteilen von Übersetzungen korrigieren. Es ist wirklich eine komplett neue Überarbeitung, die noch gelungener erscheint, als der Vorgänger.

5 Sterne

http://www.amazon.de/Microsoft-Windows-Internals-2000-Server/dp/3860639773