JCL Debug在Delphi中的应用

6 篇文章 0 订阅
1 篇文章 0 订阅

1.JCL概念

        JEDI 代码库 (JCL) 由一组经过全面测试并完整记录的代码组成实用程序函数和非视觉类,可以立即在您的Delphi中重用和 C++ Builder 项目。JCL主要用于采集程序异常报错的堆栈信息,从而能更快的定位到异常的位置,无需拿到相关的配置去运行代码debug断点跟踪。

2.JCL安装

        JCL为开源项目,代码的位置在GitHub,请自行搜索。下载jcl,还要下载project-jedi/jedi里的2个INC文件并放到jcl-master\jcl\source\include\jedi目录里。运行jcl\install.bat 安装,没有DPK工程文件。运行bat文件,弹出下面的界面,点install即可。

安装程序会默认加载电脑所有的Delphi编译器,点击安装之前,先选择“MPL 1.1 License”勾选agree,然后默认安装即可,记住安装之前要关闭Delphi编译器。

3.JCL功能简介

        安装成功之后,打开Delphi编译器,新建一个项目,然后你可以点击project菜单下面看到JCL Debug expert选项,下面有3个菜单

  1. Generate .jdbg files (使用可执行文件生成和部署 JDBG 文件。 这是基于 MAP 文件的二进制文件,但其大小通常约为 原始 MAP 文件的 12%。您可以通过 jcl\experts\debug\tools 文件夹中的 MapToJdbg 工具生成它。与MAP文件相比,其优点是体积更小,安全性更好 的文件内容,因为它不是纯文本文件,而且它也 包含一个校验和。IDE 专家可以自动创建此文件 当项目编译时(见下文))
  2. Insert JDBG data into the binary(将 JCL 调试信息插入到可执行文件中。这添加的数据的大小类似于JDBG 文件,但会插入直接添加到可执行文件中。这可能是最好的选择,因为它结合了小尺寸的包含数据,并且不需要部署其他文件。IDE EA 可以自动插入这些信息)
  3. Delete map files after conversion(转换完成后自动删除 MAP 文件)

每个选项下面都有四个subitem:Always enabled(一直开启)、Enabled for this project(只对当前项目开启)、Disabled for this project(只对当前项目禁用)、Always disabled(一直禁用)。我们一般推荐使用第二种方式,虽然这将增大exe程序的体积,但是增大的限度有限,并且发布程序的时候不必携带MAP或者JDBG文件一起发布,最为便捷。

        当我们给一个项目开启jcldebug的时候我们需要在Delphi编辑器中选择MAP file为“Detailed”,在Project->options->Linker 中可以找到。如果没开启的话,在编译程序的时候jcl会给出相应的提示,选择接受即可。

4.JCL的业务集成

        我们代码里面集成JCL一般都做全局性的部署,我们程序可以通过try..except..end代码块显示的捕获异常,如果异常块未被try..except..end捕捉,那么异常会向上传播。如果异常在应用程序的最顶层仍未被捕获,Delphi 的运行时环境(VCL 或 FMX 框架)可能会提供自己的异常处理机制。所以我们需要对Delphi项目的2块内容做处理:

a.已经在代码里面使用try..except..end,并且异常已经被Exception捕捉的,在我们show出异常的时候除了E.Message可能需要提供更多的信息以用来定位异常代码发生的堆栈数据。

b.异常的发生未显示的使用try..except..end,异常信息被VCL、消息、操作系统捕获的时候,我们需要从JCL中获取当前异常的堆栈并通过自定义弹窗的方式来show出来。

针对这两块内容我们的处理方式是:

a.提供一个函数获取当前异常的堆栈并返回格式化的字符串,在Except中获取的E.message的时候同时取得堆栈数据。

b.利用JCL自定义Application.OnException过程,并封装统一格式的弹窗界面来展示异常。

c.利用JCL(JclAddExceptNotifier)添加通知过程,当程序不管在任何位置发生的异常都会调用到这个通知的procedure过程,我们可以自定义这个过程并形成文件日志的记录。

