Partage
  • Partager sur Facebook
  • Partager sur Twitter

Fuite mémoire capture d'écran WinAPI

20 septembre 2019 à 19:11:52

Salut à tous

J'ai trouvé ce code (visible en dessous) sur Internet, il me sert à faire des captures de l'écran et ça m'est bien pratique. le problème c'est que y'a une fuite de mémoire et à chaque appel de la fonction ScreenCapture, un peu de mémoire en plus est utilisée par le programme. Plus la screenshot effectuée est grosse, plus la fuite est importante. Une screenshot de 1920*1080 pixels engendrera plus de mémoire "perdue" qu'une 100*100 pixels.

Le petit code dans le main du programme ci dessous est juste là pour montrer à quel point l'exécution ralenti (à cause de toutes les fuites de RAM cumulées) au fur et à mesure que les screenshots sont faites.

#include <stdio.h>
#include <Windows.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)
{
	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;
}

int main()
{
	int i = 0;

	while (1)
	{
		ScreenCapture("Screenshot.png", 0, 0, 100, 100);//Screenshot de 100*100 pixels

		printf("%d ", i++);
	}
}

Après 2min d'exécution, la RAM est passée de 500ko à 5Mo et on voit clairement que les chiffres qui s'affichent dans la console, ceux qui comptent le nombre de screen qui se sont effectuées, défilent clairement moins vite et ça met aussi le PC en PLS, j'ai le droit à un bel effet Superman en écrivant ce que vous lisez maintenant...

Si vous avez une solution...

-
Edité par Tom Clabault 20 septembre 2019 à 19:13:20

  • Partager sur Facebook
  • Partager sur Twitter
20 septembre 2019 à 19:35:13

Hello,

Il y a un paquet de return à partir de la ligne 45 qui ne libèrent pas la mémoire.. Il faudrait (avec un debugger ou avec printf() ) vérifer si ta fonction arrive vraiment à sa fin.

Edit 1: en plus, le code de retour de SaveBMPFile() et ScreenCapture() n'est pas testé

Edit 2: et un code publié sur internet n'est pas exempt d'erreur, loin s'en faut

-
Edité par edgarjacobs 20 septembre 2019 à 19:47:01

  • Partager sur Facebook
  • Partager sur Twitter

On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent

20 septembre 2019 à 19:37:10

Merci de ta réponse

Je suis allé voir sur un vieux forum où j'avais posé la question y'a un bout de temps et ils ont répondu avec un code qui fonctionne, sans fuites de mémoire que voici:

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)
{
	while (1)
		ScreenCapture(L"screenshot.png", 0, 0, 100, 100);

	return 0;
}

-
Edité par Tom Clabault 20 septembre 2019 à 19:37:32

  • Partager sur Facebook
  • Partager sur Twitter
20 septembre 2019 à 19:50:40

Et ?
  • Partager sur Facebook
  • Partager sur Twitter

On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent

6 octobre 2019 à 23:30:13

Désolé de la réponse tardive

Le problème venait majoritairement des lignes comme celle là:

BitBlt(hDc, 0, 0, width, height, GetDC(0), xStart, yStart, SRCCOPY);

 GetDC(0) doit être libéré sauf qu'utilisé comme ça, directement dans l'appel de la fonction, impossible de récupérer l'HDC qu'il crée pour ensuite le libérer. Il faut plutôt faire:

HDC dc = GetDC(0);

BitBlt(hDc, 0, 0, width, height, dc, xStart, yStart, SRCCOPY);

ReleaseDC(dc);

-
Edité par Tom Clabault 6 octobre 2019 à 23:47:17

  • Partager sur Facebook
  • Partager sur Twitter