Delphi5和Delphi7属性编辑器内存泄漏问题的解决
问题:
用 Delphi5 测试一个项目时用到了 DsgnIntf.pas 单元。运行后提示内存泄漏。查看项目代码,没有发现泄漏点。
FastMM4 显示的内存泄漏报告提示与 DsgnIntf.pas 单元有关,新建一个测试项目,引用 DsgnIntf.pas 单元,编译运行,没有提示内存泄漏。
在 initialization 初始化段注册几个属性编辑器,编译运行,果然提示内存泄漏,详细报告内容如下:
该内存块分配于线程 0xB10, 当时的堆栈跟踪(返回地址):
402962 [System][@GetMem]
40477E [System][@New]
446105 [DsgnIntf.pas][DsgnIntf][RegisterPropertyEditor][3295]
446364 [testMemory.pas][testMemory][RegPropEdit][27]
4463F1 [testMemory.pas][testMemory][initialization][37]
40397C [System][InitUnits]
4039D2 [System][@StartExe]
40648D [SysInit][@InitExe]
446594 [E:\Delphi\zproplst\testMem.dpr][testMem][initialization]
76D8336A [BaseThreadInitThunk]
776A9902 [RtlInitializeExceptionChain]
查看 RegisterPropertyEditor 函数,关键代码为一个指针申请了内存,加入到 ComponentClassList 中,ComponentClassList 是一个 TList,在单元的 finalization 终止化段释放了,但没有相关代码释放每个内部项的内存。同样 PropertyMapperList 也存在这样的问题。
解决方法:
复制 DsgnIntf.pas 单元到测试项目的目录中,在该单元最后加入两个函数。
procedure DisposePropertyClassList;
var
i: Integer;
begin
if PropertyClassList = nil then
Exit;
for i := 0 to PropertyClassList.Count - 1 do
Dispose(PropertyClassList[i]);
end;
procedure DisposePropertyMapperList;
var
i: Integer;
begin
if PropertyMapperList = nil then
Exit;
for i := 0 to PropertyMapperList.Count - 1 do
Dispose(PropertyMapperList[i]);
end;
在 finalization 终止化段开始插入这两个函数。最终 finalization 终止化段如下:
finalization
DisposePropertyClassList; //uu,添加,原来只是释放了list,没有释放内部项的内存
DisposePropertyMapperList; //uu,添加,原来只是释放了list,没有释放内部项的内存
FreeAndNil(EditorGroupList);
FreeAndNil(PropertyClassList);
FreeAndNil(ComponentClassList);
FreeAndNil(PropertyMapperList);
FreeAndNil(InternalPropertyCategoryList);
再次编译运行测试项目,没有发现内存泄漏。看来手工分配内存一定要注意释放,连专业的程序员也难免失误。发布这段内容,希望能帮助需要使用 DsgnIntf.pas 单元的开发者。
后记:
再把项目用 Delphi7 编译试试,结果也发现内存泄漏。Delphi7 使用了不同的方法注册属性编辑器,但仍然用了几个List记录相关信息,相关代码移到了 DesignEditors.pas 单元。这个单元定义了一个函数 procedure FreeEditorGroup(Group: Integer); 。这个函数释放了这几个List的内部项。可是下了断点后,没有地方运行这个函数。看一下函数的内容,竟然发现了两个严重问题,1、没有判断List是否初始化就使用List,2、没有释放List本身。
复制 DesignEditors.pas 单元到测试项目的目录中,在该单元最后加入一个函数。
procedure FreeList;
var
I: Integer;
P: PPropertyClassRec;
C: PComponentClassRec;
M: PPropertyMapperRec;
SelectionDef: TSelectionEditorDefinition;
begin
// Release all property editors associated with the group
if PropertyClassList <> nil then
begin
I := PropertyClassList.Count - 1;
while I > -1 do
begin
P := PropertyClassList[I];
// if P.Group = Group then
begin
PropertyClassList.Delete(I);
Dispose(P);
end;
Dec(I);
end;
FreeAndNil(PropertyClassList);
end;
// Release all component editors associated with the group
if ComponentClassList <> nil then
begin
I := ComponentClassList.Count - 1;
while I > -1 do
begin
C := ComponentClassList[I];
// if C.Group = Group then
begin
ComponentClassList.Delete(I);
Dispose(C);
end;
Dec(I);
end;
FreeAndNil(ComponentClassList);
end;
// Release all property mappers associated with the group
if PropertyMapperList <> nil then
begin
for I := PropertyMapperList.Count - 1 downto 0 do
begin
M := PropertyMapperList[I];
// if M.Group = Group then
begin
PropertyMapperList.Delete(I);
Dispose(M);
end;
end;
FreeAndNil(PropertyMapperList);
end;
// Release all selection editors associated with the group
if SelectionEditorDefinitionList <> nil then//uu,TObjectList,应该自己能释放内部对象
begin
for I := SelectionEditorDefinitionList.Count - 1 downto 0 do
begin
// SelectionDef := SelectionEditorDefinitionList[I];
// if SelectionDef.FGroup = Group then
SelectionEditorDefinitionList.Delete(I);
end;
FreeAndNil(SelectionEditorDefinitionList);
end;
// Notify everyone else that have similar registration lists that the group
// is being unloaded.
if Assigned(GroupNotifyList) then
// for I := GroupNotifyList.Count - 1 downto 0 do
// TGroupChangeProc(GroupNotifyList[I])(Group);
FreeAndNil(GroupNotifyList); //内部对象不是在这里创建的,不用释放,释放自己就可以
// Free the group ID for use by another group
// if (Group >= 0) and (Group < EditorGroupList.Size) then
// EditorGroupList[Group] := False;
if EditorGroupList <> nil then
FreeAndNil(EditorGroupList);
end;
在 finalization 终止化段开始插入这个函数,其他内容不变。编译。。。提示 CreateResFmt 函数没有相应的重载形式!看了一下该函数的定义,应该可以被调用,检查工程选项,发现 Compliler 页的 Typed @ operator 打开了。关闭这个选项,再编译。。。运行。。。关闭。没有发现内存泄漏。
另外需要注意一点:Delphi7 使用了接口技术调用属性编辑器,Delphi5 的项目转 Delphi7 除了要把一些对象参数改为接口参数外,还要及时清理接口的引用。