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!

Warum man seine Libraries mit _CRT_NOFORCE_MANIFEST erzeugen sollte

Seit VC 2005 wird die DLL-Version CRT ja grundsätzlich über ein Manifest geladen. Erzeuge ich Code, der die CRT verwendet, dann wird automatisch ein #pragma comment(linker,“/manifestdependency:..“) Eintrag erzeugt. Den entsprechenden Code dazu findet man in der _crtdefs.h Datei des entsprechenden Include-Pfades.

Dieser Manifest Eintrag wird später vom Manifest Tool (MT.EXE) gesammelt und im Manifest der Applikation oder der DLL verbaut.

Gesetzt den Fall ich habe Library erzeugt, die auch die DLL-Version der CRT verwendet, dann wird automatisch die entsprechende Version der CRT im Manifest hinterlegt. D.h. bei VS 2005 RTM die CRT Version 8.0.50727.42 und bei SP1 die CRT Version 8.0.50727.762

Wird nun für ein Projekt eine Library erzeugt mit einem VS 2005 RTM, und evtl. diese LIB lange nicht verändert und dann diese Library in einem Programm verwendet, dass mit VS 2005 SP1 erzeugt wird, dann werden beide CRT Versionen im Manifest eingetragen!
Der Lader ist zwar intelligent und lädt die letzte DLL (SP1). Aber undurchsichtig wird es in jedem Fall.
Vor allem wenn man wirklich mit evtl. einer älteren Version ausliefern möchte und SP1 auf der Ziel-Maschine nicht installiert ist.

Ich habe noch nicht genau herausbekommen wie entschieden wird welches der Manifesteintrag ist, der letzten Endes zählt. Scheinbar macht es auch nichts wenn, man die Version der CRT Versionen falsch angibt. Es wird dann die CRT mit der nächst höheren CRT geladen… (aber dazu muss ich noch etwas experimentieren)…

Dieses Verhalten ist gravierend unterschiedlich zu allen Vorgängerversionen von VC. Wie bisher hält Microsoft das Versprechen der binären Kompatibilität. Theoretisch müsste auch die mit RTM erzeugte LIB sofort funktionieren. Auch der Linker wird diese LIB sofort mit der entsprechenden MSVCR80(D).DLL verbinden. Nur hat er eben nicht das letzte Wort bei den Manifesten.

Man kann das ganze verhindern, indem man _CRT_NOFORCE_MANIFEST bevor die CRT Dateien inkludiert werden (am Besten in der stdafx.h oder in den Projekteinstellungen).
In diesem Fall wird kein #pragma, dass für den Manifest Eintrag verantwortlich ist, erzeugt.
Der Effekt ist dann wie gewollt, dass nur das eigentliche Projekt, dass dann das Executable erzeugt Einfluss auf die Ausgabe des Manifestes hat. Man kann also dann auch eine Lib-Datei mit VS-2005 SP1 erzeugen und mit einem VS-2005 RTM linken. Das Manifest wurde durch diese Lib dann nicht beeinflusst und so soll es IMHO sein.

Aber aufgepasst:
Seit VS-2005 muss in jedem Fall auf noch eine strenge Trennung der Release und Debug Versionen achten. Wenn nun kein Manifest mehr zum Beispiel in einer Release-Lib erzeugt wird, weil _CRT_NOFORCE_MANIFEST verwendet wird, nun aber diese Release Lib in ein Debug Projekt eingebunden wird.  Dann wird das Executable implizit mit der MSVCR80.DLL gebunden, aber diese DLL kann nur über ein Manifest geladen werden. Das ist aber nicht vorhanden. Resultat ist das was in diesem Beitrag beschrieben steht: Manifest-Hölle „…MSVCR80.dll nicht gefunden…“ (siehe Nachtrag)

VS Tipps & Tricks: Kann man Intellisense in VS-2005 ausschalten?

Immer wieder klagen User über Abstürze in VS-2005, die scheinbar mit dem Intellisense zusammengehören. Oder es wird von extrem langen Wartezeiten und zähem Verhalten der IDE berichtet. Die Frage ob man also Intellisense abschalten kann wundert nicht.

