Windows 7, PlaySound und die vermisste Prüfung auf Speicherlecks

Bei der Entwicklung von neuen Funktionen in einem Modul bekam ich irgendwann einen ASSERT. Diesen ASSERT hatte ich in einem Cache eingebaut. Der ASSERT sprang an, wenn bei Programmende der Cache nicht sauber aufgeräumt wurde. Also eigentlich in der Entwicklungsphase nichts ungewöhnliches. Irgendwo wurde also eine Funktion zum Aufräumen nicht aufgerufen.

Aber was mich in diesem Moment extrem stutzig machte, war, dass in der Debugausgabe meines VisualStudios keine Memory Leaks angezeigt wurden ❗ Das machte irritierte mich nun schon sehr. Erster Verdacht. Evtl. hat ja jemand die Leakprüfung für den Cache ausgeschaltet (siehe AfxEnableMemoryTracking). Aber eine kurze Prüfung ergab, dass dem nicht so ist.

Also den Test noch mal ausgeführt. Diesmal wieder der ASSERT und zusätzlich der Dump. „Oha“ dachte ich „Hier ist aber was richtig faul!“

Nachdem ich immer wieder andere Testszenarien verwendet habe, erschienen mal die Leaks und mal erschienen sie nicht. Und nach einiger Zeit kaum ich dahinter, dass immer wenn PlaySound in meiner Debug Version der Anwendung verwendet wurde, kein Leak-Check erfolgte. Wurde PlaySound nicht verwendet war alles gut und die Leaks wurden ausgegeben.

Jetzt ging es ans eingemachte. Schnell isolierte ich folgende DLLs die zusätzlich beim ersten PlaySound geladen wurden.

'xyz.exe': Loaded 'C:\Windows\SysWOW64\MMDevAPI.dll', Symbols loaded (source information stripped).
'xyz.exe': Loaded 'C:\Windows\SysWOW64\propsys.dll', Symbols loaded (source information stripped).
'xyz.exe': Loaded 'C:\Windows\SysWOW64\wdmaud.drv', Symbols loaded (source information stripped).
'xyz.exe': Loaded 'C:\Windows\SysWOW64\ksuser.dll', Symbols loaded (source information stripped).
'xyz.exe': Loaded 'C:\Windows\SysWOW64\avrt.dll', Symbols loaded (source information stripped).
'xyz.exe': Loaded 'C:\Windows\SysWOW64\setupapi.dll', Symbols loaded (source information stripped).
'xyz.exe': Loaded 'C:\Windows\SysWOW64\cfgmgr32.dll', Symbols loaded (source information stripped).
'xyz.exe': Loaded 'C:\Windows\SysWOW64\devobj.dll', Symbols loaded (source information stripped).
'xyz.exe': Loaded 'C:\Windows\SysWOW64\AudioSes.dll', Symbols loaded (source information stripped).
'xyz.exe': Loaded 'C:\Windows\SysWOW64\msacm32.drv', Symbols loaded (source information stripped).
'xyz.exe': Loaded 'C:\Windows\SysWOW64\msacm32.dll', Symbols loaded (source information stripped).
'xyz.exe': Loaded 'C:\Windows\SysWOW64\midimap.dll', Symbols loaded (source information stripped).

Und nach etwas weiter debuggen kam ich dahinter, dass der DllMain Code von setupapi.dll beim DETACH_PROCESS den Prozess sofort terminiert und nicht alle DLLs einen DETACH_PROCESS Aufruf erhalten. Aber der Code für die Leak-Detection der MFC liegt in der MFCx.DLL und wird durch eine statische Variable ausgelöst, die beim Entladen der MFCx.DLL dann zur Ausgabe der Speicherlecks führt. (Siehe PROCESS_LOCAL(_AFX_DEBUG_STATE, afxDebugState) und AfxDiagnosticInit).

Eine genauere Analyse des Stacktraces ergab folgendes Bild:

