Delphi调用FD CreateBlobStream时TStream报Sydney内存泄漏

Delphi Sydney Memory Leak Reported in TStream When Calling FD CreateBlobStream

我使用 FDQUERY 来 select 来自 MYSQL 的一条记录,它有一个 blob contining jpg 图像。一切都按预期完美运行。

我将 ReportMemoryLeaksOnShutdown:=True 添加到项目 DPR。关机时我收到消息:

"发生了意外的内存泄漏。意外的小块泄漏是: 1-12 字节:TStream x 1"

(每次调用以下代码行增加 1。) 如果我注释掉该行,则没有内存泄漏。

我试图通过源代码回溯以找到漏洞,但我就是找不到。

任何帮助或解决方法将不胜感激。非常感谢。
开发环境为:

Windows10

Delphi 10.4.2 专业版

MYSQL 8.0.25(64 位)

MS:=FDQuery1.CreateBlobStream(FDQuery1.FieldbyName('Image'),TBlobStreamMode.bmRead);`

尝试释放 MS 流变量..最终阻塞。

下面的可重现代码:

1 创建 MYSQL Table 脚本

CREATE TABLE `TestTable` (
  `pk_Id` int NOT NULL AUTO_INCREMENT,
  `Image` longblob,
  UNIQUE KEY `pk_Id_UNIQUE` (`pk_Id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=27607 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

2 Delphi 表格

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 322
  ClientWidth = 720
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object Button1: TButton
    Left = 56
    Top = 272
    Width = 129
    Height = 25
    Caption = 'Assign Blob To Stream'
    TabOrder = 0
    OnClick = Button1Click
  end
  object Button2: TButton
    Left = 512
    Top = 40
    Width = 137
    Height = 25
    Caption = 'Insert JPG into Table'
    TabOrder = 1
    OnClick = Button2Click
  end
  object FDConnection1: TFDConnection
    Params.Strings = (
      'Database= INSERT DATABASE NAME HERE'  // <-- Change to your MYSQL Database Name
      'User_Name= INSERT YOUR USERNAME HERE' // <-- Change to Your MYSQL User name
      'Password=INSET YOUR PASSWORD HERE'    // <-- Change to Your MYSQL Password
      'Server=INSER YOUR SERVER HERE'        // <-- Change to Your MYSQL Server Typically 127.0.0.1
      'DriverID=MySQL')
    Connected = True
    LoginPrompt = False
    Left = 55
    Top = 96
  end
  object FDGUIxWaitCursor1: TFDGUIxWaitCursor
    Provider = 'Forms'
    Left = 55
    Top = 32
  end
  object FDQuery1: TFDQuery
    Connection = FDConnection1
    ResourceOptions.AssignedValues = [rvDirectExecute]
    ResourceOptions.DirectExecute = True
    SQL.Strings = (
      'Select Image from testtable'
      ''
      '')
    Left = 56
    Top = 166
  end
  object FDTable1: TFDTable
    IndexFieldNames = 'pk_Id'
    Connection = FDConnection1
    TableName = 'foodforlife.testtable'
    Left = 672
    Top = 40
  end
  object FileOpenDialog1: TFileOpenDialog
    DefaultExtension = 'jpg'
    DefaultFolder = 'C:\'
    FavoriteLinks = <>
    FileTypes = <
      item
        DisplayName = ''
        FileMask = '*.jpg'
      end>
    Options = []
    Left = 432
    Top = 24
  end
end

3 Delphi 单位

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, FireDAC.Stan.Intf, FireDAC.Stan.Option,
  FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Def,
  FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Phys.MySQL,
  FireDAC.Phys.MySQLDef, FireDAC.VCLUI.Wait, FireDAC.Stan.Param, FireDAC.DatS,
  FireDAC.DApt.Intf, FireDAC.DApt, Data.DB, FireDAC.Comp.DataSet,
  FireDAC.Comp.Client, FireDAC.Comp.UI, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    FDConnection1: TFDConnection;
    FDGUIxWaitCursor1: TFDGUIxWaitCursor;
    FDQuery1: TFDQuery;
    Button2: TButton;
    FDTable1: TFDTable;
    FileOpenDialog1: TFileOpenDialog;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
Var
  MS : TStream;
begin
  MS := TStream.Create;
  try
    FDQuery1.Active:=True;
    MS:=FDQuery1.CreateBlobStream(FDQuery1.FieldbyName('Image'),TBlobStreamMode.bmRead);  // --< Memory Leak Here
    MS.Position:=0;
  finally
    ms.Free;
  end;

end;

procedure TForm1.Button2Click(Sender: TObject);
Var
fs: TFileStream;
begin

  If FileOPenDialog1.execute then fs:=TFileStream.Create(FileOPenDialog1.Files[0],fmShareDenyNone);
  try
     fs.position:=0;
     With fdtable1 do
      begin
        active:=True;
        insert;
        (FieldByName('Image') as TBlobField).LoadFromStream(fs);
        Post
      end;
  finally
    fs.Free;

  end;



end;

end.

4 Delphi DPR

program Project1;

uses
  Vcl.Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
  ReportMemoryLeaksOnShutdown:=True;
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

使用:

您可以在 Embarcadero blobs demos 中找到示例。

应用于问题中显示的代码,它变成:

var
  BStream : TStream;
begin
  BStream := MyFDProductScanQuery.CreateBlobStream(MyFDProductScanQuery.Fields[I], TBlobStreamMode.bmRead);
  try
    Base64ImgStr := TIdEncoderMIME.EncodeStream(BStream);
  finally
    BStream.Free;
  end;
end;

编辑:您的代码不符合我上面显示的内容。这就是为什么你仍然有内存泄漏。您正在创建两个流,并且只有一个是免费的:您调用 TStream.create,然后用 FDQuery1.CreateBlobStream 返回的流覆盖该值。还要注意 Base64ImgStr 发生了什么,它可能也是一个流,当您不再需要它时必须释放它。

您的 Button1Click 代码有误。你正在 Create()'ing 一个 TStream 本身的对象实例(你不应该这样做,因为 TStream 是一个抽象基础 class),但你不是 Free()'ing 那个对象,因此它被泄露了。当您创建 blob 流时,您将该对象分配给与 TStream 对象分配给的相同变量,因此您失去了对 TStream 对象的引用。

去掉对TStream.Create的调用,它不属于这里,eg:

procedure TForm1.Button1Click(Sender: TObject);
Var
  MS : TStream;
begin
  FDQuery1.Active := True; 
  MS := FDQuery1.CreateBlobStream(FDQuery1.FieldbyName('Image'), TBlobStreamMode.bmRead);
  try
    // use MS as needed...
  finally
    MS.Free;
  end;
end;