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.