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.
使用:
1 运行 在您有权访问的 MYSQL 数据库中创建脚本
2 为上面创建的 table 插入 MYSQL 数据库参数
FDConnection1
3 编译并 运行
4 单击标题为将 JPG 插入 Table 的按钮以显示打开的文件
您将从中 select 一个 jpg 文件的对话框。当你执行
对话框它将保存到 MYSQL table
5 单击标题为将 Blob 分配给流的按钮。这将分配
blob 到流并为每个按钮单击创建内存泄漏
您可以在 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;
我使用 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.
使用:
1 运行 在您有权访问的 MYSQL 数据库中创建脚本
2 为上面创建的 table 插入 MYSQL 数据库参数 FDConnection1
3 编译并 运行
4 单击标题为将 JPG 插入 Table 的按钮以显示打开的文件 您将从中 select 一个 jpg 文件的对话框。当你执行 对话框它将保存到 MYSQL table
5 单击标题为将 Blob 分配给流的按钮。这将分配 blob 到流并为每个按钮单击创建内存泄漏
您可以在 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;