【Delphi练习】回调函数实现随机数回显

问题描述

.dll文件产生/终止线程,每一秒产生一个随机数,然后用回调函数显示在edit上
在这里插入图片描述在这里插入图片描述

考点

  1. 计时器控件
Timer1: TTimer;
procedure TForm1.Timer1Timer(Sender: TObject);
可以控制刷新时间、是否开始,事件中,选择OnTimer后面的空白处,双击。进去后写定时器到点触发后的代码
  1. 随机函数
//指定范围的随机数函数
function Rand(min,max:Integer):Integer;
begin
  randomize;//初始化
  Result:=random(max) mod (max-min+1)+min;
end;
//调用
Rand(100,1000);
显示结果是100~1000之间的随机数
  1. 调用函数作为另一个函数的参数
type
  TFunc = function(): String of object;
  TFunc1 = procedure(a: Integer) of object;
add(f: TFunc)
  1. 回调函数
procedure TForm1.abc;
begin
  showmessage('这是回调函数测试');
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
   myTest(abc);
end;
function TForm1.myTest(f:TFunc):string;
begin
   f();
end;
  1. DLL文件生成
function MyDllFunc(x:integer):integer;stdcall;
begin
  Result := x*x;
end;
exports
  MyDllFunc ;

静态调用

implementation
{$R *.dfm}
function MyDllFunc(x:integer):integer;stdcall;
external 'Project2.dll'

procedure TForm1.Button1Click(Sender: TObject);
begin
  Edit2.Text := inttostr(MyDllFunc(strToint(Edit1.Text)));
end;

动态调用

implementation
{$R *.dfm}
type
  Test = function (x:integer):Integer; stdcall;

procedure TForm1.Button1Click(Sender: TObject);
var
  myDiaoYong:TmyDiaoYong;
begin
  MyTest:=Test(GetProcAddress(Loadlibrary('Project2.dll')  ,'MyDllFunc') );
  Edit2.Text := inttostr(MyTest (strToint(Edit1.Text)));
  FreeLibrary(Loadlibrary('Project2.dll')  ;
end;
end.
  1. 线程创建
    线程唤醒和挂起、构造以及执行函数的重写
  2. 类库使用
    这里用到的—TThread线程类,是一个基类 ,而有一些类以及是继承类,比如TList,线程队列类,就像是一个已经写好的线程池
    无论是基类还是一个继承类,再使用的时候:
    如果需要添加新成员,就继承过来,创建一个继承类,再使用的时候就要创建对象了,创建对象的时候只是一个声明,对象是需要实例化的
    1)创建继承类(类型)
    需要注意的是,继承过来的类里,如果有抽象函数就需要重写的,就好比这个线程类里的exec函数,就必须重写
    2)建类对象
    3)对象需要实例化

解题过程

  1. 主程序
    (1)edit
    (2)bottom——start按钮
    (3)回调函数——display
  2. .dll文件
    (1)线程开启/关闭函数
    (2)线程执行函数——随机数产生
    (3)回调处理函数——把产生的随机数作为参数传给回调函数
  3. 描述
    按下start按钮后 , 产生线程,线程每一秒产生一个随机数,并且在edit上显示出来,由于线程的产生和随机数的产生是在.dll内,不能直接使用控件,只能把产生的随机数传出来给主程序显示,传出来的方式有两种,一个是通过普通函数传出,在主程序内还要不断的扫描是否有值传出;另外一种就是使用回调函数,把显示程序函数的地址传给回调处理函数,在.dll内就可以使用显示程序函数了,也避免了一直扫描。也就是说,.dll中什么时候产生随机数,就什么时候调用显示函数

代码实现

.dll

unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;                                                         //把常用部分加进来

type //在type下的都是类型
  MyThread = class(TThread)     //线程类:TTread,继承这个类,产生这个类的对象从而使用它
  procedure Execute; override;  //线程执行函数,把要重写的放进类里来 ,重写
  end;

  //导出函数——声明后加stdcall
  PointToFunc = function (num : Integer) : Integer;//(这只是个类型)函数指针类型(要和回调函数相同)
  function CreateThread(StartFlag : BOOL) : Integer; stdcall;  //线程开启/关闭函数
  function CallBack(Func : PointToFunc) : Integer; stdcall;    //回调处理函数——把回调函数的地址传进来。

var //上面的是类型,这里才是定义对象
  thread : MyThread;     //创建类对象
  CallFunc : PointToFunc;  //函数指针对象

implementation

procedure MyThread.Execute;   //MYThread的函数,要加类
var
  num : Integer;
begin
  while not Suspended do  //挂起标志,在TTheard内的
  begin
    Randomize;
    num := Trunc(Random(100)); //随机数产生
    CallFunc(num);     //调用回调函数 CallFunc(num) --> Func(num) --->dispaly(num)
    Sleep(1000);
  end;
end;

function CreateThread(StartFlag : BOOL) : Integer; stdcall;
begin
  if thread = nil then
     thread := MyThread.Create(True);//实例化

  if StartFlag = True then
  begin
    thread.Resume;      //唤醒线程
  end
  else
  begin
    thread.Suspend;     //挂起线程
  end;
