回调函数的理解及运用

原创 2007年10月10日 21:54:00
首先申明:我是菜鸟,我只不过想把困绕了我很长时间的问题的解决方案发表出来,免得以后我又忘记,同时给还不知道这些小知识的同僚一些帮助。各位不要笑我的浅薄。同时为了表示我的低级,我会很罗嗦的讲一些基本的东西,这些都是我的理解,很不准确。

用我的方法来控制其他程序窗体上的窗口控件,必须先了解什么是回调函数。我的理解是这样的:

回调函数写出来不是自己的程序去调用的,反而是让其他的东西去调用,比如windows操作系统,比如其他的程序等等之类的。但是什么时候被调用却不知道了。回调函数一般是按照调用者的要求定义好参数和返回值的类型,你向调用者提供你的回调函数的入口地址,然后调用者有什么事件发生的时候就可以随时按照你提供的地址调用这个函数通知你,并按照预先规定好的形式传递参数。所以很多人打比方,说回调函数还真有点像您随身带的bp机:告诉别人号码,在它有事情时call您!

所以一个回调函数写出来之后,一定有个注册的动作,就是告诉调用者,你怎么样找到我写的函数。某些windows api 函数会要求以回调函数地址作为其参数之一,例如settimer 、linedda 、enumobjects,以及我们下面要用到的enumwindows。

在delphi里声明一个回调函数的格式很简单,例如:

function enumwindowsproc(ahwnd:longint;lparam:longint):boolean;stdcall;


首先是函数名称可以随便乱取,但函数参数的类型一般不得乱来,其顺序,数据类型等都有规定的,因为这些都是让其他程序调用的,他们已经规定好了的,但参数名称可以随便乱叫。注意后面一定要带上“stdcall”,

stdcall是标准调用,也就是说采用标准windows参数传递方式来调用函数。

编写函数体就很简单了,利用传递过来的参数就可以了,只要记住,这些参数是别人送给你的,你只要知道这些参数代表了什么意思。

再看个向调用者注册回调函数入口地址的函数。
function enumwindows(lpenumfunc: tfnwndenumproc; lparam: lparam): bool; stdcall;


tfnwndenumproc其实就是指针类型。其中的lpenumfunc就是回调函数的入口地址了。

下面是调用enumwindows的格式:
enumwindows(@enumwindowsproc,0);


通过向系统注册回调函数的入口地址,系统就能在需要的时候,调用回调函数,传递参数给它,也许这些参数就是我们想要的。

enumwindows函数的功能是:枚举屏幕上所有程序中的顶层窗口,将窗口句柄以参数的形式传递给回调函数。找到一个窗口,就调用一次回调函数。枚举结束的条件是:要么枚举完所有的窗口,要么回调函数返回false。

lparam: lparam参数是程序定义的值,这个值被传递到回调函数。

回过头来再看一下enumwindowsproc:

function enumwindowsproc(ahwnd:longint;lparam:longint):boolean;stdcall;

当系统找到了一个窗口后,就开始调用这个回调函数,将窗口的句柄作为第一个参数传递过来,将在enumwindows中lparam: lparam这个程序定义的值作为第二个参数传递过来。

所以我们可以在enumwindowsproc函数中利用传递过来的两个参数来做某些处理了。

下面我们新建一个程序列举系统中所有程序的顶层窗口,我们要得到窗口的标题,要得到窗口类名称。

得到窗口标题用:

function getwindowtext(hwnd: hwnd; lpstring: pchar; nmaxcount: integer): integer; stdcall;


该函数功能是将窗口句柄为hwnd的窗口的标题拷入到一个缓冲区lpstring。nmaxcount是拷入缓冲区内的最大的字符数。

要得到窗口标题还可以发送消息:wm_gettext,其实getwindowtext就是发送wm_gettext消息的。

要得到窗口类名称用:

function getclassname(hwnd: hwnd; lpclassname: pchar; nmaxcount: integer): integer; stdcall;


其参数意义和上面的函数差不多。不详细解释了。

我们先编写回调函数:enumwindowsproc。现在告诉自己,我们已经有了两个参数的值了。这两个参数是系统给我们的.

为了显示窗口标题和类名,我们用一个tmemo控件。

先在interface部分声明函数。

function enumwindowsproc(ahwnd:longint;aform:tform1):boolean;stdcall;


注意我将第二个参数改了,不要紧,到时候调用的时候注意看。

然后在implementation部分定义函数:

function enumwindowsproc(ahwnd:longint;aform:tform1):boolean;
var
lpszclassname,lpszwindowtext:array[0..254] of char; //定义两个缓冲区。
begin
getwindowtext(ahwnd,lpszwindowtext,254); //得到窗口标题
getclassname(ahwnd,lpszclassname,254); //得到窗口类名。
aform.memo1.lines.add(strpas(lpszwindowtext));
aform.memo1.lines.add(strpas(lpszclassname));
aform.memo1.lines.add('--------------------');
result:=true;
end;