ntdll.dll!_ZwTerminateProcess@8()  + 0x5 bytes	
ntdll.dll!_RtlpWaitOnCriticalSection@8()  + 0x1d38f bytes	
ntdll.dll!_RtlEnterCriticalSection@4()  + 0x16a38 bytes	
setupapi.dll!_pSetupInitGlobalFlags@4()  + 0x1fc bytes	
setupapi.dll!_pSetupGetGlobalFlags@0()  + 0xe2 bytes	
setupapi.dll!_FlushWVTCache@0()  + 0x2f bytes	
setupapi.dll!_DestroyDeviceInfoSet@8()  + 0x3f bytes	
setupapi.dll!_SetupDiDestroyDeviceInfoList@4()  + 0x44 bytes	
MMDevAPI.dll!CDeviceEnumerator::FinalRelease()  + 0x78 bytes	
MMDevAPI.dll!ATL::CComObjectCached::~CComObjectCached()  + 0x3c bytes	
MMDevAPI.dll!ATL::CComObjectCached::`scalar deleting destructor'()  + 0xd bytes	
MMDevAPI.dll!ATL::CComObjectCached::Release()  + 0xf0c6 bytes	
MMDevAPI.dll!ATL::CComClassFactorySingleton::~CComClassFactorySingleton()  + 0x18 bytes	
MMDevAPI.dll!ATL::CComObjectNoLock<ATL::CComClassFactorySingleton >::`scalar deleting destructor'()  + 0x1a bytes	
MMDevAPI.dll!ATL::CComObjectNoLock<ATL::CComClassFactorySingleton >::Release()  + 0xe0e3 bytes	
MMDevAPI.dll!ATL::CAtlComModule::Term()  + 0x27 bytes	
MMDevAPI.dll!__CRT_INIT@12()  + 0x26e6 bytes	
MMDevAPI.dll!__CRT_INIT@12()  + 0x2588 bytes	
ntdll.dll!_LdrpCallInitRoutine@16()  + 0x14 bytes	
ntdll.dll!_LdrShutdownProcess@0()  + 0x141 bytes	
ntdll.dll!_RtlExitUserProcess@4()  + 0x74 bytes	
kernel32.dll!75eb7a0d() 	
msvcr100d.dll!__crtExitProcess(int status=0)  Line 709	C
msvcr100d.dll!doexit(int code=0, int quick=0, int retcaller=0)  Line 621 + 0x9 bytes	C
msvcr100d.dll!exit(int code=0)  Line 393 + 0xd bytes	C
xyz.exe!__tmainCRTStartup()  Line 568	C

Ursache war, dass MMDevAPI.DLL in seinem DllMain Code ausführt in der setupapi.dll, die aber bereits den DETACH_PROCESS abgearbeitet hat. Das grundsätzliche Problem wird hier in diesem Blog Artikel geschildert: http://blogs.msdn.com/b/oldnewthing/archive/2005/05/23/421024.aspx
Mit eigenen Worten: Die MMDevAPI.dll ruft Funktionen in einer DLL auf, die bereits alle Ihren Speicher und Ressourcen freigegeben hat. Und wie es hier im Code so aussieht, versucht die SetupAPI.DLL wieder Ressourcen zu akquirieren, die eben bereits schon freigegeben wurden, weil eine DLL sie erneut benutzt.

Die Folge ein „Crash“ in DllMain, die der Windows-Lader aber abfängt und sofort mit einer Terminierung des Prozesses ahndet. D.h. nun aber, dass nicht alle DLLs, die noch einen DllMain Aufruf mit DETACH_PROCESS erhalten müssten,  auch an die Reihe kommen.

Etwas weitere Recherche ergab, dass dieses Problem auch in den MSDN Foren bereits diskutiert wurde inkl. einer möglichen Lösung.
http://social.msdn.microsoft.com/Forums/vstudio/en-US/8cb1847d-3218-4610-9cb8-6905bd255ff5/no-dllprocessdetach-after-calling-playsound-on-windows-7-64bit

Die Lösung ist erstaunlich einfach: Wenn vor der Benutzung von PlaySound explizit die SetupAPI.DLL geladen wird, dann verläuft der Rest der Deinitialisierung vollkommen normal. SetupAPI.DLL wird nicht entladen, weil die DLL durch den LoadLibrary Aufruf die DLL im Speicher sperrt. MMDevAPI.DLL kann erfolgreich selbst aufräumen und der Code läuft nicht mehr ins Nirvana.

Hier handelt sich offensichtlich um einen Bug in Windows 7 (Windows 8 konnte ich dies bzgl. nicht testen).
Dieser Bug kann natürlich auch empfindlichere Probleme mit sich bringen, wenn Ressourcen betroffen sind, die nicht automatisch mit Prozessende freigegeben werden und wenn diese Ressourcen ausschließlich in der DllMain bei einem DETACH_PROCESS behandelt werden.

Inhalt des Papierkorbs wird nicht angezeigt…

Ich hatte immer wieder Probleme, dass mein Papierkorb leer war, obwohl ich gerade eine Datei dort hin verschoben war.

Es schien auch alles andere zu funktionieren, denn ich konnte auch Aktionen im Explorer rückgängig machen und Dateien wieder so aus dem Papierkorb herausholen. Nur anzeigen konnte ich den Inhalt des Papierkorbes nicht mehr.

Analyse der Festplatte ergab, aber, dass die Dateien vorhanden sind, CHKDSK keinen Fehler liefert… nichts problematisches eigentlich zu finden. Ich habe keine Ahnung wie oft ich den entsprechenden Ordner entfernt habe und hoffte, dass sich die Datenbank des Papierkorbes irgendwann fängt. Aber dem war nicht so 🙁
Ich löschte eine Datei, die kurz im Papierkorb erschein um sofort wieder zu verschwinden.

