VS-2015 RC ist verfügbar – ein kurzer Einblick aus der C++ Sicht

Seit den letzten Tagen des April ist nun auch der Release Candidate für VS_2015 verfügbar.
http://www.visualstudio.com/en-us/news/vs2015-vs.aspx

Die Liste ist lang. Was sich für C++ Neues ergibt dagegen doch recht übersichtlich. 😉

Hier ein paar Punkte, die ein erster Test und Streifzug ergab:

  • Man sollte in jedem Fall die angepasste Installation verwenden. Ansonsten wird der MFC Sourcecode nicht mit installiert (siehe auch Kommentar von Michael Külshammer)
  • „Fast“ alle Projekte ließen sich fehlerfrei kompilieren. Es gab zwar einen Haufen neuer Warnungen aber, diese haben eher mit dem Stil des Programmierens zu tun. Speziell sind es C4456, C4457, C4458 (Declaration off… hides previous …  declaration). Im Klartext. Ich verwende immer mal wieder eine Variable it oder i gerne, die auch im äußeren Scope vorhanden ist.
    Für diese neuen Warnings gibt es allerdings bisher keine Dokumentation.
  • Und eben die typischen Fallen in denen man eben thirdparty Libs (z.B. OpenSSL) eben neu erzeugen muss.
  • Leider benötige ich für ein spezielles Update Programm noch die MBCS Variante der MFC. Diese ist zwar über das Netz als Link bei MS vorhanden, führt aber zu einer 404 Sackgasse. (Dieses Programm benutzt extrem alten Code, bei denen noch niemand an char/wchar_t bzgl. der Windows API und anderes dachte und Neuschreiben hatte ich bisher vermieden. 🙂 )
  • Nervig war eine „neue“ Compilermeldungen C1041, aber diese ist nirgendwo beschrieben (wie auch andere nicht). Online jedenfalls nicht. Ich bekam den Hinweis, dass eine PDB Datei nicht geschrieben werden kann und ich bitte die Option /FS verwenden soll. Die Ursache ist mir unklar, weil bei späteren Kompilierungsversuchen das Ganze durchlief.
  • Etwas länger musste ich für den folgenden Fehler suchen:
     (odbccp32.lib(dllload.obj) : error LNK2019: unresolved external symbol _vsnwprintf_s referenced in function StringCchPrintfW).
    Hier scheint noch etwas mit dem SDK auch in der Release Version nicht zu passen. Auf connect.microsoft.com fand ich einen entsprechenden Eintrag um diesen Fehler zu umgehen. Scheinbar ist das neue „korrigierte“ SDK, dass in der Lösung versprochen wird im RC noch nicht enthalten.
  • Vieles in der UI hat sich geändert. So werden zum Beispiel die Breakpoint mit einem eigenen Kontexttoolbar angezeigt und benutzt man diesen dann werden die Einstellungen nicht in einem Dialog angepasst, sondern innerhalb des Sourcecode Fensters wird am unteren Rand ein Box für die Eigenschaften geöffnet. Das ist definitiv gewöhnungsbedürftig. Und nur mit der Maus zu bedienen.
  • Im MFC Sourcecode findet sich nur eine neue Header-Datei afxlayout.h. In dieser Headerdatei findet sich die Deklaration für die Klasse CMFCDynamicLayout. Diese Klasse wird direkt in CWnd als Zeiger verwendet und erlaubt die Neupositionierung von Kindfenstern damit für alle Fenstertypen. D.h. nicht nur Dialoge. D.h. man kann in jedem MFC-Fenster EnableDynamicLayout ausführen und entsprechend die Kindfenster neu anordnen lasen wie man es selber definiert.
    Die Implementierung schließt scheinbar die Nutzung des Ressourceneditors ein, denn die Einstellungen können aus dem Ressourcentyp RT_DIALOG_LAYOUT ausgelesen werden. In den Fenstereigenschaften eines Controls findet sich nun der Abschnitt Dynamic Layout in dem man Position und Größe in Prozent zur Gesamtänderung angeben kann.
  • Wer die Header genauer untersucht findet klitze kleine Änderungen Hier und Da wie z.B. die Unterstützung von runden Ecken bei Tooltipps.
  • In der ATL konnte ich bisher keine Erweiterungen und gravierende Änderungen finden.
  • Die meisten Änderungen im ATL und MFC sind Änderungen im Trace Code. Statt „%s“ wird nun konsequent „%Ts“ als Formatierungszeichen verwendet. Angekündigt war eine grundsätzliche Änderung bzgl. „Wide String Format Specifiers“ allerdings wurde diese bereits im April zurückgezogen (siehe hier).
  • Vermisst habe ich einige CRT Source Dateien, die normalerweise im VC\CRT\SRC Order liegen. Der Ordner war in meiner Installation leer und enthielt nur weitere Ordner.
    Mir war es auch nicht möglich beim Testen in den CRT Code zu steppen. Zumindest bei mir wurden keine passenden Source Dateien gefunden.
  • Mit den CRT-DLLs werde ich mich ab morgen mal befassen. Auch hier hat Michael Külshammer gerade eben einen Beitrag in meinem Blog geschrieben.