end;

function CallBack(Func : PointToFunc) : Integer; stdcall;
begin
  CallFunc := Func;     //地址传递
end;
end.

.pas

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

type
  TForm1 = class(TForm)
    Edit: TEdit;
    Start: TButton;
    procedure FormCreate(Sender: TObject);
    procedure StartClick(Sender: TObject);   

  private
    { Private declarations }
  public
    { Public declarations }
    StartFlag : BOOL;   //按键标志:true :打开  false : 关闭 
  end;

  //导入动态库中的函数 ——加external '*.dll(库名)' name '新名'
  PointToFunc = function (num : Integer) : Integer;
  function CreateThread(StartFlag : BOOL) : Integer; stdcall;external 'randomthread.dll' name 'CreateThread';
  function CallBack(Func : PointToFunc) : Integer; stdcall;external 'randomthread.dll' name 'CallBack';

  function display(num : Integer) : Integer;

var
  Form1: TForm1;
implementation
{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  StartFlag := True;
  CallBack(display);      //一开始就导入回调函数的地址 ,函数名可以就直接当地址传入
end;

procedure TForm1.StartClick(Sender: TObject);
begin
  if StartFlag = True then
  begin
    CreateThread(True);
    StartFlag := False;           //按键切换
  end
  else
  begin
    CreateThread(False);
    StartFlag := True;
  end;
end;

function display(num : Integer) : Integer;
begin
  Form1.Edit.Text := IntToStr(num);    //显示 ,因为是全局的,所以要加上
end; 
end.
Delphi中制作DLL •一 Dll的制作一般分为以下几步: 1 在一个DLL工程里写一个过程或函数 2 写一个Exports关键字,在其下写过程的名称。不用写参数和调用后缀。 二参数传递 1 参数类型最好与window C++的参数类型一致。不要用DELPHI的数据类型。 2 最好有返回值[即使是一个过程],来报出调用成功或失败,或状态。成功或失败的返回值最好为1[成功]或0[失败].一句话,与windows c++兼容。 3 用stdcall声明后缀。 4 最好大小写敏感。 5 无须用far调用后缀,那只是为了与windows 16位程序兼容。 三 DLL的初始化和退出清理[如果需要初始化和退出清理] 1 DLLProc[SysUtils单元的一个Pointer]是DLL的入口。在此你可用你的函数替换了它的入口。但你的函数必须符合以下要求[其实就是一个回调函数]。如下: procedure DllEnterPoint(dwReason: DWORD);far;stdcall; dwReason参数有四种类型: DLL_PROCESS_ATTACH:进程进入时 DLL_PROCESS_DETACH进程退出时 DLL_THREAD_ATTACH 线程进入时 DLL_THREAD_DETACH 线程退出时 在初始化部分写: DLLProc := @DLLEnterPoint; DllEnterPoint(DLL_PROCESS_ATTACH); 2 如Form上有TdcomConnection组件,就Uses Activex,在初始化时写一句CoInitialize (nil); 3 在退出时一定保证DcomConnection.Connected := False,并且数据集已关闭。否则报地址错。 四全局变量的使用 在widnows 32位程序中,两个应用程序的地址空间是相互没有联系的。虽然DLL在内存中是一份, 但变量是在各进程的地址空间中,因此你不能借助dll的全局变量来达到两个应用程序间的数据 传递,除非你用内存映像文件。 五、其他:调用方式按照标准的Windows调用方式. 六、关于参数传递 •Delphi程序之间调用DLL,如果要用String类型的话,要在引用的单元加上ShareMem 单元。 •如果Delphi写的DLL供其他开发工具使用的话,不要使用String类型,用PAnsiChar类型。 尽量使用标准DLL接口。指的是传递的参数类型及函数返回类型不能是Delphi特有的, 比如string(AnsiString),以及动态数组和含有这些类型成员的复合类型(如记录),也不 能是包含有这些类型成员数据成员的对象类型,以避免可能的错误。如果使用了string类型或 动态数组类型,且调用方不是Delphi程序,则基本上会报错。如果调用方是Delphi但调用方或 被调用方没有在工程文件的第一包含单元不是ShareMem,也可能会出错。 七、关于回调Funciton 你可以把Callback函数看作是一种特殊的消息响应函数,一般来说我们不会自己调用这种函数, 而是有某些系统函数调用,而且不需要向后传递消息。 只要象C/C++这样支持函数指针的语言都 有回调函数的概念,它实际上是向被调用函数传一个你的函数地址,然后被调用函数向通过你传 入的函数地址来调用你的函数 。 以上是结构化回调,到高级语言Object Pascal、C++中回调函数并没有退出,反而得到延伸与 扩展,在面向对像中的回调,其实是指面向对像中类对像的事件,事件就是原始的回调函数。面 向对像, 将回调函数定义成事件过程,在程序引用对像时,若指定了对像的过程事件后,那么在 要进行事件触发的地方检查事件过程是否分配,如果分中的就执行事,也就是执行了回调函数
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值