Irgendwann hat es mich so genervt, dass ich doch weiter auf die Suche gegangen bin und habe in einem Forum einen Hinweis für eine Richtung gefunden, die ich gar nicht auf der Rechnung hatte.
Die Ursache war ein Wibu Codemeter-Dongle. Dieser Dongle steckt immer in meiner Entwicklungsmaschine um bestimmte Szenarien testen zu können. Wir benutzen für größere Lizenzen die Wibu-Codemeter-Dongles seit einigen Jahren mit gutem Erfolg und wir sind auch sehr zufrieden mit der Qualität, sowohl von Hardware als auch Software und SDK.

Der eingesteckte Codemeter Dongle meldete sich als  lokaler Datenträger an. Dadurch wollte Windows auch scheinbar den Papierkorb auf diesem Laufwerk suchen und finden. Aber eigentlich ist das Ding nur eine RAM-Disk, die keinen permanenten Speicher bietet. Sobald der Dongle als lokale Festplatte angemeldet ist, funktioniert auch der Windows-Papierkorb nicht mehr.
Ein schneller Test zeigte, dass der Papierkorb nur bei nicht eingestecktem Dongle verfügbar war.
Weitere Recherche ergab, dass die eigentliche Ursache ein Bug in Windows 7/2008R2 ist. Netterweise ist sogar ein Hotfix für Windows-7 und Windows Server 2008R2 verfügbar (Links siehe unten), aber leider hat der seinen Weg nach Windows-Update nicht gefunden :(.

Aber es lässt sich auch ohne Installation des Hotfixes schnell mit der Codemeter Runtime schnell ändern.
Die Codemeter Runtime gibt folgende Auskunft zu dem Dongle.

C:\...\CodeMeter\Runtime\bin>cmu32  -s1-2345678 --show-config-disk
cmu32 - CodeMeter Universal Support Tool.
Version 5.00b of 2013-May-14 (Build 1067) for Win32
Copyright (C) 2007-2013 by WIBU-SYSTEMS AG. All rights reserved.

- CmStick with Serial Number 1-2345678 and version 1.16
  Version:            1.16
  Flash Size:         no real flash available
  Virtual Drive:      D:
  Configuration:      LocalDisk with ActivePartition
  File System:        FAT32
  Boot-Code:          Int18 Boot Code
  Mdfa:               0x539

Über die entsprechenden Commandline Utilities der Wibu-Runtime kann man das aber umkonfigurieren. der Befehl lautet:
cmu32 -s1-2345678 –set-config-disk RemovableDisk
Dazu muss mit dem Parameter -s die Seriennummer des betroffenen Dongles angegeben werden.

C:\...\CodeMeter\Runtime\bin>cmu32 -s1-2345678 --set-config-disk RemovableDisk
cmu32 - CodeMeter Universal Support Tool.
Version 5.00b of 2013-May-14 (Build 1067) for Win32
Copyright (C) 2007-2013 by WIBU-SYSTEMS AG. All rights reserved.

  Communication mode changed successfully.

  Please replug your CmDongle to apply the changes.

Also Dongle wie angezeigt einmal rausziehen und wieder anstecken.
Und sie da 🙂 mein Papierkorb ist sofort wieder da und sichtbar. „Gelöschte Dateien“ erscheinen wir gewohnt.
Die Kontrolle zeigt nun einen Removeable-Device, wie das bei anderen USB-Sticks auch der Fall wäre.

C:\...\CodeMeter\Runtime\bin>cmu32  -s1-2345678 --show-config-disk
cmu32 - CodeMeter Universal Support Tool.
Version 5.00b of 2013-May-14 (Build 1067) for Win32
Copyright (C) 2007-2013 by WIBU-SYSTEMS AG. All rights reserved.

- CmStick with Serial Number 1-2345678 and version 1.16
  Version:            1.16
  Flash Size:         no real flash available
  Virtual Drive:      D:
  Configuration:      RemovableDisk with ActivePartition
  File System:        FAT32
  Boot-Code:          Int18 Boot Code
  Mdfa:               0x539

Weitere Hinweise zu dem Vorgehen finden sich auf den Microsoft und Wibu-Supportseiten hier:
http://support.microsoft.com/kb/2677246/en-us
http://www.wibu.com/de/faq-codemeter/question/single/warum-ist-mein-papierkorb-leer-wenn-ein-cmstick-angesteckt-ist-138.html
http://www.wibu.com/de/faq-codemeter/question/single/wie-kann-man-einen-cmstick-ohne-speicher-umkonfigurieren-145.html

Patchday vom 14.06.2011 behebt die Probleme der VisualStudio 2005/2008 Servicepacks vom 13.04.2011

Die nachfolgenden 4 Sicherheitsupdates wurden am 14.06.2011 von Microsoft herausgegeben:

Sicherheitsupdate für Microsoft Visual C++ 2005 Service Pack 1 Redistributable Package (KB2538242)
Sicherheitsupdate für Microsoft Visual Studio 2005 Service Pack 1 (KB2538218)
Sicherheitsupdate für Microsoft Visual C++ 2008 Service Pack 1 Redistributable Package (KB2538243)
Sicherheitsupdate für Microsoft Visual Studio 2008 Service Pack 1 (KB2538241)

Eigentlich beheben Sie nur das, was am Patchday vom 13.04.2011 hätte behoben werden sollen. Ich habe dazu ja mehrere Artikel geschrieben (1, 2, 3, 4, 5). Nach Vergleich der entsprechenden Sourcen machen Sie Änderungen jetzt das, was sie sollen.

Auch das Problem, dass mit dem letzten Sicherheitsupdate, die Größe der Executables immens angewachsen ist wurde behoben.
Schade, dass dieses Problem in VS-2010 weiter bestehen bleibt.
Insbesondere funktioniert diese neue Runtime für VS-2005 auch auf Windows 2000. Eines der Hauptprobleme vom Patchday im April.

Anzumerken wäre hier eigentlich nur noch die neuen Versionen der Runtime die mit diesem Sicherheitsupdate veröffentlicht werden, d.h. auch, dass man nun die neuen Runtimes auch in sein Setup einbauen sollte, bzw. dass man die neueste passende VCRedist_x86 nun auch mit ausliefern muss. D.h. auch, dass sich die entsprechenden Manifeste wieder ändern, sofern man diese manuell angepasst hat:

  • Die neuen Runtimedateien von VS-2005 haben die Versionsnummer 8.0.50727.6195
    Die neue Runtime für VS-2005 gibt es hier zum Download.
  • Die neuen Runtimedateien von VS-2008 haben die Versionsnummer 9.0.30729.6161
    Die neue Runtime für VS-2008 gibt es hier zum Download.

Weitere und vollständige Infos zu diesem Sicherheitsupdate finden sich im VC-Blog:
http://blogs.msdn.com/b/vcblog/archive/2011/06/17/10175518.aspx

Ich jetzt kann nur allen Entwicklern raten diese neuen Sicherheitsupdates auch zu installieren und zu nutzen!

BUG: Black Patchday for all OS from XP and later 3. – MFC 8.0 (VC-2005) or MFC 9.0 (VC-2008) linked dynamically to the MFC may not find the MFC language DLLs after installation of the security packs dated April 12th 2011

This is the English translation of the already published German article:
BUG: Schwarzer Patchday für alle OS XP und später 3. – MFC 8.0 (VC-2005) oder MFC 9.0 (VC-2008) die dynamisch gelinkt wurden finden die MFC Sprach-DLLs evtl. nicht mehr nach Installation der Sicherheitspatches vom 12.04.2011

Affected are:

  • All programs created with MFC 8.0 and MFC 9.0 that link dynamically to the MFC DLLs .
  • All operating systems from Windows XP and later. 32bit as 64bit
  • Al programs that do not use an application local installation (program directory, see note at the bottom of the article). So all programs that use and depend on WinSxS and VCRedist_x86.exe ( VCRedist_x64.exe).
  • All programs that are localized and use the MFC90xxx.DLL or. MFC80xxx.DLL language-DLLs and the OS system language is not set to English.

It is affected due to the security fixes offered April 12th, 2011:

For VS-2005 SP1 http://support.microsoft.com/kb/2465367 and http://support.microsoft.com/kb/2467175
For VS-2008 SP1 http://support.microsoft.com/kb/2465361 and http://support.microsoft.com/kb/2467174

Failure description:

The MFC language DLLs (satellite DLLs) are not loaded any longer. Parts of the application appear in English and not the selected language from the OS.

Background:

To prevent loading of wrong satellite DLLs (Binary Planting), an internal function in appcore.cpp named _AfxLoadLangDLL was changed. It checks if an activation context is active or not and if the DLLs should be loaded using this context. If there is an activation context active it is safe to load the satellite DLLs(MFCDEUxxx.DLL etc.) without defining a full path. If no activation context is active the path of the current application is used to load and find the satellite DLLs. The DLLs are loaded with a call to LoadLibrary.

The code used looks like this (empty lines removed):

...
TCHAR *pszFilename = ::PathFindFileName(szLangDLL);
ACTCTX_SECTION_KEYED_DATA data;
if (FindActCtxSectionString(
    FIND_ACTCTX_SECTION_KEY_RETURN_HACTCTX,
    NULL,
    ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION,
    pszFilename,
    &data) )
{
    // Load using the dll name only...
    hInstance = ::LoadLibraryEx(pszFilename, NULL, 0);
}
else
{
    // Load using the full path...
    hInstance = ::LoadLibraryEx(szLangDLL, NULL, 0);
}
...

The code looks OK.  And it is conform to the documentation of FindActCtxSectionString where the last parameter is defined as __out.

BOOL FindActCtxSectionString(
  __in   DWORD dwFlags,
  __in   const GUID *lpExtensionGuid,
  __in   ULONG ulSectionId,
  __in   LPCTSTR lpStringToFind,
  __out  PACTCTX_SECTION_KEYED_DATA ReturnedData
);

But the documentation of ACTCTX_SECTION_KEYED_DATA tells a different story:

Callers should initialize the ACTCTX_SECTION_KEYED_DATA structure as such:
„ACTCTX_SECTION_KEYED_DATA askd = { sizeof(askd) };“
which initializes all members to zero/null except the size field which is set correctly.

(BTW: In my eyes a documentation failure)

So what we see is that the code misses this: data.cbSize isn’t initialized
Now we have 3 possible scenarios what can happen with a  randomly initialized data.cbSize field:

  1. data.cbSize is larger than sizeof(ACTCTX_SECTION_KEYED_DATA):
    In this case the activation context is correctly detected. The program executes normal.  With an activation context no full path is needed. The MFC90xxx.DLL will be loaded from the WinSxS (Side by Side) or found over the common search path.
  2. data.cbSize is less than  sizeof(ACTCTX_SECTION_KEYED_DATA):
    In this case FindActCtxSectionString returns with an error. The DLL is now loaded with a full path name constructed from the application directory to prevent Binary Planting. Butthe problemis that with a normal installation the searched files are all in WinSxS, and the application directory has no such data. The DLL is not loaded.
    If the application local assemblies are used and placed in sub directories they aren’t found either.
  3. A future problem.
    If an OS will use a larger ACTCTX_SECTION_KEYED_DATA and data.cbSize has a greater value than the corresponding sizeof(…):
    We have a buffer-overrun!

I always recommend to use private and application local assemblies for the CRT and MFC DLLs. And to install all this files local to the application.
Years ago I wrote an article for this scenario that was published on CodeProject and a hotfix for VS-2008 is also available
Create projects easily with private MFC, ATL and CRT assemblies
Hotfix für UseMSPrivateAssemblies.h und VC-2008

What to do?

Uninstall all of the mentioned security fixes with the specified article IDs.
Runtime-2005: KB2467175, Runtime-2008: KB2467174
VS-2007 SP1: KB2465367, VS-2008 SP1: KB2465361).