Ich persönlich kann diese Effekte nicht bestätigen bzw. habe es noch nicht erlebt.
<werbeblock>Ich nutze außerdem Visual-Assist von http://www.wholetomato.com , mit dem wohl besten Intellisense, das man sich vorstellen kann. Ein Blick lohnt sich in jedem Fall!
Vorsicht! Es macht süchtig. Sobald ich an einem Rechner sitze an dem es nicht installiert ist, fühle ich mich, als ob man mir 4 Finger amputiert hat.</werbeblock> 🙂

Aber zurück zur eigentlichen Frage:
Kann man Intellisense ganz ausschalten?

Ja es geht dazu muss man die folgende Datei löschen oder umbenennen:
<VS root path>\VC\vcpackages\feacp.dll

Manifest-Hölle „…MSVCR80.dll nicht gefunden…“

Mir ist wieder einmal ein Projekt untergekommen, das in der Debug Version nicht starten wollte. Ursache diese Fehlermeldung:
„Die Anwendung konnte nicht gestartet werden, weil MSVCR80.dll nicht gefunden wurde. Neuinstallation der Anwendung könnte das Problem beheben.“

Der Grund für diesen Fehler war in diesem Fall ein Manifest, dass selbst geschrieben wurde und nicht durch den Manifest Compiler erzeugt wurde.

Der Reihe nach:

  • Es wurde ein Projekt gebaut, das eine externe Library benutzte
  • Diese externe Library wurde mit dem selben Compiler erzeugt. Sowohl als Debug, als auch als auch Release Version. (ZLib)
  • Alle Projekte verwenden dynamische Bindung an die CRT.
  • Der Entwickler machte nun aber einen kleinen Fehler. Im Debug Build instruierte er den Linker so, dass die Release Version der Library verwendet wurde. BTW: Ein Grund mehr immer #pragma comment(lib,…) zu verwenden, dann kann man in den Projekteinstellungen keinen Mist machen.
  • Wie ich schon erwähnte wurde nun das Manifest mit der Hand erzeugt um die entsprechenden Abhänigkeiten und auch das Trustinfo Manifest korrekt zusammenzubauen, bzw. weil nicht mit MT.EXE aus dem Vista SDK verwendet wurde (siehe mein Blog).
    Die Release CRT wurde natürlich nicht mit angegeben (obwohl verwendet), aber dessen war sich natürlich keiner im Team bewusst.

Nun kam es zu dem Effekt, dass dieses Projekt im Debug Modus nicht starten wollte, weil die MSVCR80.DLL nicht gefunden wird. Ja und das stimmt, denn diese DLL kann nicht gefunden werden, da Sie nicht in den entsprechenden Manifesten definiert wurde.

Wie findet man so einen Fehler?
Herausbekommen habe ich das durch die Verwendung von DEPENDS.EXE. Damit konnte ich erstmal ermitteln wer, wen, wann laden möchte und wo das Laden letzten Endes fehlschlägt. 
Dann habe ich die entsprechenden Module (DLLs und EXE Dateien) in den Resource-Editor geladen. Dort habe ich die entsprechenden Manifeste kontrolliert.

Nachtrag 22.03.2007 (als Kommentar wird es evtl. übersehen)
Ich habe aktuell auch in einem anderen Fall ähnliches herausbekommen. Hier wurde auch eine alte ZLib-Release Version (und andere) in ein Debug Projekt eingebaut. Nur waren diese Libraries im Projekt nicht mit VS-2005 kompiliert worden, denn sonst wären entsprechende Manifest Einträge eingeflossen. Soweit ich das Projekt sehen konnte wurde _CRT_NOFORCE_MANIFEST auch nicht verwendet, was die Erzeugung der Manifest Einträge verhindert hätte.
Das Resultat auch hier. Eine MSVCRT80.DLL wird vom Linker verlangt, aber es existiert kein Manifest dazu. Die entsprechende gleiche Fehlermeldung auch hier.
Ein Rebuild der entsprechenden Libraries und des Projektes lies das Programm dann zwar starten, aber dennoch wurden in diesem Falle gleichzeitig Debug-CRT und Release-CRT verwendet, was sicher nicht im Sinne der Programmierer war.