以下附上部分参考代码:

program ExceptionTest;

uses
  Forms,
  JclDebug,
  JclHookExcept,
  utMain in 'utMain.pas' {Form1},
  utJCLFrmExcept in 'utJCLFrmExcept.pas' {FrmExcept},
  utDataM in 'utDataM.pas' {DataModule1: TDataModule},
  utJCLException in 'utJCLException.pas';

{$R *.res}

begin
  Application.Initialize;
  //初始化 JclStackTrackingOptions
  Include(JclStackTrackingOptions, stRawMode);
  Include(JclStackTrackingOptions, stStaticModuleList);
  //开始JCL跟踪
  JclStartExceptionTracking;
  //注册通知程序
  JclAddExceptNotifier(EJCLException.LogException);
  //截获程序异常
  Application.OnException := EJCLException.ShowException;
  Application.CreateForm(TDataModule1, DataModule1);
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
unit utJCLException;

interface

uses
  Windows, SysUtils, Classes, JclDebug, Dialogs, utJCLFrmExcept, Forms;

type
  // 定义一个自定义异常类
  EJCLException = class
  private
  public
    {-------------------------------------------------------------------------------
      函数名:    ShowException
      函数说明:  显示出一个最近的一次异常详细信息 此过程和Application.OnException绑定,
                 用于处理代码中未用try except捕捉,而由应用程序自动捕捉的异常信息,
                 请注意try except包裹的代码块不会触发Application.OnException。
      作者:      trump
      日期:      2024-08-02
      参数:      Sender: TObject; E: Exception
      返回值:    无
    -------------------------------------------------------------------------------}
    class procedure ShowException(Sender: TObject; E: Exception);
    {-------------------------------------------------------------------------------
      函数名:    LogException
      函数说明:  记录异常报错的日志。与JclAddExceptNotifier事件共同绑定,JclAddExceptNotifier
                 注册一个事件用于响应所有的异常,并通过JCLLogException来生成日志信息
      作者:      trump
      日期:      2024-08-02
      参数:      ExceptObj: TObject; ExceptAddr: Pointer; IsOS: Boolean
      返回值:    无
    -------------------------------------------------------------------------------}
    class procedure LogException(ExceptObj: TObject; ExceptAddr: Pointer; IsOS: Boolean);
    {-------------------------------------------------------------------------------
      函数名:    GetExceptionInfo
      函数说明:  获取当前最近的一次异常的报错信息,此函数一般用作在try except中在弹出
                 异常的时候取到E.messgae之后再调用这个函数获取到详细的堆栈信息。
      作者:      trump
      日期:      2024-08-02
      参数:      无
      返回值:    异常信息
    -------------------------------------------------------------------------------}
    class function GetExceptionInfo:string;
  end;

var
  JCLLogList:TStringlist;
  JCLShowList:TStringlist;

implementation


{ EJCLException  }

procedure WriteExceptionLog(sLogInfo: string);
var
  sDate, sTime, sDirName, sInfo: string;
  sFName: OleVariant;
  fLog: TextFile;
begin
  sDate := FormatDatetime('YYYYMMDD', Now);
  sDirName := ExtractFilePath(Application.ExeName) + 'Log\';
  if not DirectoryExists(sDirName) then CreateDir(sDirName);
  if not DirectoryExists(sDirName) then   //如果创建不成功
    CreateDirectory(PChar(sDirName), nil);
  sFName := sDirName + sDate + '.log'; 
  AssignFile(fLog, sFName);
  if not FileExists(sFName) then
    ReWrite(fLog)
  else
    Append(fLog);
  sTime := FormatDatetime('YYYY-MM-DD HH:NN:SS', Now);
  sInfo := sTime + '  msg: ' + sLogInfo + '';
  WriteLn(fLog, sInfo);
  CloseFile(fLog);
end;

class function EJQException.GetExceptionInfo: string;
var
  ModInfo: TJclLocationInfo;
  sLogMsg:string;
  iListCnt:Integer;