Further notes:

The affected C/C++ Runtimes of Visual Studio have the following version numbers:
– VC-2005 8.0.50727.5592 (KB2467175)
– VC-2008 9.0.30729.5570 (KB2467174)

My comment to tis issue:
It was easier to live with the DLL-hell. 🙁

Many thanks to my Co-MVP Mike Ryan who helped me to discover this problems with the latest security patches:!:

What Do I mean with „application local“?
Some people ship the MFC files in the application directory. In such a case this DLLs are not loaded if a newer version can be found in the WinSxS directory. This is not application local for me!
So if the manifest file in the program directory still have a publicKey entry, the local files will be used  in case of the here described bug. Even if the activation context was not detected, so the local files are a kind of fallback and help prevent get around the problem.
My articles describe how to make your application really application local in removingthe publicKey tokens from the manifest files. Such programs will never fail on such broken security patches. (Just read my article at Codeproject). (Thanks for Co-MVP David Ching who asked me for a clarification)

BUG: Schwarzer Patchday für alle OS XP und später 3. – MFC 8.0 (VC-2005) oder MFC 9.0 (VC-2008) die dynamisch gelinkt wurden finden die MFC Sprach-DLLs evtl. nicht mehr nach Installation der Sicherheitspatches vom 12.04.2011

Betroffen sind:

  • Alle Programme die mit MFC 8.0 oder MFC 9.0 erzeugt wurden und dynamisch an die MFC DLLs gelinkt sind.
  • Alle Betriebssysteme ab Windows XP aufwärts. 32bit wie 64bit
  • Alle Programme, die nicht die MFC und CRT DLLs applikationsnah (d.h. im Programmverzeichnis, siehe dazu auch die Fußnote in meinem Artikel) installiert haben. Also alle Programme die WinSxS benutzen und die VCRedist_x86.exe ( VCRedist_x64.exe)  mit ausliefern.
  • Alle Programme, die lokalisiert sind und die MFC90xxx.DLL bzw. MFC80xxx.DLL Sprach-DLLs verwenden und das OS nicht auf Englisch eingestellt ist

