当前位置:首页 > 技术 > 正文内容

Delphi 中回调函数的实践应用与代码示例

访客 技术 2026年9月28日 13

回调函数(Callback Function)是一种常见的编程范式,它允许我们将一段可执行代码作为参数传递给另一个函数或方法。当接收方在特定时机需要执行某些操作时,它会"回调"这个传入的代码。这种机制在事件处理、异步操作和模块间解耦方面发挥着重要作用。在 Delphi 中,回调函数通常通过过程指针(Procedure Pointers)或方法指针(Method Pointers)来实现。

示例一:面向对象的方法指针回调

第一个示例展示如何将一个对象的方法作为回调函数传递。这种方式在 Delphi 中非常常见,因为它保留了方法的对象上下文。

首先,在 Unit2 中定义一个方法指针类型和一个接受该回调类型的函数:

// Unit2.pas
unit Unit2;

interface

uses
  Windows, Classes;

// 定义一个方法指针类型,该方法接受一个字符串参数,不返回任何值
// 这种方法指针类型包含了 'of object',表明它指向一个类的方法
type
  TMessageFeedbackMethod = procedure(const AFeedbackMessage: string) of object;

// 声明一个函数,它接受一个回调方法和一个双精度浮点数
// 根据数值的正负零状态,调用传入的回调方法并传递相应的消息
function AnalyzeAndNotify(ACallback: TMessageFeedbackMethod; AInputNumber: Double = 0): Integer;

implementation

function AnalyzeAndNotify(ACallback: TMessageFeedbackMethod; AInputNumber: Double = 0): Integer;
begin
  Result := 0; // 默认返回0,表示操作成功完成

  if AInputNumber > 0 then
  begin
    ACallback('输入的是一个正数');
  end
  else if AInputNumber = 0 then
  begin
    ACallback('输入的是零');
  end
  else
  begin
    ACallback('输入的是一个负数');
  end;
end;

end.

接着,在 Unit1 (主窗体单元) 中,我们实现一个匹配 TMessageFeedbackMethod 签名的方法,并将其传递给 AnalyzeAndNotify 函数:

// Unit1.pas
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TMainForm = class(TForm)
    InputValueEdit: TEdit;
    ResultDisplayEdit: TEdit;
    ProcessNumberButton: TButton;
    procedure ProcessNumberButtonClick(Sender: TObject);
  private
    { Private declarations }
    // 这是一个类方法,签名与 Unit2 中定义的 TMessageFeedbackMethod 匹配
    procedure UpdateFeedback(const AMessage: string);
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

uses Unit2; // 引用包含回调逻辑的单元
{$R *.dfm}

procedure TMainForm.UpdateFeedback(const AMessage: string);
begin
  ResultDisplayEdit.Text := AMessage; // 将接收到的消息显示在界面的编辑框中
end;

procedure TMainForm.ProcessNumberButtonClick(Sender: TObject);
var
  CurrentValue: Double;
begin
  // 尝试将 InputValueEdit 中的文本转换为浮点数,如果转换失败则默认为 0.0
  CurrentValue := StrToFloatDef(InputValueEdit.Text, 0.0);
  // 调用 Unit2 中的 AnalyzeAndNotify 函数,并将 UpdateFeedback 方法作为回调传入
  AnalyzeAndNotify(UpdateFeedback, CurrentValue);
end;

end.

对应的窗体 DFM 结构如下:

object MainForm: TMainForm
  Left = 100
  Top = 100
  Width = 300
  Height = 250
  Caption = 'Delphi 方法指针回调示例'
  object InputValueEdit: TEdit
    Left = 50
    Top = 30
    Width = 150
    Height = 21
    Text = '0.0'
    TabOrder = 0
  end
  object ResultDisplayEdit: TEdit
    Left = 50
    Top = 70
    Width = 150
    Height = 21
    ReadOnly = True // 结果显示框设为只读
    TabOrder = 1
  end
  object ProcessNumberButton: TButton
    Left = 75
    Top = 120
    Width = 100
    Height = 25
    Caption = '处理数值'
    TabOrder = 2
    OnClick = ProcessNumberButtonClick
  end
end

示例二:将类方法转换为纯过程指针回调