Wir lernen daraus wieder einmal: Achte streng auf die Trennung von Release und Debug Libraries. 😉

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.

Gründe für den R6034 bei der VC 8.0 DLL CRT

Warum lässt sich die neue VC 8.0 CRT eigentlich nicht ohne Manifest verwenden?
Schauen wir uns also mal an was passiert wenn eine Applikation oder eine DLL geladen wird, die die VC 8.0 CRT als DLL verwendet:

Die DllMain der CRT wird aufgerufen. Bei der C-Runtime DLL ist dies die Funktion _CRTDLL_INIT die sofort die Funktion __CRTDLL_INIT aufruft.

Diese Funktion sorgt nun der Reihe nach dafür, dass:

  • die aktuelle Windows Version bestimmt wird
  • der Heap initialisiert wird
  • die TLS Bereiche für multithreading angelegt werden
  • das Environment und die Befehlszeile bestimmt werden
  • die MBCS Umwandlungstabellen angelegt werden
  • alles was mit atexit, floating-point math etc. initialisiert wird
  • und… jetzt wird es spannend… die Funktion _check_manifest aufgerufen wird.

In der Funktion _check_manifest wird der Code ausgeführt der zum Runtime Fehler R6034 „The application has attempted to load the runtime library incorrectly. Contact support for more information“ führen kann, nämlich genau dann, wenn diese Funktion FALSE zurück gibt.

Was macht diese Funktion nun?
Im Source-Code der CRT ist dies wunderbar dokumentiert:

  1. if (pre-fusion OS)
      return TRUE; [no need to check]

    Das bedeutet nichts anderes, als das die nachfolgenden Tests nur auf XP/Vista/2003 und folgenden OS durchgeführt werden. Also im Klartext unter Windows 2000 kann dieser Runtime Error nicht auftreten.
  2. if dll is being loaded by instrumented-mscoree.dll.
      return TRUE;
    OK. Ein Speziallfall. Im Kontext der .NET CLR und des Profilers ist auch ales erlaubt. Also wenn sich MSCoree.dll (Microsoft .NET Runtime Execution Engine) und PGORT80.dll (Profile Guided Optimization Instrumentation Runtime) im Speicher befinden ist auch alles paletti.
  3. if (dll is loaded from system32)
      return FALSE;
    Dies ist interessant. In keinem Fall darf diese DLL im Windows\System32 stehen! Oder wenn Sie von dort geladen wird fielgt einem der R6045 um die Ohren.
  4. if (!(loaded through a manifest))
      return FALSE;

    Hier wird über die Funktion FindActCtxSectionStringW geprüft ob diese DLL über ein Manifest geladen wird. ActCtx ist die Abkürzung für Activation Context.
    Wurde kein solcher Activation Context gefunden (also kein Manifest), dass für diese DLL verantwortlich ist, dann heißt das auch R6045.
  5. if (loaded from %SystemRoot%\WinSxS)
      return TRUE; [loaded from the WinSxS cache]

    Wird die DLL aus den Side by Side Verzeichnissen geladen ist schon alles gut.
    Aber auch das geht nur mit gültigem Manifest.
  6. if (manifest is in the same folder as the dll)
      return TRUE;
    Auch das ist interessant. Das Manifest muss zwingend im selben Verzeichnis wie diese DLL liegen, denn sonst sind wir fertig
  7. return FALSE; [loaded with another manifest]
    und nun schlägt das Laden fehl, da das Manifest offensichtlich nicht im DLL Verzeichnis liegt. Auch hier ein R6045.

Zurück noch mal zum Fall 2. Was macht dieser Test „if dll is being loaded by instrumented-mscoree.dll“? Er macht nichts anderes als zu prüfen ob die beiden DLLs im Speicher sind.