Betrifft die folgenden Fixes vom 12.04.2011:

Für VS-2005 SP1 http://support.microsoft.com/kb/2465367 und http://support.microsoft.com/kb/2467175
Für VS-2008 SP1 http://support.microsoft.com/kb/2465361 und http://support.microsoft.com/kb/2467174

Effekt:

Die MFC Statelite DLLs werden nicht geladen. Teile der Anwendung erscheinen in englischer Sprache.

Hintergrund:

Um das Laden von falschen Satelite DLLs zu verhindern (Binary Planting), wurde intern in appcore.cpp in der Funktion _AfxLoadLangDLL geprüft, ob die DLLs in aus einem Activation Context geladen werden oder nicht. Sollte ein Activation Context vorhanden sein, dann kann man gefahrlos die Sprach DLLs (MFCDEUxxx.DLL etc.) ohne Pfadnamen laden. Ist kein Activation Context vorhanden wird der Pfad der Anwendung verwendet und LoadLibrary mit vollem Pfadnamen durchgeführt.

Der Code der dazu verwendet wird sieht so aus (Leerzeilen entfernt):

...
TCHAR *pszFilename = ::PathFindFileName(szLangDLL);
ACTCTX_SECTION_KEYED_DATA data;
if (FindActCtxSectionString(
    FIND_ACTCTX_SECTION_KEY_RETURN_HACTCTX,
    NULL,
    ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION,
    pszFilename,
    &data) )
{
    // Load using the dll name only...
    hInstance = ::LoadLibraryEx(pszFilename, NULL, 0);
}
else
{
    // Load using the full path...
    hInstance = ::LoadLibraryEx(szLangDLL, NULL, 0);
}
...