接着需要做的就是调用enumwindows函数,注册回调函数入口地址,让系统调用回调函数,列举窗口了。所以再添加一个tbutton: btn_listwindow

procedure tform1.btn_listwindowclick(sender: tobject);
begin
enumwindows(@enumwindowsproc,longint(self));
end;


程序清单如下:

unit unit1;

interface

uses
windows, messages, sysutils, variants, classes, graphics, controls, forms,
dialogs, stdctrls;

type
tform1 = class(tform)
memo1: tmemo;
btn_listwindow: tbutton;
procedure btn_listwindowclick(sender: tobject);
private
{ private declarations }
public
{ public declarations }
end;

var
form1: tform1;

function enumwindowsproc(ahwnd:longint;aform:tform1):boolean;stdcall;

implementation
{$r *.dfm}
function enumwindowsproc(ahwnd:longint;aform:tform1):boolean;
var
lpszclassname,lpszwindowtext:array[0..254] of char;
begin
getwindowtext(ahwnd,lpszwindowtext,254);
getclassname(ahwnd,lpszclassname,254);
aform.memo1.lines.add(strpas(lpszwindowtext));
aform.memo1.lines.add(strpas(lpszclassname));
aform.memo1.lines.add('--------------------');
result:=true;
end;

procedure tform1.btn_listwindowclick(sender: tobject);
begin
enumwindows(@enumwindowsproc,longint(self));
end;

end.


f9,运行,看看结果。最好是f7单步跟踪调试一下,看看回调函数是怎么被调用的。
 

对Redis的理解总结

1. Redis是什么 这个问题的结果影响了我们怎么用Redis。如果你认为Redis是一个key value store, 那可能会用它来代替MySQL;如果认为它是一个可以持久化的cache...
  • u012538947
  • u012538947
  • 2015年04月02日 15:00
  • 2502

如何更好的理解和使用Github

刚在知乎上面看到了一个对Github使用通俗易懂的解释: 你也许不懂如何造一辆凯迪拉克,但你可以驾驶凯迪拉克。 你也许不懂Evernote是用什么技术做出来的,但你也可以使用Evernot...
  • yujin753
  • yujin753
  • 2014年12月16日 09:48
  • 1572

java多线线程用法总结

文章目录: 1、线程的定义 2、线程的创建于启动 3、线程的生命周期 4、线程的安全问题...
  • huanghi11
  • huanghi11
  • 2015年10月24日 19:01
  • 715

深入理解java映射map的底层操

亲戚
  • u012486727
  • u012486727
  • 2014年04月20日 22:00
  • 685

this 关键字的理解以及运用

this 关键字/* * this:看上去,是用于区别局部变量和成员变量同名情况 * this为什么可以解决这个问题? * this到底代表的是什么呢? * * this:就代表本类的对象...
  • qq_36868342
  • qq_36868342
  • 2017年06月22日 10:29
  • 121

display运用与理解

display:规定元素应该生成的框的类型 以下是一些关于display比较常用的属性值: 值 描述 none 元素不会显示 block 此元素将显示为块级元素,此元素前后会带有...
  • zn740395858
  • zn740395858
  • 2016年07月24日 21:41
  • 76

递归算法的理解与运用

一、基本概念             递归算法是一种直接或者间接调用自身函数或者方法的算法。Java递归算法是基于Java语言实现的递归算法。递归算法的实质是把问题分解成规模缩小的同类问题的子问题,...
  • u011144780
  • u011144780
  • 2017年05月19日 10:07
  • 243

struts配置的运用和理解

action配置的格式 该结果名称所对应的JSP页面 常用的两种action配置示例 ...
  • q383965374
  • q383965374
  • 2015年02月26日 11:07
  • 971

深入理解及运用Activity

activity是最常用的四大组件之一,这里将从activity的正常和异常生命周期、启动模式、IntentFilter匹配原则、activity的过渡动画等方面做个总结。 一、 activ...
  • u010584514
  • u010584514
  • 2017年04月02日 12:06
  • 102

SSH的简单理解和运用

一、什么是SSH? 简单说,SSH是一种网络协议,用于计算机之间的加密登录。如果一个用户从本地计算机,使用SSH协议登录另一台远程计算机,我们就可以认为,这种登录是安全的,即使被中途截获,密码也不会...
  • superhero_123
  • superhero_123
  • 2018年01月09日 16:34
  • 4
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:回调函数的理解及运用
举报原因:
原因补充:

(最多只允许输入30个字)