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

GDI+多线程解码JPEG随机触发内存资源不足错误如何解决

问题根因分析

你的代码存在多个核心错误,是导致随机内存不足报错的直接原因:

  1. TStreamAdapter 生命周期管理错误
    你直接把临时创建的TStreamAdapter传给TGPBitmap构造函数,作为COM对象的TStreamAdapter没有被显式持有引用,构造结束后会被自动析构,导致TGPBitmap后续读取流数据时访问无效内存,被系统误判为内存资源不足。
  2. 未检查TGPBitmap的创建结果
    你没有调用GetLastStatus校验TGPBitmap是否创建成功,就直接调用GetWidth/GetHeight方法,当TGPBitmap因为流读取失败、GDI+未初始化等原因处于无效状态时,调用成员方法必然抛出异常。
  3. 跨线程操作VCL的TBitmap对象不符合规范
    VCL的TBitmap不是线程安全对象,你仅锁定Canvas无法保护修改Width/Height/PixelFormat的操作:修改这些属性会触发内部HBITMAP的重新分配,该过程不受Canvas.Lock保护,多线程同时操作GDI对象时会出现随机资源冲突。
  4. 工作线程未初始化GDI+环境
    GDI+要求每个使用它的线程都要单独执行初始化/销毁流程,仅在主线程初始化GDI+的情况下,工作线程调用GDI+接口会随机出现各类未定义错误,包括内存不足报错。
  5. TGPGraphics创建重试逻辑无意义
    TGPGraphics创建失败的核心原因是传入的Canvas.Handle无效,重试无法解决根本问题,反而会增加不必要的线程阻塞。
解决方案

按照以下步骤修改代码即可解决问题:

  1. 修复TStreamAdapter的引用管理,新增TGPBitmap创建结果校验,移除无意义的重试逻辑:
function LoadJPEGImageGDIPlus(lStream : TStream; LoadedImage : TBitmap; LoadEXIF : Boolean{$IFDEF LOCALTRACE}; ThreadID : WideString{$ENDIF}) : Boolean;
var
  gdiBitmap  : TGPBitmap;
  gfx        : TGPGraphics;
  streamAdapter: IStream;
  gdiStatus  : TStatus;
begin
  Result    := True;
  gfx       := nil;
  gdiBitmap := nil;

  LoadedImage.Canvas.Lock;
  Try
    // 显式持有StreamAdapter的引用,soReference表示不接管传入的lStream生命周期
    streamAdapter := TStreamAdapter.Create(lStream, soReference);
    gdiBitmap := TGPBitmap.Create(streamAdapter);
    // 校验TGPBitmap是否创建成功
    if gdiBitmap.GetLastStatus <> ok then
    begin
      Result := False;
      Exit;
    end;

    LoadedImage.PixelFormat := pf32bit;
    LoadedImage.Width  := gdiBitmap.GetWidth;
    LoadedImage.Height := gdiBitmap.GetHeight;

    gfx := TGPGraphics.Create(LoadedImage.Canvas.Handle);
    gdiStatus := gfx.GetLastStatus;
    If gdiStatus = ok then
    Begin
      gdiStatus := gfx.DrawImage(gdiBitmap,0,0,LoadedImage.Width,LoadedImage.Height);
      If gdiStatus <> ok then
      Begin
        Result := False;
      End;
      FreeAndNil(gfx);
    End
    else
    Begin
      Result := False;
    End;
  Except
    on E : Exception do
    Begin
      Result := False;
    End
    else
    Begin
      Result := False;
    End;
  End;

  If gdiBitmap <> nil then FreeAndNil(gdiBitmap);
  If gfx <> nil then FreeAndNil(gfx);

  LoadedImage.Canvas.Unlock;
end;
  1. 在所有调用该函数的工作线程入口处新增GDI+初始化,线程退出前执行销毁:
// 线程执行入口代码
var
  gdiplusToken: ULONG_PTR;
  gdiplusInput: TGDIPlusStartupInput;
begin
  gdiplusInput.GdiplusVersion := 1;
  gdiplusInput.DebugEventCallback := nil;
  gdiplusInput.SuppressBackgroundThread := False;
  gdiplusInput.SuppressExternalCodecs := False;
  GdiplusStartup(gdiplusToken, @gdiplusInput, nil);
  try
    // 线程原有业务逻辑,包括调用LoadJPEGImageGDIPlus
  finally
    GdiplusShutdown(gdiplusToken);
  end;
end;
  1. (可选优化)避免跨线程操作UI层的TBitmap:在工作线程中创建临时TBitmap完成解码,再通过线程同步的方式将像素数据复制到UI线程的目标TBitmap中,彻底规避跨线程GDI对象操作的冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 21:00:02