Problem mit SetDIBitsToDevice() und GetDIBits()



  • Hallo,

    ich habe schon seit längerem ein Problem mit SetDIBitsToDevice() und GetDIBits(). Und zwar schlagen diese scheinbar willkürlich fehl (geben also 0 zurück). Ein Aufruf von GetLastError() ergibt: "Der Vorgang wurde erfolgreich beendet." Beheben lässt sich das ganze, indem man wiederholt neuen Speicher für das anzuzeigende Bitmap anfordert, bis die Funktionen nicht mehr fehlschlagen (wird im Code unten durch die while-Schleife gemacht). Dann funktioniert das Ganze aber auch eine Weile problemlos (oder sogar stundenlang bis zum Programmende), ohne dass mehr Speicher angefordert werden muss.
    Und obwohl der Code, den ich zum Ändern und Lesen des Bildschirminhaltes benutze, bei jedem Programm gleich bleibt, variiert das Verhalten bei jedem Programm und auf jedem Rechner stark.
    Hier sind einige Beispiele:

    Ein Clientprogramm zur Remotesteuerung alloziiert z.B. auf meinem Rechner (Windows XP, 512 MB RAM) immer rund 400-500 MB, bevor ich den Bildschirminhalt der Gegenseite zu Gesicht bekomme. Auf anderen Rechnern sind das beispielsweise 100-600 MB (auch Windows XP und 512 MB), oder nur rund 100 MB (Windows 98, 128 MB RAM). Nach 4-7 Minuten problemlosen Betriebs bekomme ich aber die Meldung "Externe Exception EEEFACE", was eine Exception vom Typ xalloc ist. Ich bin nicht ganz sicher, wo sie erzeugt wird, meine testweisen try-catch-Blöcke um SetDIBitsToDevice() und new[] haben nämlich nicht gegriffen.
    Der Server zur Remotesteuerung, der GetUncompressedScreenBuffer() benützt, alloziiert manchmal nur einmal, manchmal auch bis zu 100 MB Speicher, ich bekomme hier aber nie eine xalloc-Exception.
    Anders ist es bei einem Programm zur Videoaufzeichnung des Bildschirminhaltes, da bekomme ich die Exception ziemlich zuverlässig nach 4 Minuten und ein paar Sekunden, obwohl das Programm genauso GetUncompressedScreenBuffer() verwendet.
    Ein anderes Beispiel ist ein Spiel, das den Screenbuffer auf meinem Rechner immer nur einmal anfordern muss, auf einem anderen Rechner mal einmal, aber zuweilen auch 100-mal anfordern muss und auf einem Windows 98-Rechner grundsätzlich solange weitermacht, bis der virtuelle Speicher alle ist.
    Ein weiteres Spiel, das nun bereits von hunderten Leuten problemlos benutzt wird, macht zur Zeit keine Probleme (eine einfache weitere Klassen- oder Funktionsdeklaration in der "graphics.cpp" macht das aber zunichte, selbst wenn nie eine Instanz der Klasse erzeugt, bzw. die Funktion jemals aufgerufen wird, genauso beispielsweise das Erzeugen einer leeren linked list auf dem Heap *bevor* der Bildschirmbuffer erstellt wurde).
    Bei einem Experiment, wo ich den Bildschirmbuffer mittels File mapping realisiert habe, gab es keine Probleme (könnte aber auch einfach nur Glückssache gewesen sein).

    Letztendlich sieht es für mich so aus, als würde es den WinAPI-Funktionen darauf ankommen, wo das Bitmap im Speicher liegt, erklärt aber immer noch nicht, warum manche Programme den Dienst verweigern, nachdem sie doch schon einige Minuten erfolgreich gelaufen sind.
    Unten ist die Klasse, die ich in manchen der oben genannten Programme verwende (dazu sollte ich sagen, dass SetScreenBuffer() und Get(Un)compressedScreenBuffer() niemals beide bei derselben ScreenInterface-Instanz aufgerufen werden).
    Ich denke, dass ich entweder beim Befüllen der BITMAPINFO-Struktur einen Fehler mache oder dass ich SetDIBitsToDevice() und GetDIBits() falsche Parameter übergebe.
    Ich wäre froh, wenn jemand mir helfen könnte, das Geheimnis zu lüften, denn mit der Zeit wird dieses ewige Glücksspiel ziemlich nervenaufreibend. Und dass mir einige der Programme erst einmal den Großteil des Arbeitsspeichers auslagern, bevor etwas passiert, natürlich auch.
    Falls etwas noch nicht ganz klar sein sollte (wovon ich ausgehe, falls ich im Code keinen trivialen Fehler gemacht habe), stehe ich für Nachfragen natürlich zur Verfügung.

    class Buffer
    {
     public:
      byte* Data;
      ulong Size;
      TBitStream* ToBitStream() {TBitStream* BS=new TBitStream;BS->Write(Data,Size);BS->SetPosition(0);return BS;};
      Buffer() {Data=NULL;Size=0;};
      Buffer(byte* data,ulong size) {Data=data;Size=size;};
      ~Buffer() {delete[] Data;};
    };
    
    const SIERR_SUCCESS=0,SIERR_UNKNOWN=1,SIERR_WRONGRESOLUTION=2,SIERR_TOOMANYREALLOCS=3;
    
    class ScreenInterface
    {
     public:
      ScreenInterface();
      ~ScreenInterface();
      void ReallocateScreenBuffer();
    
      Buffer* GetCompressedScreenBuffer();
      Buffer* GetUncompressedScreenBuffer();
      int SetScreenBuffer(Buffer* Image=NULL);
    
     protected:
      void* hdcScreen;
      int ScreenWidth;
      int ScreenHeight;
      void* hdcCompatible;
      void* hbmScreen;
      tagBITMAPINFO BInfo;
      byte* RGBField;
      byte* CRGBData;
    };
    
    ScreenInterface::ScreenInterface()
    {
     hdcScreen=GetDC(0);
     assert(hdcScreen);
     ScreenWidth=Screen->Width;
     ScreenHeight=Screen->Height;
     //assert(ScreenWidth==1024 && ScreenHeight==768);
     hdcCompatible=CreateCompatibleDC(hdcScreen);
     assert(hdcCompatible);
     hbmScreen=CreateCompatibleBitmap(hdcScreen,ScreenWidth,ScreenWidth);
     assert(hdcCompatible);
     if (!SelectObject(hdcCompatible,hbmScreen))error_quit("Compatible bitmap selection failed");
     ZeroMemory(&BInfo,sizeof(tagBITMAPINFO));
     tagBITMAPINFOHEADER& BHead=BInfo.bmiHeader;
     BHead.biWidth=ScreenWidth;
     BHead.biHeight=ScreenHeight;
     BHead.biSize=sizeof(tagBITMAPINFOHEADER);
     BHead.biPlanes=1;
     BHead.biBitCount=24;
     BHead.biCompression=BI_RGB;
     BHead.biSizeImage=0;
     BHead.biXPelsPerMeter=3779;
     BHead.biYPelsPerMeter=3779;
     BHead.biClrUsed=0;
     BHead.biClrImportant=0;
     RGBField=new byte[ScreenWidth*ScreenHeight*3];
     CRGBData=new byte[ScreenWidth*ScreenHeight*4];
    }
    
    ScreenInterface::~ScreenInterface()
    {
     delete[] RGBField;
     delete[] CRGBData;
     DeleteDC(hdcCompatible);
     DeleteObject(hbmScreen);
     ReleaseDC(Application->Handle,hdcScreen);
    }
    
    Buffer* ScreenInterface::GetUncompressedScreenBuffer()
    {
     if (!BitBlt(hdcCompatible,0,0,ScreenWidth,ScreenHeight,hdcScreen,0,0,SRCCOPY))error_quit("Screen to compatible blt failed");
     while (!GetDIBits(hdcCompatible,hbmScreen,0,ScreenHeight,RGBField,&BInfo,DIB_RGB_COLORS))
     {
      RGBField=new byte[ScreenWidth*ScreenHeight*3];
     }
     byte* CData=new byte[ScreenWidth*ScreenHeight*3];
     memcpy(CData,RGBField,ScreenWidth*ScreenHeight*3);
     return new Buffer(CData,ScreenWidth*ScreenHeight*3);
    }
    
    int ScreenInterface::SetScreenBuffer(Buffer* Image)
    {
     byte* RGB;
     if (Image==NULL)
     {
      if (Screen->Width!=ScreenWidth || Screen->Height!=ScreenHeight)return SIERR_WRONGRESOLUTION;
      RGB=RGBField;
     }
     else
     {
      if ((Screen->Width*Screen->Height*3)!=Image->Size)return SIERR_WRONGRESOLUTION;
      RGB=RGBField;
      memcpy(RGBField,Image->Data,ScreenWidth*ScreenHeight*3); //copying could be skipped if the function is never called with Image==NULL, no one would notice
     }
     int ReallocFailures=0;
     while (SetDIBitsToDevice(hdcScreen,0,0,ScreenWidth,ScreenHeight,0,0,0,ScreenHeight,RGB,&BInfo,DIB_RGB_COLORS)==0)
     {
      //ShowLastError();
      RGBField=new byte[ScreenHeight*ScreenWidth*3];
      assert(RGBField);
      if (++ReallocFailures>2)return SIERR_TOOMANYREALLOCS;
     }
     return SIERR_SUCCESS;
    }
    


  • Also, um das ganze noch einmal zusammenzufassen:

    1. SetDIBitsToDevice() und GetDIBits() schlagen manchmal fehl, GetLastError() gibt trotzdem 0 zurück
    2. Wiederholungsversuche schlagen ebenfalls immer fehl
    3. Man kann periodisch neuen Speicher für das Bitmap anfordern und nachdem man ungefähr Speicher in der Größe des physikalischen RAMs angefordert hat, schlagen die Funktionen unter Verwendung des zuletzt angeforderten Speichers nicht mehr fehl

    Und die Frage wäre:
    warum ist das so?



  • msdn: GetDIBits() schrieb:

    The bitmap identified by the hbmp parameter must not be selected into a device context when the application calls this function.

    ...dasselbe gilt auch für SetDIBits().

    Außerdem bekommst du Probleme bei krummen Auflösungen:

    msdn schrieb:

    The scan lines must be aligned on a DWORD except for RLE-compressed bitmaps.



  • Das eine könnte natürlich ein Grund sein, denn

    SelectObject(hdcCompatible,hbmScreen);
    

    müsste ja hbmScreen in hdcCompatible selecten. Da wundert mich es, dass es überhaupt funktioniert.

    Bei SetDIBitsToDevice() ist das aber anders, da habe ich ja kein Bitmap-Objekt. Das anzuzeigende Bitmap wird nur durch ein Array mit den Farbwerten und die BITMAPINFO-Struktur repräsentiert.

    Dass die Scanlines DWORD-aligned sein müssen, weiss ich, das stellt aber bei den verwendeten Größen 640x480, 800x600 und 1024x768 kein Problem dar.


Anmelden zum Antworten