You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用设备上下文时的内存泄漏问题排查求助

屏幕截图内存泄漏排查求助

我通过Python(PyQt5)与C++结合实现屏幕区域截图,将截图作为视频流显示在Label的Pixmap中,同时保存为BMP文件,当前以30fps运行用于后续生成视频文件。目前功能正常,但存在内存泄漏问题:

最初因未释放new创建的字节数组、错误使用DeleteDC而非ReleaseDC导致严重内存泄漏,修复后本应用内存无明显增长,但系统整体内存会逐渐升高,约7分钟耗尽16GB内存。怀疑问题与DC(设备上下文)相关,求排查与解决帮助。

C++代码(编译为libccreator.so)

#include "contextcreator.h"
#include <cstring>
#include <stdio.h>
#include <shlwapi.h>
#include <typeinfo>

BYTE* createContext(int x, int y, int width, int height){
    HDC hdesktop = GetDC(NULL);
    HDC memDC = CreateCompatibleDC(hdesktop);    
    HBITMAP hbitmap = CreateCompatibleBitmap(hdesktop, width, height);
    HGDIOBJ hbitmapOld = (HBITMAP)SelectObject(memDC, hbitmap);
    BitBlt(memDC, 0, 0, width, height, hdesktop, x, y, SRCCOPY|CAPTUREBLT);

    SelectObject(memDC, hbitmapOld);

    BITMAPINFO bmi = {0};
    bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);

    GetDIBits(hdesktop, hbitmap, 0, 0, NULL, &bmi, DIB_RGB_COLORS);

    BYTE* stream = new BYTE[bmi.bmiHeader.biSizeImage];
    bmi.bmiHeader.biCompression = BI_RGB;

    GetDIBits(hdesktop, hbitmap, 0, bmi.bmiHeader.biHeight, (LPVOID)stream, &bmi, DIB_RGB_COLORS);

    BYTE* data = new BYTE[14 + sizeof(bmi) + bmi.bmiHeader.biSizeImage];
    memcpy(data + 14, &bmi, sizeof(bmi));
    memcpy(&data[0] + sizeof(bmi) + 14, stream, bmi.bmiHeader.biSizeImage);

    for(int i = 0; i < 14; i++){
        data[i] = 0;  }

    delete[] stream;

    ReleaseDC(NULL, hdesktop);
    DeleteDC(memDC);

    return data;
}

void releaseData(BYTE* stream){
    delete[] stream;
}

Python调用代码

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from ctypes import *
import ctypes.wintypes as wintypes
import time
import os

os.add_dll_directory("C:/msys64/mingw64/bin")

mylib = cdll.LoadLibrary('C:/Users/amish_ac2c1jm/OneDrive/Documents/blahblah/libccreator.so')

create_context = mylib.createContext
create_context.argtypes = [c_int, c_int, c_int, c_int]
create_context.restype = POINTER(wintypes.BYTE)

release_stream = mylib.releaseData
release_stream.argtypes = [POINTER(wintypes.BYTE)]
release_stream.restype = None


class CaptureThread(QObject):
    finished = pyqtSignal()
    update_image = pyqtSignal([bytearray])

    def __init__(self, x, y, w, h, parent=None):
        super().__init__(parent)
        self.x = x
        self.y = y
        self.w = w
        self.h = h
        self.stopthread = False
        self.framenumber = 0

    def run(self):
        test_timer = time.time()
        while not self.stopthread:
            if time.time() - test_timer >= 1000/30/1000:
                test_timer = time.time()
                self.capture()
        self.finished.emit()

    def capture(self):
        bmpptr = create_context(self.x, self.y, self.w, self.h)

        data = bytearray(string_at(addressof(bmpptr.contents) + 0x22, 0x4))
        size = int.from_bytes(data, byteorder='little', signed=False) + 0x36
        data = bytearray(string_at(bmpptr, size))

        release_stream(bmpptr)

        data[0:2] = b'BM'
        value = int.from_bytes(data[0x22:0x26], byteorder='little', signed=False)
        data[2:6] = (value + 0x36).to_bytes(4, byteorder='little', signed=False)
        data[6:10] = b'\x00\x00\x00\x00'
        data[10:14] = b'\x36\x00\x00\x00'

        with open(f"images/frame{self.framenumber}.bmp", "wb") as f:
            f.write(data)
        self.framenumber += 1

        self.update_image.emit(data)

问题排查与修复方案

1. 核心泄漏原因:HBITMAP未释放

C++代码中CreateCompatibleBitmap创建的hbitmap属于GDI对象,必须手动调用DeleteObject释放,否则会造成系统级GDI资源泄漏——这就是系统内存逐渐耗尽的根本原因。

在C++的createContext函数中,添加DeleteObject(hbitmap);,放在DeleteDC(memDC);之前:

// 原代码中的释放部分
ReleaseDC(NULL, hdesktop);
DeleteObject(hbitmap); // 添加这行,释放创建的HBITMAP
DeleteDC(memDC);

2. 优化BITMAPINFO的内存计算

BITMAPINFO的sizeof(bmi)可能包含未使用的颜色表空间,建议明确设置bmi.bmiHeader.biClrUsed = 0和bmi.bmiHeader.biClrImportant = 0,避免后续内存分配冗余:

BITMAPINFO bmi = {0};
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biClrUsed = 0; // 明确不需要颜色表
bmi.bmiHeader.biClrImportant = 0;

3. 验证泄漏是否解决

修改后重新编译C++库,运行Python程序,观察系统内存变化:

  • 打开任务管理器,切换到「详细信息」标签,查看进程的「GDI对象」计数,正常情况下该数值应该稳定在一个范围内,不会持续增长。
  • 长时间运行后,系统内存不再持续攀升,说明泄漏已修复。

4. 额外优化建议

  • Python中的帧率控制改用QElapsedTimer替代time.time(),精度更高且更适配Qt线程环境:
    def run(self):
        timer = QElapsedTimer()
        timer.start()
        while not self.stopthread:
            if timer.elapsed() >= 1000/30:
                timer.restart()
                self.capture()
    
  • 避免在Python中手动修改BMP头,可在C++中直接生成完整的BMP数据,减少跨语言数据处理的复杂度和潜在错误。

内容的提问来源于stack exchange,提问作者Patrick Blanton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 10:25:18