windows - hvorfor kan jeg ikke sætte en DIB sektion på udklipsholderen?

Indlæg af Hanne Mølgaard Plasc

Problem



Jeg forsøger at fange skærmindholdet, ændre bitene af det grebede billede direkte, og sæt derefter resultatet på udklipsholderen. (Faktisk er jeg ikke i sidste ende interesseret i udklipsholderen, men bruger den som et testtrin.)


Jeg begyndte med eksemplet fra et af svarene på dette spørgsmål. Men det bruger CreateCompatibleBitmap, og fra det jeg forstår, er der ingen måde at få direkte adgang til bitmapbiterne, der er oprettet med den funktion, så jeg forsøger at bruge CreateDIBSection i stedet. Her er hvad jeg har hidtil:


void GetScreenShot(void)
{
    int x1, y1, w, h;

    // get screen dimensions
    x1  = GetSystemMetrics(SM\_XVIRTUALSCREEN);
    y1  = GetSystemMetrics(SM\_YVIRTUALSCREEN);
    w  = GetSystemMetrics(SM\_CXVIRTUALSCREEN);
    h  = GetSystemMetrics(SM\_CYVIRTUALSCREEN);

    // copy screen to bitmap

    HDC hScreen = GetDC(NULL);

    HDC hDC = CreateCompatibleDC(hScreen);
    if( !hDC )
        throw 0;

    // This works:
    //HBITMAP hBitmap = CreateCompatibleBitmap(hScreen, w, h);

    BITMAPINFO BitmapInfo;
    BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    BitmapInfo.bmiHeader.biWidth = w;
    BitmapInfo.bmiHeader.biHeight = h;
    BitmapInfo.bmiHeader.biPlanes = 1;
    BitmapInfo.bmiHeader.biBitCount = 24;   // assumption; ok for our use case
    BitmapInfo.bmiHeader.biCompression = BI\_RGB;
    BitmapInfo.bmiHeader.biSizeImage = ((w * 3 + 3) & ~3) * h;
    BitmapInfo.bmiHeader.biXPelsPerMeter = (int)(GetDeviceCaps( hScreen, LOGPIXELSX ) * 39.3701 + 0.5);
    BitmapInfo.bmiHeader.biYPelsPerMeter = (int)(GetDeviceCaps( hScreen, LOGPIXELSY ) * 39.3701 + 0.5);
    BitmapInfo.bmiHeader.biClrUsed = 0;
    BitmapInfo.bmiHeader.biClrImportant = 0;
    BitmapInfo.bmiColors[0].rgbBlue = 0;
    BitmapInfo.bmiColors[0].rgbGreen = 0;
    BitmapInfo.bmiColors[0].rgbRed = 0;
    BitmapInfo.bmiColors[0].rgbReserved = 0;

    void *pBits;
    // This does not work:
    HBITMAP hBitmap = CreateDIBSection( hScreen, &BitmapInfo, DIB\_RGB\_COLORS, &pBits, NULL, 0 );
    if( !hBitmap )
        throw 0;

    HGDIOBJ old\_obj = SelectObject(hDC, hBitmap);
    if( !old\_obj )
        throw 0;

    if( !BitBlt(hDC, 0, 0, w, h, hScreen, x1, y1, SRCCOPY) )
        throw 0;

    if( !SelectObject(hDC, old\_obj) )
        throw 0;

    if( !GdiFlush() )
        throw 0;

    // this is where we would modify the image

    // save bitmap to clipboard

    if( !OpenClipboard(NULL) )
        throw 0;

    if( !EmptyClipboard() )
        throw 0;

    if( !SetClipboardData( CF\_BITMAP, hBitmap ) )   // CF\_DIB causes the throw
        throw 0;

    if( !CloseClipboard() )
        throw 0;

    // clean up
    DeleteDC(hDC);
    ReleaseDC(NULL, hScreen);
    DeleteObject(hBitmap);
}


Dette virker dog ikke. Alle opkald rapporterer succes, men billedet slutter ikke på udklipsholderen.


Når jeg kører dette i en debugger, kan jeg se, hvad der ligner billeddata ved pBits efter opkaldet til BitBlt, selvom det er så mistanke om, at den første masse værdier har R, G, B det samme, men nederste venstre hjørne af min skærm har faktisk en blålig farve. Uanset om de faktiske bits er forkerte, skal jeg få noget af et billede på udklipsholderen, men jeg don 't.


Jeg har forsøgt at bruge CF\_DIB som det første argument til SetClipboardData i stedet for CF\_BITMAP, men så fejler opkaldet.