begin
  if JCLShowList = nil then JCLShowList := TStringList.Create;
  JCLShowList.Clear; sLogMsg := '';
  JclLastExceptStackListToStrings(JCLShowList, False, False, False, False);
  ModInfo := GetLocationInfo(ExceptAddr);
  sLogMsg := JCLShowList.Text;
  Result := Format(cstExceptionShow, [ModInfo.UnitName, ModInfo.ProcedureName,
                                        ModInfo.SourceName, IntToStr(ModInfo.LineNumber),
                                        sLogMsg]);
end;

class procedure EJQException.LogException(ExceptObj: TObject; ExceptAddr: Pointer; IsOS: Boolean);
var
  sLogMsg,sEMsg,sOsMsg,sEnCrp: string;
  ModInfo: TJclLocationInfo;
begin
  if JCLLogList = nil then JCLLogList := TStringList.Create;
  JCLLogList.Clear;
  JclLastExceptStackListToStrings(JCLLogList, False, False, False, False);
  if ExceptObj is Exception then
    sEMsg := Exception(ExceptObj).Message
  else sEMsg := '';
  if IsOS then sOsMsg := '1' else sOsMsg := '';
  ModInfo := GetLocationInfo(ExceptAddr);
  sLogMsg := Format(cstExceptionLog, [ExceptObj.ClassName,sEMsg,sOsMsg,
                                      ModInfo.UnitName, ModInfo.ProcedureName,
                                      ModInfo.SourceName, IntToStr(ModInfo.LineNumber)]);
  sLogMsg := sLogMsg + #13#10 + JCLLogList.Text;
  WriteExceptionLog(sLogMsg);
end;

class procedure EJQException.ShowException(Sender: TObject; E: Exception);
var
  ModInfo: TJclLocationInfo;
  iListCnt:Integer;
  sMsgShow:string;
begin
  if JCLShowList = nil then JCLShowList := TStringList.Create;
  JCLShowList.Clear; sMsgShow := '';
  JclLastExceptStackListToStrings(JCLShowList, False, False, False, False);
  ModInfo := GetLocationInfo(ExceptAddr);
  sMsgShow := JCLShowList.Text;
  CrtFrmExcept(E.ClassName, ModInfo.UnitName, ModInfo.ProcedureName, ModInfo.SourceName,
               IntToStr(ModInfo.LineNumber), E.Message, sMsgShow);
end;

end.

ps:程序的堆栈信息比较敏感,我们做日志记录的时候或者弹窗的时候,需要做好加密和数据筛选。

  • 16
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Delphi JCL控件是Delphi开发环境的一个非常实用的组件库。JCL是JEDI Code Library的缩写,是一个由开源社区JEDI项目开发的自由软件。它包含了很多基本的控件和组件,用于增强和扩展Delphi IDE的功能。 JCL控件提供了许多在Delphi内置控件没有的功能和特性。例如,JclSysInfo组件可以方便地获取系统信息,如操作系统版本、CPU类型、内存大小等。JclDebug组件可以提供调试应用程序的功能,例如捕获异常、记录调试信息等。JclMath组件可以进行高级的数学计算,包括矩阵运算、线性代数等。JclRegistry组件可以方便地读写Windows注册表,包括读取和写入键值、创建和删除项等。 除了以上提到的功能,JCL控件还包括了许多其他实用的控件和组件,如日期选择器、提示框、对话框、进度条等。这些控件都经过了开发者的精心设计和优化,可以在Delphi应用程序快速集成和使用。 使用JCL控件不仅可以提高开发效率,还可以避免重复造轮子的问题。JCL控件具有良好的文档和示例,可以帮助开发者快速上手并了解如何使用这些控件。此外,JCL控件是开源项目,开发者可以根据自己的需求进行自定义和扩展,满足特定的项目需求。 总之,Delphi JCL控件是一个非常有用的组件库,可以扩展和增强Delphi开发环境的功能。开发者可以借助JCL控件,提高开发效率,减少重复工作,并开发出更加高效和功能丰富的应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值