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是否初始化就使用List2、没有释放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 除了要把一些对象参数改为接口参数外,还要及时清理接口的引用。