BTW: Man könnte nun ganz frech zwei leere DLLs mit diesen Namen erzeugen und diese in ein Projekt implizit laden lassen… und siehe da, die Applikation benötigt auf einmal kein Manifest mehr…
Aber warum sich das ganze verbietet ist auch klar. DLLs mit den oben genannten Namen zu erzeugen, kann ganz schön verwirrend und problematisch werden.
Und warum zwei leere DLLs in einen Prozess laden, wenn es reicht ein Manifest zu ergänzen? 😉

VS-2005 SP1 für Vista bringt kein Update für die MT.EXE

In meinem Beitrag UAC Trustinfo Manifest in ein VC-2005 SP1 Projekt einfügen habe ich berichtet wie man auf einfache Art und Weise ein UAC-Trustinfo-Manifest in ein VS2005 Projekt einbauen kann.

Dazu musste jedoch die MT.EXE aus dem Vista SDK in das entsprechende VC-Programmverzeichnis kopiert werden.

Ich hatte dies auch als Bug gemeldet und habe gehofft, dass dies auch im VS2005 SP1 für Vista noch behoben wird. Dem ist aber nicht so.
Die MT.EXE bleibt unverändert die Datei aus dem SP1 für VS2005. Und damit verbunden bleibt auch weiterhin das Versions Chaos. Wie schon berichtet haben beide Versionen aus dem Vista SDK und dem VS2005 SP1 die gleiche Versionsnummer. Wobei aber nur die Version aus dem Vista-SDK tut was man von ihr erwartet.

Skibo, Young, Johnson: Arbeiten mit Microsoft Visual Studio 2005

Der Titel, ist etwas irreführend. Man muss hier den Untertitel ernst nehmen, der klarer sagt um was es geht: „Mit Makros und individueller Konfiguration Visual Studio 2005 optimieren“. Leider steht dieser Untertitel nirgendwo klar lesbar.
Das muss dem Leser/Interessenten von Anfang an klar sein!
Es geht hier weniger um das Arbeiten mit Visual Studio, als viel mehr um die Möglichkeiten, das bestehende Visual Studio 2005 anzupassen, zu erweitern und durch Makros Und Add-Ins Arbeiten zu automatisieren..

Das Ganze schließt Makros, Add-Ins, Assistenten ein und wir wollen die Starter-Kits nicht vergessen, auf die mich erst dieses Buch hingewiesen hat. Ein großer Part des Buches behandelt von der Erstellung von Add-Ins und später ein weitere großer Teil die Anpassungsmöglichkeiten von Visual Studio durch Makros.
Die Beispiele sind hauptsächlich in C# und bei den Makros natürlich in Visual Basic geschrieben. Man erhält einen relativ guten Einblick in das Objektmodell, das dem Entwickler zur Verfügung steht um Visual Studio an die eigenen oft speziellen Bedürfnisse anzupassen.
Beim „Tieftauchen“ habe ich allerdings gemerkt, dass trotz großem Angebot, doch Manches nicht möglich ist. Das liegt allerdings nicht an dem Buch, sondern an dem was die Entwickler von Microsoft in Visual Studio hineingepackt haben.

Am Ende wird das Buch leider zu einer besseren und ausführlichen Beschreibung der Makro Schnittstellen. Hier wären praktische Beispiele besser als nur zu zeigen wie, was, wo angesprochen wird. Ich vermute, dass die Autoren selbst von den Möglichkeiten überwältigt waren und sich dadurch (leider) auf das Nötigste beschränkten.

Trotz Übersetzung ist der Text flüssig und gut zu lesen, wobei der erste Teil ganz klar besser ist als der letzte. Da ich normalerweise englische Originale bevorzuge muss ich den Übersetzern ein Lob aussprechen. Der Ton hätte sicherlich an manchen Stellen etwas weniger trocken sein können. Aber das lag sicherlich auch an der Vorlage. Ich bin in letzter Zeit etwas mehr Humor auch bei technischen Büchern von MS-Press gewöhnt.

Als C++ Entwickler möchte ich darauf hinweisen, dass viele der beschriebenen Features wie z.B. das Exportieren von Vorlagen in C++ nicht zur Verfügung steht. Auch die sehr interessante Funktion zum Erzeugen von „Community-Content“ und Starter-Kits bleibt leider nur C#, J# und VB Programmierern zugänglich. Ein C++ Programmierer wird also beim Lesen dieses Buches öfters eifersüchtig werden.