Eigentlich sieht der Code prima aus. Und er verträgt sich auch mit der Doku von FindActCtxSectionString dort wird der letzte Parameter als __out definiert.

BOOL FindActCtxSectionString(
  __in   DWORD dwFlags,
  __in   const GUID *lpExtensionGuid,
  __in   ULONG ulSectionId,
  __in   LPCTSTR lpStringToFind,
  __out  PACTCTX_SECTION_KEYED_DATA ReturnedData
);

Aber die Doku zu ACTCTX_SECTION_KEYED_DATA sagt was anderes:

Callers should initialize the ACTCTX_SECTION_KEYED_DATA structure as such:
„ACTCTX_SECTION_KEYED_DATA askd = { sizeof(askd) };“
which initializes all members to zero/null except the size field which is set correctly.

(BTW: Auch ein krasser Doku-Bug in meinen Augen)

Und jetzt sieht man was dem Code fehlt: data.cbSize wird nicht gesetzt
Daraus ergeben sich nun drei Varianten, da data.cbSize nun zufälligen (nicht initialisierten) Inhalt hat:

  1. data.cbSize ist größer als  sizeof(ACTCTX_SECTION_KEYED_DATA):
    In diesem Fall wird korrekt ermittelt ob ein Activation Context vorhanden ist. Das Programm läuft normal. Mit Activation Context ist kein voller Pfadname nötig. Die MFC90xxx.DLL wird evtl. aus dem WinSxS (Side by Side) geladen, oder in einem der Suchpfade gefunden.
  2. data.cbSize ist kleiner als  sizeof(ACTCTX_SECTION_KEYED_DATA):
    In diesem Fall liefert FindActCtxSectionString einen Fehler und nun wird es spannend. Die DLL wird nun versucht mit dem vollen Pfadnamen zu laden um Binary Planting zu verhindern. Das Problem ist aber dass bei korrekter Installation im WinSxS, dass im Applikationsverzeichnis keine dieser Daten liegen. Die DLL wird nicht gefunden.
    Sollten die private applikationsnahe Assemblies in einem Unterverzeichnis installiert sein, werden diese auch nicht gefunden.
  3. Für die Zukunft.
    Ein zukünftiges OS vergrößert ACTCTX_SECTION_KEYED_DATA und data.cbSize hat zufälligen Inhalt und ist größer als sizeof(…):
    Ein Buffer-Overrun!

Ich empfehle nicht ohne Grund seit VS-2005 private Assemblies zu verwenden, und die MFC Dateien in das Anwendungsverzeichnis zu kopieren. Dazu habe ich auf Code-Projekt einen entsprechenden Artikel geschrieben und ein Hotfix für VS-2008 existiert auch ❗
Create projects easily with private MFC, ATL and CRT assemblies
Hotfix für UseMSPrivateAssemblies.h und VC-2008

Was ist zu tun?

Deinstallation aller hier erwähnten Sicherheitspatches mit den entsprechenden Arikelnummern:
Runtime-2005: KB2467175, Runtime-2008: KB2467174
VS-2007 SP1: KB2465367, VS-2008 SP1: KB2465361).

Weitere Anmerkungen:

Die betroffenen C/C++ Runtimes des Visual Studio haben die folgenden Versionsnummern
– VC-2005 8.0.50727.5592 (KB2467175)
– VC-2008 9.0.30729.5570 (KB2467174)

Mein Kommentar dazu:
Das Leben in der DLL-Hölle war fast angenehmer als das hier. Ohne Worte 🙁

Herzlichen Dank auch an meinem Mit-MVP Mike Ryan, der mit mir zusammen auf diese gesamte Problematik gestoßen ist ❗

Was meine ich mit „application local“?
Einige Entwickler installieren die MFC runtme Dateien im Applikationsverzeichnis. In diesem Fall werden diese DLLs nicht verwendet wenn eine neuere Version der DLLs im WinSxS Verzeichnis liegen. Das ist für mich keine applikationsnahe Instalation! Diese Manifeste im Programmverzeichnis haben immer noch einen publicKey Eintrag. Aber durch die Existenz der lokalen Dateien wird dieses hier beschriebene Problem umgangen, weil die lokalen Dateien eine Art Fallback bilden.
Meine Artikel beschriben wie man eine Anwendung wirklich applikationslokal macht und damit unabhängig von solchen „kaputten“ Security Patches. Dazu muss der publicKey Token aus den Manifesten entfernt werden. (Lesen Sie meinen Artikel aufCodeproject).
(Danke an Co-MVP David Ching der mich um Kläurung gebeten hat)

