截图WinAPI - 免费内存

时间:2017-08-27 01:02:26

标签: c winapi memory-leaks screenshot

我已经在一两天前问了一个非常相似的问题,但我的问题不是很清楚,所以我试着在这里重新制定它:

我想做一个屏幕截图,为此我搜索并找到了这段代码:

#include <windows.h>
#include <stdio.h>

inline int GetFilePointer(HANDLE FileHandle)
{
    return SetFilePointer(FileHandle, 0, 0, FILE_CURRENT);
}

extern _Bool SaveBMPFile(char* filePath, HBITMAP bitmap, HDC bitmapDC, int width, int height)
{
    _Bool Success = 0;
    HDC SurfDC = NULL;        // GDI-compatible device context for the surface
    HBITMAP OffscrBmp = NULL; // bitmap that is converted to a DIB
    HDC OffscrDC = NULL;      // offscreen DC that we can select OffscrBmp into
    LPBITMAPINFO lpbi = NULL; // bitmap format info; used by GetDIBits
    LPVOID lpvBits = NULL;    // pointer to bitmap bits array
    HANDLE BmpFile = INVALID_HANDLE_VALUE;    // destination .bmp file
    BITMAPFILEHEADER bmfh;  // .bmp file header

                            // We need an HBITMAP to convert it to a DIB:
    if ((OffscrBmp = CreateCompatibleBitmap(bitmapDC, width, height)) == NULL)
        return 0;

    // The bitmap is empty, so let's copy the contents of the surface to it.
    // For that we need to select it into a device context. We create one.
    if ((OffscrDC = CreateCompatibleDC(bitmapDC)) == NULL)
        return 0;

    // Select OffscrBmp into OffscrDC:
    HBITMAP OldBmp = (HBITMAP)SelectObject(OffscrDC, OffscrBmp);

    // Now we can copy the contents of the surface to the offscreen bitmap:
    BitBlt(OffscrDC, 0, 0, width, height, bitmapDC, 0, 0, SRCCOPY);

    // GetDIBits requires format info about the bitmap. We can have GetDIBits
    // fill a structure with that info if we pass a NULL pointer for lpvBits:
    // Reserve memory for bitmap info (BITMAPINFOHEADER + largest possible
    // palette):
    if ((lpbi = (LPBITMAPINFO)malloc(sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD))) == NULL)
        return 0;


    ZeroMemory(&lpbi->bmiHeader, sizeof(BITMAPINFOHEADER));
    lpbi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    // Get info but first de-select OffscrBmp because GetDIBits requires it:
    SelectObject(OffscrDC, OldBmp);
    if (!GetDIBits(OffscrDC, OffscrBmp, 0, height, NULL, lpbi, DIB_RGB_COLORS))
        return 0;

    // Reserve memory for bitmap bits:
    if ((lpvBits = malloc(lpbi->bmiHeader.biSizeImage)) == NULL)
        return 0;

    // Have GetDIBits convert OffscrBmp to a DIB (device-independent bitmap):
    if (!GetDIBits(OffscrDC, OffscrBmp, 0, height, lpvBits, lpbi, DIB_RGB_COLORS))
        return 0;


    //ANSI->Unicode
    LPCSTR szAnsi = filePath;
    int Size = MultiByteToWideChar(CP_ACP, 0, szAnsi, -1, NULL, 0);
    LPWSTR filename = malloc(sizeof(LPWSTR) * Size);
    MultiByteToWideChar(CP_ACP, 0, szAnsi, -1, filename, Size);
    // Create a file to save the DIB to:
    if ((BmpFile = CreateFile(filename,
        GENERIC_WRITE,
        0, NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL)) == INVALID_HANDLE_VALUE)

        return 0;

    DWORD Written;    // number of bytes written by WriteFile

                      // Write a file header to the file:
    bmfh.bfType = 19778;        // 'BM'
                                // bmfh.bfSize = ???        // we'll write that later
    bmfh.bfReserved1 = bmfh.bfReserved2 = 0;
    // bmfh.bfOffBits = ???     // we'll write that later
    if (!WriteFile(BmpFile, &bmfh, sizeof(bmfh), &Written, NULL))
        return 0;

    if (Written < sizeof(bmfh))
        return 0;

    // Write BITMAPINFOHEADER to the file:
    if (!WriteFile(BmpFile, &lpbi->bmiHeader, sizeof(BITMAPINFOHEADER), &Written, NULL))
        return 0;

    if (Written < sizeof(BITMAPINFOHEADER))
        return 0;

    // Calculate size of palette:
    int PalEntries;
    // 16-bit or 32-bit bitmaps require bit masks:
    if (lpbi->bmiHeader.biCompression == BI_BITFIELDS)
        PalEntries = 3;
    else
        // bitmap is palettized?
        PalEntries = (lpbi->bmiHeader.biBitCount <= 8) ?
        // 2^biBitCount palette entries max.:
        (int)(1 << lpbi->bmiHeader.biBitCount)
        // bitmap is TrueColor -> no palette:
        : 0;
    // If biClrUsed use only biClrUsed palette entries:
    if (lpbi->bmiHeader.biClrUsed)
        PalEntries = lpbi->bmiHeader.biClrUsed;

    // Write palette to the file:
    if (PalEntries) {
        if (!WriteFile(BmpFile, &lpbi->bmiColors, PalEntries * sizeof(RGBQUAD), &Written, NULL))
            return 0;

        if (Written < PalEntries * sizeof(RGBQUAD))
            return 0;
    }

    // The current position in the file (at the beginning of the bitmap bits)
    // will be saved to the BITMAPFILEHEADER:
    bmfh.bfOffBits = GetFilePointer(BmpFile);

    // Write bitmap bits to the file:
    if (!WriteFile(BmpFile, lpvBits, lpbi->bmiHeader.biSizeImage, &Written, NULL))
        return 0;

    if (Written < lpbi->bmiHeader.biSizeImage)
        return 0;

    // The current pos. in the file is the final file size and will be saved:
    bmfh.bfSize = GetFilePointer(BmpFile);

    // We have all the info for the file header. Save the updated version:
    SetFilePointer(BmpFile, 0, 0, FILE_BEGIN);
    if (!WriteFile(BmpFile, &bmfh, sizeof(bmfh), &Written, NULL))
        return 0;

    if (Written < sizeof(bmfh))
        return 0;

    return 1;
}