在某些特定场景下,例如与一些不理解"对象"概念的旧 C 库或低级 API 交互时,我们可能需要将 Delphi 的类方法转换为一个不带对象上下文的纯过程指针。Delphi 的 TMethod 结构可以帮助我们实现这一点,但需要特别注意其潜在的风险,因为纯过程指针不会自动传递对象实例 (Self)。

首先,在 Unit3 (为了清晰,这里使用新单元名) 中定义一个纯过程指针类型和一个接受该回调的函数:

// Unit3.pas
unit Unit3;

interface

uses
  SysUtils, Dialogs;

// 定义一个纯过程指针类型,它接受一个整数变量的引用并返回一个整数
// 注意:这里没有 'of object' 关键字
type
  TIntProcessorProc = function(var AInput: Integer): Integer;

// 声明一个函数,它接受一个纯过程指针并执行它
function ExecutePureCallback(ACallback: TIntProcessorProc): Integer;

implementation

function ExecutePureCallback(ACallback: TIntProcessorProc): Integer;
var
  TempInt: Integer;
begin
  TempInt := 500; // 提供一个初始值
  // 调用传入的纯过程指针。此时,如果回调函数是类方法,
  // 它将无法直接访问 Self,除非其逻辑不依赖于对象状态。
  Result := ACallback(TempInt);
  ShowMessage('ExecutePureCallback: 回调函数执行完毕,返回 ' + IntToStr(Result) + '. 输入参数在回调中变为 ' + IntToStr(TempInt));
end;

end.

然后,在 Unit1 中,我们编写一个类方法,并演示如何将其代码地址提取并作为纯过程指针传递:

// Unit1.pas (继续在之前的 Unit1 中添加)
// ...
type
  TMainForm = class(TForm)
    // ... (示例一的控件和方法)
    CallPureProcButton: TButton; // 新增按钮
    procedure CallPureProcButtonClick(Sender: TObject);
  private
    // ... (示例一的方法)
    // 这是一个类方法,但其签名与 Unit3 中定义的 TIntProcessorProc 纯过程指针类型匹配
    function TransformAndCalculate(var ANumber: Integer): Integer;
  public
    { Public declarations }
  end;
// ...

implementation

uses Unit2, Unit3; // 引用两个回调单元
{$R *.dfm}

// ... (示例一的方法实现)

function TMainForm.TransformAndCalculate(var ANumber: Integer): Integer;
begin
  // 注意:在此方法中,我们没有直接使用 Self 成员变量或方法,
  // 从而使其可以安全地作为纯过程指针被调用。
  ANumber := ANumber + 100; // 修改传入的变量
  Result := 300 + ANumber;
  ShowMessage('TransformAndCalculate: 类方法被调用,计算结果 ' + IntToStr(Result) + '. 输入参数修改为 ' + IntToStr(ANumber));
end;

procedure TMainForm.CallPureProcButtonClick(Sender: TObject);
var
  MethodReference: TMethod; // Delphi 内部用于表示方法指针的结构
  PureProcedurePointer: TIntProcessorProc;
begin
  // 将类方法 TransformAndCalculate 赋值给 MethodReference
  // TMethod 结构包含 Data (指向对象实例,即 Self) 和 Code (指向方法的入口地址) 两个字段
  MethodReference.Data := Self; // 理论上指向当前窗体实例
  MethodReference.Code := @TMainForm.TransformAndCalculate; // 获取方法的代码地址

  // 将方法的代码地址强制转换为纯过程指针类型
  // 这一步是关键,它使得一个类方法能够被当作一个不带对象上下文的普通函数指针来使用
  PureProcedurePointer := TIntProcessorProc(MethodReference.Code);

  // 调用 Unit3 中的 ExecutePureCallback 函数,传入纯过程指针
  ExecutePureCallback(PureProcedurePointer);
end;

end.

对应的窗体 DFM 结构新增部分:

object MainForm: TMainForm
  // ... (示例一的现有属性和控件)
  object CallPureProcButton: TButton
    Left = 75
    Top = 160 // 放置在第一个按钮下方
    Width = 100
    Height = 25
    Caption = '调用纯过程回调'
    TabOrder = 3
    OnClick = CallPureProcButtonClick
  end
end

重要提示: 将类方法转换为纯过程指针并传递时,需要确保该类方法在执行期间不需要访问 Self (即对象自身的字段或方法),否则程序可能会因为无法找到正确的对象上下文而崩溃或产生不可预测的行为。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。