惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 司徒正美
M
MIT News - Artificial intelligence
博客园_首页
IT之家
IT之家
L
LangChain Blog
D
DataBreaches.Net
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Google DeepMind News
Google DeepMind News
Blog — PlanetScale
Blog — PlanetScale
人人都是产品经理
人人都是产品经理
博客园 - Franky
云风的 BLOG
云风的 BLOG
罗磊的独立博客
量子位
G
Google Developers Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 【当耐特】
博客园 - 叶小钗
S
SegmentFault 最新的问题
Stack Overflow Blog
Stack Overflow Blog
B
Blog
T
Tailwind CSS Blog
A
About on SuperTechFans
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com

博客园 - 姜乂元

Notepad++ 使用 及 插件开发 记录 EverythingSDK中Everything.h转的Everything.pas 可以关闭显示器的软件 win10 x64 解决hlp文件打开问题 支持正则表达式更名的命令行工具 一个类似ALTRun的程序 一个测试程序迭代的故事05 一个测试程序迭代的故事04 一个测试程序迭代的故事03 一个测试程序迭代的故事02 一个测试程序迭代的故事01 使用Calibre自带工具批量转换电子书格式 用批处理安装卸载程序 Delphi XE7试用记录2 Delphi XE7试用记录1 开发程序时在主要功能之外应当考虑的问题 开心网外挂 天下的导游一般般 使用delphi5加xkol开发的程序还真小。
Delphi5和Delphi7属性编辑器内存泄漏问题的解决
姜乂元 · 2020-03-10 · via 博客园 - 姜乂元

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 除了要把一些对象参数改为接口参数外,还要及时清理接口的引用。