_Bool ScreenCapture(char* filePath, int xStart, int yStart, int width, int height)
{
    // get a DC compat. w/ the screen
    HDC hDc = CreateCompatibleDC(0);

    // make a bmp in memory to store the capture in
    HBITMAP hBmp = CreateCompatibleBitmap(GetDC(0), width, height);

    // join em up
    SelectObject(hDc, hBmp);

    // copy from the screen to my bitmap
    BitBlt(hDc, 0, 0, width, height, GetDC(0), xStart, yStart, SRCCOPY);

    // save my bitmap
    _Bool ret = SaveBMPFile(filePath, hBmp, hDc, width, height);

    // free the bitmap memory
    DeleteObject(hBmp);

    return ret;
}

main()
{
    ScreenCapture("screenshot.bmp", 0, 0, 1920, 1080);

    FILE* Screen = NULL;
    Screen = fopen("screenshot.bmp", "r"); //Error, the image is "used" somewhere...     

    return 0;
}

我不知道WinAPI的一个词,但我在Viual Studio中看到,每次调用ScreenCapture()时,都会出现非释放的RAM,这是一个问题。

如果有人知道错误在哪里......

感谢帮助,谢谢:)

2 个答案:

答案 0 :(得分:0)

  

GetDC(0)

必须使用ReleasedDC清除

GetDC(0)。以这种方式编写HDC hdc_desktop = GetDC(HWND_DESKTOP); HBITMAP hBmp = CreateCompatibleBitmap(hdc_desktop, width, height); ... ReleaseDC(HWND_DESKTOP, hdc_desktop); 时,无法清理。您的程序只能使用10,000个GDI句柄,因此这种资源泄漏可能是一个严重的问题。

你应该这样做:

HWND_DESKTOP

0只是GetDC,我使用它是为了清晰。 hdc_desktop会返回一个句柄DeleteObject(hBmp),可以在最后清理它。