Summa summarum nur minimale Anpassungen in meinen Projekten für den Umstieg von VS-2013 auf VS-2015, darüber hinaus so gut wie keine (erkennbaren) Änderungen in der CRT/ALT und MFC. Was es an neuen Compiler Features gibt findet man in den Blogs ausführlicher beschrieben als ich das kann. Zudem halte ich mich nicht für den großen C++ Sprachen-und-Standard-Guru.

 

Advanced Developers Conference zu native C++ in München Erding vom 05.-06. Mai

Die Konferenz wird unter dem Thema Doing C++ The Right Way stehen. Bernd Marquardt wird als Content Manager mit für den Inhalt verantwortlich sein.

Da die Agenda noch nicht sehr voll ist, bin ich auf die endgültige Themenauswahl doch gespannt. C++11 ist ja fast schon ein alter Hut und Parallelisierung wird auch schon etwas langweilig. 😉 Es stehen zumindest altbekannte Namen auf der Rednerliste.

Veranstalter ist die ppedv AG (hinter der Johannes Preishuber steht). Ich habe die Konferenzen der letzten Jahre, an denen ich teilgenommen habe, immer in guter Erinnerung. Sowohl in der Auswahl der Themen und Redner, als auch in der exzellenten Durchführung, mit immer sehr schon gewählten Konferenzorten und Hotels. Letztes Jahr hätten es ein paar mehr Leute sein können, aber das zeitgleiche Champions-League Spiel in München hat doch vielen einen Strich durch die Rechnung gemacht, weil es keine freien Hotelzimmer mehr gab.

Ich denke die Konferenz lohnt sich sicherlich.
Und ganz gespannt bin ich persönlich auf die Abendveranstaltung am Mittwoch, denn hier wird auf den ADC-Konferenzen den Teilnehmern immer etwas wirklich außergewöhnliches geboten und auch diesmal wird wieder zu etwas „Speziellem“ eingeladen. Letztes Jahr das Essen in der Flugwerft Schleißheim war schon etwas besonderes.

Ich freue mich auch dieses Jahr wieder dabei sein zu können; alte Kontakte beleben zu können, neue Entwickler kennen zu lernen und Neues aus der C++ Welt zu hören.

ADC

 

Wenn man nicht genau aufpasst mit Overlapped I/O und CancelIo vergisst

Overlapped I/O ist eine Standardtechnik, die ja jeder kennt, der mit Datenquellen arbeitet, die „irgendwann“ mal Informationen liefert, man aber in der Zeit evtl. noch anderes zu tun hat 😉

In meinem Fall, den ich hier schildere, benutzte unser Programm einen Service, mit dem es über Named Pipes kommuniziert. Das entsprechende Modul, ist schon ziemlich alt und hat seit ca. 3 Jahren keine Änderung erfahren. Aber jetzt auf einmal häuften sich Meldungen, dass unser Programm beim Beenden einen UAE auslöst.

Entsprechende Minidumps wurden angefordert und deren Analyse zeigte eigentlich nicht viel wirklich erhellendes.  Es sah alles danach aus, als ob ein Speicherblock freigegeben wird, der bereits freigegeben wurde.
Leider konnte das Szenario in unserem Testfeld nicht einfach nachgestellt werden. Und auch mehrfache Analyse des Sourcecodes brachte erst einmal nichts. Man ist manchmal einfach betriebsblind 🙁

Auffällig war aber, dass diese Meldungen erst vereinzelt auftraten nachdem unsere Software auf VS-2013 Update 3 umgestellt war und diese an Kunden ausgerollt wurde. Und weiterhin konnten wir feststellen, dass alle diejenigen, die diesen Fehler gemeldet haben, relativ leistungsfähige Rechner hatten.

Schließlich gelang es mir, das Szenario auf einem meiner Entwicklungsrechner nachzustellen.

Das passierte:

  1. Das Programm erhielt den Befehl zum Beenden.
  2. Irgendwann wurde auch dem Thread, der mit dem Service interagiert gesagt, dass er sich beim Dienst ausloggen soll.
  3. Der Dienst gibt auch brav den Befehl „Logoff“ an den Service.
  4. Der Thread der in einer Schleife immer auf einen Response des Dienstes wartet beendet sofort.
  5. Und nun erfolgte der Crash.