Massive Probleme mit ADO auf Windows 7 SP1

Windows 7 SP1 scheint einige Probleme in Bezug auf ADO zu haben. So jedenfalls hat dies Mike Ryan gemeldet.
Hier die beiden Threads in den MSDN Foren, die von den Problemen berichten:

  1. Massive Thread-Handle Leaks bei asnychronen Operationen:
    ADO, adAsyncExecute and Windows 7 SP1 handles leaking
    http://social.msdn.microsoft.com/Forums/en/sqldataaccess/thread/68e23681-f6b5-4ed5-b963-e63e34eeac2f
    Dieser Bug wurde bereits von Microsoft bestätigt.
    Wer einen Fix braucht muss sich an den Microsoft Support wenden.
  2. Das zweite Problem betrifft die COM Registrierung für Applikationen, die auf Windows 7 SP1 Maschinen gebaut werden.
    Breaking change in MDAC ADODB COM components in Windows 7 Service Pack 1
    http://social.msdn.microsoft.com/Forums/en-US/windowsgeneraldevelopmentissues/thread/3a4ce946-effa-4f77-98a6-34f11c6b5a13
    Siehe auch:
    http://blogs.technet.com/b/asiasupp/archive/2011/03/14/changes-in-mdac-adodb-com-components-in-windows-7-service-pack-1.aspx

PS: Ich bin ziemlich froh, dass ich direkt auf OLD-DB arbeite… 😉

Nach Installation von Windows 7 SP1 wird immer wieder die Installation für den USB Treiber der Microsoft IntelliType Tastatur durchgeführt

Ich hatte nach der Installation von Windows 7 auf einem meiner Rechner ein eigentümliches Problem:

Auf diesem Rechner wie auf auf zwei anderen meiner Rechner benutze ich eine Microsoft Keyboard-Maus Kombination. Entsprechend ist auf den Rechnern auch Microsoft IntelliType und IntelliPoint installiert.

Alles lief beim Update von Windows 7 glatt. Aber nach dem Neustart des Systems bekam ich einen UAC Prompt und es wurde gemeldet, dass ein neuer USB Treiber für die Microsoft Tastatur installiert werden müsste. Also OK.

Aber beim nächsten Neustart wieder die gleiche Meldung, ein detailierter Blick auf den Treiber der angefordert wurde ergab folgende Info:

rundll32.exe C:\Windows\system32\newdev.dll,pDiDeviceInstallAction \\.\pipe\PNP_Device_Install_Pipe_1.{541e93b9-2da1-4d96-91e1-68472a06f5a9} „usb\vid_045e&pid_00e3&mi_00\7&13cc06b7&0&0000“

Neuinstallation/Reparaturinstalltion der IntelliType Software nützte nichts.

Erst als ich die Software komplett entfert hatte und dann eine Neuinstalltion durchgeführt habe verschwand diese lästige Meldung.

Nach der Installation des SP1 von Wndows 7 / Windows Server 2008 R2 lässt sich wieder einiges an Speichplatz freigeben

Auch durch die Installation des SP1 für Windows 7 und Windows Server 2008 R2 kann man durch das Aufrufen der Datenträger Bereinigung 0,8GB bis 2,5GB an Speicher freigeben.

Entfernen der SP1 Backupdateien für Window 7

Dazu startet man einfach die Datenträgerbereinigung (CLEANMGR.EXE) als Administrator. Oder man startet die Datenträgerbereinigung normal und klickt dann auf den Schalter Systemdateien bereinigen. Nur wenn man das Programm als Admin startet erhält man auch Zugriff auf die Backupdateien des SP1.
Bei mir wurden hier zwischen 0,5GB und 0,8GB freigegeben.

Entfernen der SP1 Backupdateien für Windows Server 2008 R2

Das Löschen der Backup Dateien des SP1 für einen Windows Server 2008 R2 ist hier beschrieben:
http://technet.microsoft.com/en-us/library/ff817650(WS.10).aspx (ziemlich weit unten unter To remove service pack backup files).

Der Befehl zum Entfernen der Backupdateien für ein durchgeführtes Online-Update lautet entsprechend:
DISM.exe /online /Cleanup-Image /spsuperseded

bzw. bei Verwendung eines Offline-Images:
DISM.exe /Image:<path_to_offline_image> /Cleanup-Image /spsuperseded

Auf meinen Servern konnte ich durch diese Operation im Schnitt ca. 2,4GB freigeben.

Nachtrag (09.03.2011):
Es sollte klar sein, dass man die Installation des SP1 nach Löschen der Backup-Dateien nicht mehr rückgängig machen kann.

C:\WINDOWS\TEMP und die Tücken von Programme der Kategorie „es war einmal“

Es waren einmal die Zeiten in denen man in C:\WINDOWS\TEMP einfach mal eine Datei anlegen konnte und jeder darauf zugreifen konnte.
Seit Windows Vista hat sich ja einiges getan was Sicherheit betrifft, besonders auch die Rechtevergabe auf Dateien, die im C:\Windows\Temp Ordner angelegt werden.