Hvis jeg kommenterer opkaldet til CreateDIBSection og frigiver opkaldet til CreateCompatibleBitmap, fungerer det, men jeg har ingen mulighed for at ændre billedbitene direkte.


Jeg antager, at jeg først kunne indfange min DIB-sektion, ændre den, ring til CreateCompatibleBitmap og blit fra DIB-sektionen til 'kompatibel bitmap', men det ser ud til, at der er lidt asinin til at kopiere bitene igen uden tilsyneladende grund.


Hvorfor kan jeg ikke sende min DIB sektion til SetClipboardData?


(Jeg må sige, at jeg hader at arbejde med GDI osv. Det er generelt klart som mudder.)

Bedste reference


Fik det ud, endelig, da jeg fandt dette. API-dokumentationen på MSDN er ret vag om dette, sandsynligvis fordi det selv daterer sig så langt, men det ser ud til, at udklipsholderfunktionerne alle bruger Windows 3.x stilhukommelsesallokeringssystemet (GlobalAlloc osv.). [31]


Det er fornuftigt for et systemklemkort at eksponere delt hukommelse til applikationen direkte i modsætning til, at operativsystemet skal kopiere data til interne buffere. Men funktionen til udklipsholdere går langt nok tilbage, at de nyere sidefilbaserede ordninger for delt hukommelse ikke eksisterede, så de skulle bruge GlobalAlloc hukommelse. Når 32-bit Windows kom sammen, var det mere fornuftigt at bare efterligne det mekanisme snarere end at bryde eksisterende applikationskode.


Jeg har stærkt en mistanke om, at de fleste GDI-håndtag faktisk også GlobalAlloc håndterer, og det er derfor, du kan vende tilbage til CreateCompatibleBitmap til udklipsholderen. Derimod gør CreateDIBSection ikke brug fuldt ud den gamle stilallokering, hvilket er tydeligt fra det faktum, at du kan fortælle det at gemme bitene i en filmappe. (Jeg formoder, at håndtaget, der vender tilbage, stadig er fra GlobalAlloc, men at den således tildelte blok indeholder en direkte peger til virtuel hukommelse for billeddataene, og SetClipboardData tester for dette, fordi det er en åbenbar 'gotcha'.)


Så jeg fikset alt ved bare at lade CreateDIBSection allokere, hvor det vil, fordi det på en eller anden måde ikke er muligt at aflevere det til SetClipboardData og derefter gøre det, når jeg vil sende til udklipsholderen:


void CScreenshot::SendToClipboard( void )
{
    HGLOBAL hClipboardDib = GlobalAlloc( GMEM\_MOVEABLE | GMEM\_SHARE, cbDib );
    if( !hClipboardDib )
        throw 0;

    void *pClipboardDib = GlobalLock( hClipboardDib );
    memcpy( pClipboardDib, &BitmapInfo, sizeof(BITMAPINFOHEADER) );
    memcpy( (BITMAPINFOHEADER*)pClipboardDib+1, pBits, BitmapInfo.bmiHeader.biSizeImage );
    GlobalUnlock( hClipboardDib );

    if( !OpenClipboard( NULL ) )
    {
        GlobalFree( hClipboardDib );
        throw 0;
    }

    EmptyClipboard();
    SetClipboardData( CF\_DIB, hClipboardDib );
    CloseClipboard();
}


Det er uheldigt, jeg skal lave en overflødig kopi her, men på den stærke side har jeg stærkt en mistanke om, at applikationen, der læser udklipsholderen, vil se den samme kopi, i modsætning til, at Windows gør yderligere kopiering internt.


Hvis jeg ønskede at være en effektiv effektivitet junkie, jeg mistanke at håndtaget returneret fra CreateCompatibleBitmap kunne bruges i et opkald til GlobalLock og så kunne du få direkte ved bitene uden at pådrage kopien i CScreenshot::SendToClipboard, fordi du bare kunne sende det direkte til SetClipboardData. Men jeg har også stærkt mistanke om, at det ville være uokumenteret adfærd (men retter mig hvis jeg er forkert!), Så en temmelig dårlig ide. Du skal også holde øje med, om du har bestået det i udklipsholderen eller ej, og hvis du gjorde det, ikke ringe DeleteObject på det. Men jeg er ikke sikker. Jeg formoder også, at SetClipboardData skulle lave en kopi af det alligevel, fordi det sandsynligvis ikke er tildelt med GMEM\_SHARE.


Tak til kommentarerne for at få mig lidt tættere på at finde ud af det.