Die Ursache war ein fehlender CancelIo. Damit das Beenden möglichst schnell vonstatten geht, wurde darauf verzichtet, auf eine Antwort des Services zu warten, ob der Logoff Befehl verstanden wurde, denn kurz danach wird sowieso die Pipe geschlossen. Keine gute Idee in diesem Fall! Das hatte auch den Grund darin, dass

Was ist also passiert ❓

  • Die OVERLAPPED Struktur für den Lesevorgang lag auf dem Stack innerhalb einer while Schleife, die auf verschiedene Events wartet.
  • Die Funktion in der diese Struktur deklariert war wurde aber bereits verlassen.
  • Wenn dann sehr schnell die Antwort des Dienstes kam, wurde in die alte Adresse der OVERLAPPED Struktur ein Wert verändert.
  • … und damit wurde der Stack an dieser Stelle zerstört.

Es waren in meinem Fall nur 4 Bytes und in manchen Fällen würde eine Rücksprungadresse verändert und es kam zum Crash. In anderen Fällen wurde Speicher verändert, der nicht mehr benutzt wurde. Das ganze passierte eben auch nur auf den Systemen, die sehr schnell auf die Antwort des Dienstes reagierten. Kam die Antwort spät genug, war die Änderung des Speichers nicht mehr gefährlich.

Der Fehler blieb jahrelang unentdeckt, weil scheinbar das Stacklayout, das durch den VS-2010 Compiler erzeugt wurde, gegen diesen Fehler unempfindlich war, weil nur Speicher verändert wurde, der nicht zu einem Fehler führte.

Was lerne ich daraus:
Wenn ich einen ReadFile mit OVERLAPPED verwende, sollte ich immer darauf achten, dass im Bedarfsfall auch dieser Vorgang abgebrochen wird durch ein CancelIo ❗ Ein CancelIo mehr kann nicht schaden, wenn man den Block verlässt, in der eine OVERLAPPED Struktur definiert war.

Eine Überraschung mit GetModuleFileName

Ich habe ein Programm um COM-Automation erweitert. Der Test verlief super. Das Programm lief stand alone oder wurde über die Automation (CoCreateInstance) gestartet. Das Programm wurde im Installer integriert und ab da ging erst mal nichts mehr.

Wurde der externe COM Server über die Automation gestartet wurden auf einmal Satelite-DLLs nicht mehr gefunden. Das Programm ermittelte mit GetModuleFileName den Programmnamen. Ergänzte DEU zum Beispiel für die Deutsche Programmversion und suchte eine entsprechende DLL. Eigentlich ganz einfach. Das funktionierte aber nicht, weil GetModuleFileName den kurzen Dateinamen zurück gab. Also: aus PROGRAMM.EXE wurde PROGRA~1.EXE und Die Datei PROGRA~1DEU.DLL wurde gesucht, anstatt PROGRAMMDEU.DLL. Aber dann nicht gefunden…

Der Installer erzeugt scheinbar bei mir kurze Dateinamen in der Registrierung und auch MsiGetComponentPath, dass beim Start über CoCreateInstance konsultiert wurde, lieferte den kurzen Dateinamen.

Wirklich erstaunlich ist aber, dass der Name der in CreateProcess verwendet wurde auch später durch GetModuleFileName zurückgegeben wird. Wird also der kurze Dateiname beim Start verwendet, dann liefert GetModuleFileName einen kurzen Dateinamen.

Grundsätzlich kann man also nicht davon ausgehen, dass GetModuleFileName den langen Dateinamen zurück liefert.

Visual Studio 2013 Update 2 RC ist nun verfügbar

Mit dem neuen SQL 2014 der gestern veröffentlicht wurde kamen auch ein RC für Visual Studio 2013 Update 2. Dabei ist das Update 1 erst ende Januar heraus gekommen.
Das Update 2 für den TFS 2013 ist bereits veröffentlicht worden.
Siehe:  http://blogs.msdn.com/b/bharry/archive/2014/04/02/tfs-2013-2-update-2-released.aspx

Besonders gespannt bin ich auf die Linker Enchantements, die mit Update 2 kommen.
http://blogs.msdn.com/b/vcblog/archive/2014/03/25/linker-enhancements-in-visual-studio-2013-update-2-ctp2.aspx

Die Geschwindigkeit mit der aktuell Updates und Releases veröffentlicht werden ist geradezu verstörend. Man kommt kaum dazu sich auf eine Entwicklungsumgebung festzulegen und da ist schon das nächste Update, dass mit neuen Features Funktionen und Fixes reizt. 🙁