Ein Programm  im Rentenalter (es ist gerade mal so um die 16 Jahre alt 😉 geschätzt) erzeugte in C:\WINDOWS\TEMP eine Datei, mit der bestimmte Zugriffe abgesichert wurden. Darunter auch wenn mehrere Instanzen des Programms auf einem Rechner liefen. Das funktionierte prima. Die Datei wurde unter einem festen Namen angelegt und nicht entfernt, nachdem das Programm beendet wurde.

Seit Vista gibt es aber nun ein kleines Problem.:
Seit Windows Vista darf in C:\WINDOWS\TEMP immer noch jeder User Dateien erzeugen. Auf diese Dateien hat er auch vollen Zugriff. Aber auf diese Dateien hat kein anderer Nutzer mehr Zugriff… 😮 … und selbst ein Admin muss erst hier erst den Besitz übernehmen, wenn er die Datei nicht erzeugt hat.

Und nun hat diese kleine alte Programm den folgenden Effekt:

  • Benutzer A meldet sich an.
  • Benutzer A startet das Programm Er arbeitet damit und die temporäre Datei wird in C:\WINDOWS\TEMP angelegt.
  • Benutzer A meldet sich ab und beendet das Programm.
  • Benutzer B meldet sich an.
  • Benutzer B startet das Programm und … bekommt eine Fehlermeldung mit einem „Access denied!“.

Mit den neuen Rechten, die auf dem C:\WINDOWS\TEMP Verzeichnis liegen, kann der zweite Benutzer auf diese Datei nicht mehr zugreifen auf die eben nur der Erzeuger Zugriff hat, und der ist eben Benutzer A.

PS: Es Frage mich keiner warum C:\WINDOWS\TEMP aus GetWindowsDir und angehängtem Text TEMP zusammengesetzt wurde. Vermutlich um zu umgehen, dass ein privates temporäres Verzeichnis benutzt wird. Tja und CSIDL_APPDATA war dem damaligen Entwickler (evtl. noch unter Windows 3.1) nicht bekannt.

Warum unter Vista und Windows 7 C:\Programme nicht genau dasselbe ist wie C:\Program Files

Jeder kennt von Windows Vista und Windows 7 die Junctions „C:\Programme“ oder „C:\Dokumente und Einstellungen“.
Diese Verweise erlauben es auch älteren Programmen die evtl. hardcodierte Verzeichnisnamen haben oder auch Programmen, die die alten Verzeichnisstrukturen von Windows 2000 und XP nutzen korrekt zu arbeiten.

Jetzt könnte man meinen, dass es vollkommen egal ist ob man nun „C:\Programme“ oder „C:\Program Files“ benutzt.
Aber das ist es nicht ❗ Es gibt ein paar ganz feine Unterschiede.

Ein simpler DIR auf der Befehlszeile

C:\>dir C:\Programme
 Datenträger in Laufwerk C: ist C-LAPTOP
 Volumeseriennummer: D483-432C

 Verzeichnis von C:\Programme

Datei nicht gefunden

macht das Problem offensichtlich!
Und was geht hier ab?

Die Antwort liefert ICACLS für die Junction:

C:\>icacls C:\Programme
C:\Programme Jeder:(DENY)(S,RD)
             Jeder:(RX)
             NT-AUTORITÄT\SYSTEM:(F)
             ORDEFINIERT\Administratoren:(F)

Dagegen zeigt ICACLS für das Verzeichnis selbst

C:\>icacls "C:\Program Files"
C:\Program Files NT SERVICE\TrustedInstaller:(F)
                 NT SERVICE\TrustedInstaller:(CI)(IO)(F)
                 NT-AUTORITÄT\SYSTEM:(M)
                 NT-AUTORITÄT\SYSTEM:(OI)(CI)(IO)(F)
                 VORDEFINIERT\Administratoren:(M)
                 VORDEFINIERT\Administratoren:(OI)(CI)(IO)(F)
                 VORDEFINIERT\Benutzer:(RX)
                 VORDEFINIERT\Benutzer:(OI)(CI)(IO)(GR,GE)
                 ERSTELLER-BESITZER:(OI)(CI)(IO)(F)

Während man auf „C:\Program Files“ die Rechte sieht, die man auch erwartet, liegt auf der Junction selbst eine Einschränkung:
Die Berechtigung zum „Ordner Auflisten“ wird verweigert für „Jeder“ ❗
Jeder der neu eingeführten Junctions hat diese Einschränkung.

Meines Erachtens wurde dies gemacht um beim Durchsuchen von Verzeichnisstrukturen nicht unendliche Rekursionen zu erzeugen oder Verzeichnisse oder Dateien doppelt aufzuführen.
Eine Verzeichnis Rekursion ergäbe sich zum Beispiel durch die Junction Anwendungsdaten im Verzeichnis C:\Users\Username\AppData\Local, der exakt wieder auf das Verzeichnis C:\Users\Username\AppData\Local verweist.