hBmp是一个小错误,因为hDc当前在GetDIBits中被选中,无法删除。很多程序员都犯了这个错误,所以更新的Windows版本(我认为自XP以来)已经开始期待这个,所以Windows修复了这个bug(至少在这种情况下)但是你应该真的保存旧位图的句柄,恢复旧的位图,然后删除新的位图。

SaveBMPFile还有另一个小错误。文档说,调用函数时,不能选择hbitmap到dc。但是Windows再次用于查看此错误并修复它。

您的L"screenshot.bmp"可以简化。您没有保存调色板位图,您可以完全忽略调色板。

未解决的问题:您正在使用Unicode函数,因此您使用UTF-16字符串文字:"screenshot.bmp"而不是extern _Bool SaveBMPFile(const wchar_t* filePath, HDC memdc, HBITMAP hbitmap, int width, int height) { _Bool success = 0; WORD bpp = 24; //or 32 for 32-bit bitmap DWORD size = ((width * bpp + 31) / 32) * 4 * height; BITMAPFILEHEADER filehdr = { 0 }; filehdr.bfType = 19778; filehdr.bfSize = 54 + size; filehdr.bfOffBits = 54; //54 = 14 + 40, sizeof BITMAPFILEHEADER & BITMAPINFOHEADER BITMAPINFOHEADER infohdr = { sizeof(infohdr) }; infohdr.biWidth = width; infohdr.biHeight = height; infohdr.biPlanes = 1; infohdr.biBitCount = bpp; BYTE *bits = malloc(size); GetDIBits(memdc, hbitmap, 0, height, bits, (BITMAPINFO*)&infohdr, DIB_RGB_COLORS); HANDLE hfile = CreateFileW(filePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if(hfile != INVALID_HANDLE_VALUE) { DWORD temp; WriteFile(hfile, &filehdr, 14, &temp, NULL); WriteFile(hfile, &infohdr, 40, &temp, NULL); WriteFile(hfile, bits, size, &temp, NULL); CloseHandle(hfile); success = 1; } free(bits); return success; } _Bool ScreenCapture(const wchar_t* filePath, int x, int y, int width, int height) { HDC hdc = GetDC(HWND_DESKTOP); HDC memdc = CreateCompatibleDC(hdc); HBITMAP hbitmap = CreateCompatibleBitmap(hdc, width, height); HBITMAP oldbitmap = SelectObject(memdc, hbitmap); BitBlt(memdc, 0, 0, width, height, hdc, x, y, SRCCOPY); SelectObject(memdc, oldbitmap); _Bool ret = SaveBMPFile(filePath, memdc, hbitmap, width, height); DeleteObject(hbitmap); DeleteDC(memdc); ReleaseDC(HWND_DESKTOP, hdc); return ret; } int main(void) { ScreenCapture(L"screenshot.bmp", 0, 0, GetSystemMetrics(SM_CXFULLSCREEN), GetSystemMetrics(SM_CYFULLSCREEN)); return 0; }

data = [
    { box: '',  name: 'Brady, Marsha', status: 'busy', room: 'ROOM1' ,pos:2},
    { box: '1', name: 'Davis, Kevin', status: 'busy-1' , room: 'ROOM1',pos:2},
    { box: '1', name: 'Carter, Dawn',  status: 'busy-1' , room: 'ROOM1',pos:2},
    { box: '',  name: 'McDonald, Jane',status: 'busy', room: 'ROOM1',pos:2 },
    { box: 'MA',name: 'Bacon, Anna', status: 'wbusy-3', room: 'ROOM3',pos:1 },
    { box: 'MA',name: 'Yoga, Martha', status: 'busy-3', room: 'ROOM3' ,pos:1},
    { box: '1', name: 'Steve, Ana', status: 'busy-2', room: 'ROOM2',pos:3},
    { box: '2', name: 'tester', status: '', room: 'ROOM2',pos:3},
    { box: '3', name: 'Jhonson, Francisco', status: 'busy-2' , room: 'ROOM2',pos:3},
    { box: '4', name: 'Mathews, Susan', status: 'busy-2' , room: 'ROOM2',pos:3},
    { box: '5', name: 'Davis, Christine', status: 'busy-2' , room: 'ROOM2',pos:3},
    { box: '6', name: 'Wheaters, Bob', status: 'busy-2' , room: 'ROOM2',pos:3},
    { box: '7', name: 'Kinsky, Annabelle' , status: 'busy-2' , room: 'ROOM2',pos:3},
    { box: '8', name: '', status: '', room: 'ROOM2',pos:3},
    { box: '9', name: 'Stephanapolous', status: 'busy-2' , room: 'ROOM2',pos:3},
    { box: '10',name: 'Kelley, Tom', status: 'busy-2' , room: 'ROOM2',pos:3},
    { box: 'T', name: 'Peterson, James', status: 'busy-2' , room: 'ROOM2',pos:3}
]

您可以在循环中运行它并在Taskman中查看GDI句柄以确保没有泄漏。

答案 1 :(得分:-2)

这是最终的代码,没有问题,没有内存泄漏工作:

#include <windows.h>
#include <stdio.h>

// Helper function to retrieve current position of file pointer:
inline int GetFilePointer(HANDLE FileHandle)
{
    return SetFilePointer(FileHandle, 0, 0, FILE_CURRENT);
}
//---------------------------------------------------------------------------

// Screenshot
//    -> FileName: Name of file to save screenshot to
//    -> lpDDS: DirectDraw surface to capture
//    <- Result: Success
//
extern _Bool SaveBMPFile(char* filePath, HBITMAP bitmap, HDC bitmapDC, int width, int height)
{
    _Bool Success = 0;
    HBITMAP OffscrBmp = NULL; // bitmap that is converted to a DIB
    HDC OffscrDC = NULL;      // offscreen DC that we can select OffscrBmp into
    LPBITMAPINFO lpbi = NULL; // bitmap format info; used by GetDIBits
    LPVOID lpvBits = NULL;    // pointer to bitmap bits array
    HANDLE BmpFile = INVALID_HANDLE_VALUE;    // destination .bmp file
    BITMAPFILEHEADER bmfh;  // .bmp file header

                            // We need an HBITMAP to convert it to a DIB:
    if ((OffscrBmp = CreateCompatibleBitmap(bitmapDC, width, height)) == NULL)
        return 0;

    // The bitmap is empty, so let's copy the contents of the surface to it.
    // For that we need to select it into a device context. We create one.
    if ((OffscrDC = CreateCompatibleDC(bitmapDC)) == NULL)
        return 0;

    // Select OffscrBmp into OffscrDC:
    HBITMAP OldBmp = (HBITMAP)SelectObject(OffscrDC, OffscrBmp);

    // Now we can copy the contents of the surface to the offscreen bitmap:
    BitBlt(OffscrDC, 0, 0, width, height, bitmapDC, 0, 0, SRCCOPY);

    // GetDIBits requires format info about the bitmap. We can have GetDIBits
    // fill a structure with that info if we pass a NULL pointer for lpvBits:
    // Reserve memory for bitmap info (BITMAPINFOHEADER + largest possible
    // palette):
    if ((lpbi = (LPBITMAPINFO)malloc(sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD))) == NULL)
        return 0;


    ZeroMemory(&lpbi->bmiHeader, sizeof(BITMAPINFOHEADER));
    lpbi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    // Get info but first de-select OffscrBmp because GetDIBits requires it:
    SelectObject(OffscrDC, OldBmp);
    if (!GetDIBits(OffscrDC, OffscrBmp, 0, height, NULL, lpbi, DIB_RGB_COLORS))
        return 0;

    // Reserve memory for bitmap bits:
    if ((lpvBits = malloc(lpbi->bmiHeader.biSizeImage)) == NULL)
        return 0;

    // Have GetDIBits convert OffscrBmp to a DIB (device-independent bitmap):
    if (!GetDIBits(OffscrDC, OffscrBmp, 0, height, lpvBits, lpbi, DIB_RGB_COLORS))
        return 0;


    //ANSI->Unicode
    LPCSTR szAnsi = filePath;
    int Size = MultiByteToWideChar(CP_ACP, 0, szAnsi, -1, NULL, 0);
    LPWSTR filename = malloc(sizeof(LPWSTR) * Size);
    MultiByteToWideChar(CP_ACP, 0, szAnsi, -1, filename, Size);
    // Create a file to save the DIB to:
    if ((BmpFile = CreateFile(filename,
        GENERIC_WRITE,
        0, NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL)) == INVALID_HANDLE_VALUE)

        return 0;

    DWORD Written;    // number of bytes written by WriteFile

                      // Write a file header to the file:
    bmfh.bfType = 19778;        // 'BM'
                                // bmfh.bfSize = ???        // we'll write that later
    bmfh.bfReserved1 = bmfh.bfReserved2 = 0;
    // bmfh.bfOffBits = ???     // we'll write that later
    if (!WriteFile(BmpFile, &bmfh, sizeof(bmfh), &Written, NULL))
        return 0;

    if (Written < sizeof(bmfh))
        return 0;

    // Write BITMAPINFOHEADER to the file:
    if (!WriteFile(BmpFile, &lpbi->bmiHeader, sizeof(BITMAPINFOHEADER), &Written, NULL))
        return 0;

    if (Written < sizeof(BITMAPINFOHEADER))
        return 0;

    // Calculate size of palette:
    int PalEntries;
    // 16-bit or 32-bit bitmaps require bit masks:
    if (lpbi->bmiHeader.biCompression == BI_BITFIELDS)
        PalEntries = 3;
    else
        // bitmap is palettized?
        PalEntries = (lpbi->bmiHeader.biBitCount <= 8) ?
        // 2^biBitCount palette entries max.:
        (int)(1 << lpbi->bmiHeader.biBitCount)
        // bitmap is TrueColor -> no palette:
        : 0;
    // If biClrUsed use only biClrUsed palette entries:
    if (lpbi->bmiHeader.biClrUsed)
        PalEntries = lpbi->bmiHeader.biClrUsed;

    // Write palette to the file:
    if (PalEntries) {
        if (!WriteFile(BmpFile, &lpbi->bmiColors, PalEntries * sizeof(RGBQUAD), &Written, NULL))
            return 0;

        if (Written < PalEntries * sizeof(RGBQUAD))
            return 0;
    }

    // The current position in the file (at the beginning of the bitmap bits)
    // will be saved to the BITMAPFILEHEADER:
    bmfh.bfOffBits = GetFilePointer(BmpFile);

    // Write bitmap bits to the file:
    if (!WriteFile(BmpFile, lpvBits, lpbi->bmiHeader.biSizeImage, &Written, NULL))
        return 0;

    if (Written < lpbi->bmiHeader.biSizeImage)
        return 0;

    // The current pos. in the file is the final file size and will be saved:
    bmfh.bfSize = GetFilePointer(BmpFile);

    // We have all the info for the file header. Save the updated version:
    SetFilePointer(BmpFile, 0, 0, FILE_BEGIN);
    if (!WriteFile(BmpFile, &bmfh, sizeof(bmfh), &Written, NULL))
        return 0;

    if (Written < sizeof(bmfh))
        return 0;

    DeleteObject(OffscrBmp);
    DeleteObject(OldBmp);
    DeleteDC(OffscrDC);
    CloseHandle(BmpFile);

    free(lpbi);
    free(lpvBits);
    free(filename);

    return 1;
}

_Bool ScreenCapture(char* filePath, int xStart, int yStart, int width, int height)
{
    // get a DC compat. w/ the screen
    HDC hDc = CreateCompatibleDC(0);

    // make a bmp in memory to store the capture in
    HBITMAP hBmp = CreateCompatibleBitmap(GetDC(0), width, height);

    // join em up
    SelectObject(hDc, hBmp);

    // copy from the screen to my bitmap
    BitBlt(hDc, 0, 0, width, height, GetDC(0), xStart, yStart, SRCCOPY);

    // save my bitmap
    _Bool ret = SaveBMPFile(filePath, hBmp, hDc, width, height);

    // free the bitmap memory
    DeleteObject(hBmp);
    DeleteDC(hDc);

    return ret;
}

main()
{
    ScreenCapture("screenshot.png", 0, 0, 1920, 1080);

    return 0;
}