PS: Die Veröffentlichung von MS-SQL 2014 erinnert mich schmerzhaft, dass es nur noch 5 Jahre Support für OLE-DB gibt. Das ist noch einen traurigen Smiley wert 🙁

Advanced Developers Conference zu native C++ in Münchem vom 29.-30.04

Wer das lange Wochenende zum 1. Mai auf ganz besondere Weise einleiten will, dem rate ich die ADC C++ Konferenz vom 29.-30.04.2014 in München zu besuchen.

Die Agenda enthält alt bekannte Themen, aber auch interessantes Neues. Nachdem mit C++x11, C++/RT wieder etwas Schwung in die Weiterentwicklung von C++ gekommen ist und auch die Geschwindigkeit in denen neue Compiler und Library Features veröffentlich werden beschleunigen, lohnt sich ein Blick über den Tellerrand.
Oft genug finden wir als Entwickler nicht die Zeit uns neuem zu widmen, dass bereits verfügbar ist oder in nächster Zeit verfügbar wird.

Veranstalter ist die ppedv AG (hinter der Johannes Preishuber steht). Ich habe die Konferenzen der letzten Jahre, an denen ich teilgenommen habe, immer in guter Erinnerung. Sowohl in der Auswahl der Themen und Redner, als auch in der exzellenten Durchführung, mit immer sehr schon gewählten Konferenzorten und Hotels.
Insofern lohnt sich sicherlich ein Blick in die Agenda.
Und ganz gespannt bin ich persönlich auf die Abendveranstaltung am Mittwoch, denn hier wird auf den ADC-Konferenzen den Teilnehmern immer etwas wirklich außergewöhnliches geboten.

Da ich letztes Jahr aus privaten Gründen leider nicht teilnehmen konnte freue ich mich dieses Jahr wieder dabei sein zu können; ,alte Kontakte beleben zu können, neue Entwickler kennen zu lernen und Neues aus der C++ Welt zu hören.

ADC

Das Problem, wenn man Fehler richtig erkennt, richtig dokumentiert aber dann den gleichen Blödsinn wieder macht

… oder auch: Es gibt Tage, da könnte man sich in der Tischplatte verbeißen…

Wir haben vor ein paar Wochen einen sehr mystischen Fehler in unserer Software gefunden.
Das Problem war eine Funktion, die einen (const) Zeiger auf einen Datenblock bekam.
Die Funktion hat aber unter Umständen eine UI-Interaktion (Dialog) ausgelöst und die entsprechende offene Nachrichtenschleife hat es wiederum einen Hintergrundthread erlaubt (unter bestimmten Umständen) Nachrichten an den Mainthread zu senden, der wiederum die Anzeige aktualisierte und auch den Datenblock, der eben noch als Zeiger übergeben wurde, ungültig machte.

Die Funktion sah in etwa so aus:

bool CSomeClass::SomeAction(const S_FOO *pData)
{
...

pData war nun ungültig und wurde verändert, war aber immer noch gültiger Speicher in dem ein paar Ids aus einer Datenbank standen. Das Programm stürzte nicht ab. Tat aber aufgrund der falschen Ids auch nicht das, was es eigentlich sollte.

Der Code wurde dann wie folgt geändert und ein entsprechender Kommentar geschrieben:

bool CSomeClass::SomeAction(const S_FOO &data)
{
// We use a copy of the result object. There is a chance that the
// global result gets replaced while we are inside this routine and 
// the storage where our pointer points gets deleted and replaced 
// by a new result object.
...

Oha. Aber was passierte hier. Anstatt wirklich eine Kopie zu verwenden, wurde wieder nur ein Zeiger verwendet. Nur diesmal in Form einer Referenz.

Es wird nun niemanden wundern., dass mir die selbe Fehlebeschreibung wieder auf den Tisch flatterte. Im Debugger  wurden die selben Probleme festgestellt und ich musste dreimal auf die Code-Zeile schauen und den Kommentar dreimal lesen, bevor ich verstand was hier geändert wurde.

  • Fehler erkannt.
  • Fehler dokumentiert.
  • Fehler aber nicht gefixed.

So hätte es aussehen müssen:

bool CSomeClass::SomeAction(const S_FOO data)
{
...

PS: Ich oute mich mal. Der Trottel war ich…

Visual Studio 2013 ist nun verfügbar

Heute ist Visual Studio 2013 verfügbar gemacht worden.

Weitere Infos finden sich im VC++ Blog und in Somasegar’s Blog:
http://blogs.msdn.com/b/vcblog/archive/2013/10/17/visual-studio-2013-available-now.aspx
http://blogs.msdn.com/b/somasegar/archive/2013/10/17/visual-studio-2013-available-for-download.aspx

 

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.