3 Sterne

http://www.amazon.de/Arbeiten-Microsoft-Visual-Studio-2005/dp/3866454007

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.

Der Application Verifier, mein neuer Freund

Durch die Vista Zertifizierung habe ich den Application Verifier von Microsoft als neuen guten Freund kennengelernt. Warum? Schauen wir uns mal den nachfolgenden Code an:

{
  CImageList il;
  il.Create(IDB_IMAGELIST,16,0,RGB(255,0,255));
  m_lcList1.SetImageList(&il,LVSIL_SMALL);
  m_lcList2.SetImageList(&il,LVSIL_SMALL);
  il.Detach();
}

Dieser Code ist natürlich fehlerhaft! Allerdings nicht ersichtlich auf den ersten Blick. Er erzeugt korrekt eine Image List. Setzt diese in ein List View 1 und auch in ein List View 2. Sofern LVS_SHAREIMAGELISTS nicht gesetzt wird die Image List beim Zerstören des List Views 1 freigegeben. Aber eben auch noch mal wenn List View 2 zerstört wird! Nicht gut.

Oder noch ein übles Beispiel:

{
  CImageList il;
  il.Create(IDB_IMAGELIST,16,0,RGB(255,0,255));
  m_lcList.SetImageList(&il,LVSIL_SMALL);
  ImageList_Destroy(il.m_hImageList);
}

Er erzeugt auch korrekt eine Image List. Setzt diese in ein List View und zerstört dann einmal die Image List über das Handle und anschließend noch einmal durch den Destruktor von CImageList. Und ein drittes Mal wird die Image List beim zerstören des List Views freigegeben. Übel übel!

Was passiert wenn dieser Code ausgeführt wird? Nichts…
Natürlich würde keiner so etwas programmieren 😉 . Aber man kann sich vorstellen, dass so etwas ablauftechnisch und programmiertechnisch schon mal passieren kann.
Alles ist scheinbar in Ordnung, obwohl hier eine Zeitbombe tickt.

Wie kommt man einem solchen Bug auf die Spur?
Der Titel dieses Beitrages gibt die Antwort: Der Application Verifier.

Man installiert den Verifier und fügt die Anwendung zu den Verifier Einstellungen hinzu. Man belässt es bei den Default Einstellungen und ergänzt am Besten noch unter Miscellaneous die Checkboxen für DangerousAPIs und DirtyStacks.

Sobald man nun den obigen Code im Kontext eines Debuggers ausführt bekommt man eine Exception! Wow… und wenn man einen entsprechenden Symbolserver hat und den Stacktrace betrachtet sieht man

comctl32.dll!CImageListBase::IsValid()+0x2a bytes
comctl32.dll!_HIMAGELIST_QueryInterface@12()+0x29 bytes
comctl32.dll!_ImageList_Destroy@4()+0x19 bytes

Das Ausgabefenster des Debuggers zeigt zusätzlich:

03D8F964 : Invalid address causing the exception.
75C273A8 : Code address executing the invalid access.
0012F08C : Exception record.
0012F0A8 : Context record.

Aus den Informationen kann man sich leicht denken was hier faul ist, oder sein könnte. Es empfiehlz sich die Anwendung auch im Release Mode mit Debug-Infos zu erzeugen. Das sollte sowieso Standard sein!

Es lohnt sich seine Applikation mal mit dem Application Verifier auszuführen, wenn man mal gerade nichts zu tun hat und man wundert sich dann, an welchen Stellen einem sein – so gut programmierter oder gut gemeinter – Code um die Ohren fliegt :mrgreen:

Für wen Qulitätssicherung kein Fremdwort ist, der kommt an diesem sehr nützlichen Tool nicht vorbei. Der Application Verifier und weitere brauchbare Infos findet sich hier auf der entsprechenden Produktseite von Microsoft:
http://www.microsoft.com/technet/prodtechnol/windows/appcompatibility/appverifier.mspx