精品主页 | 软件下载 | 系统下载 | 精品导航| 精彩图片 | 转帖工具 | 版主申请 | 影视下载
发新话题
打印

感悟 Visual Basic (九)

感悟 Visual Basic (九)

  


                  第十四话回调

《细水长流话API》系列写到现在已经第十四话了,不知不觉许多API知识都已经讲过,由于读者的要求,我们现在进入更深一层的学习:回调。

第一部分:回调的概念

什么是回调?它是干什么用的?相信初次接触回调的读者都会这样问。在一个程序中,当我们要访问一个外部函数时(指函数不在我们的程序中,一般称之为DLL函数),我们可以主动的访问;如果我们需要外部函数主动访问我们程序中的函数时,一般是做不到的。但是在某些情况下我们需要和DLL函数之间进行信息的相互传递,或者我们的函数需要接收DLL函数连续发回的多个数据,又该怎么办呢?回调就是为此而设计的。

我们可以把我们程序中的某个函数公开给DLL函数来调用,而不是由我们自己调用,这样的函数就是回调函数,而DLL对此函数的调用,就是回调(参考图1)。回调的应用很广,比如子类。其实子类也是一种回

调,是系统DLL对我们程序回调。又比如计时器(API中的计时器),是每隔一定时间的回调,又或者API中的枚举函数,都是广泛应用到回调的。


第二部分:建立简单的回调函数



其实建立回调函数并不难,它只是一种高级的技术而已,而不是高难度的技术。下面我就用一个最简单不过的例子给你看看如果建立一个回调函数。看图2,这是一个显示桌面上所有窗口(不只是可见窗口)的句柄和它们的标题的程序,这个程序用到了三个API:

PublicDeclareFunctionEnumWindowsLib"user32"(ByVallpEnumFunc

AsLong,ByVallParamAsLong)AsBoolean

PublicDeclareFunctionGetWindowTextLib"user32"Alias

"GetWindowTextA"(ByValhwndAsLong,ByVallpStringAsString,ByValcchAsLong)AsLong

PublicDeclareFunctionGetWindowTextLengthLib"user32"Alias



"GetWindowTextLengthA"(ByValhwndAsLong)AsLong

其中,EnumWindows是最简单的一个使用回调函数的API,作用是枚举桌面上所有可见和不可见的窗口句柄,GetWindowText用来得到窗口的标题,GetWindowTextLength用来得到窗口标题的长度。

建立一个回调函数,首先你必须根据要进行回调的DLL函数写一个相符合的函数在公共模块里,如EnumWindows所要求的回调函数形式是这样的:

FunctionEnumWindowsProc(ByValhwndAsLong,ByVallParamAs

Long)AsLong所以你应该在一个公共模块里写这样一个函数:PublicFunctionEnumWindowsProc(ByValhwndAsLong,ByVal



lParamAsLong)AsLong

这个函数将要成为回调函数,作为一个原则,你不应该自己调用它,而应该把它留给DLL。接下来,在你需要调用EnumWindows时,把这个函数告诉EnumWindows:



EnumWindowsAddressOfEnumWindowsProc,ByVal0&

这里有几点要说明,一是EnumWindowsProc这个函数不必一定是这个函数名,可以是funcABC等之类的名字,但在传递给EnumWindows(或其它需要回调函数的API时),AddressOf之后的函数名一定要用相应的函数名

(如函数名是funcABC,则这里必须是AddressOffuncABC),DLL函数不是靠函数名来识别你程序中哪个函数是回调函数,而是靠你给DLL函数的入口地址(即该函数在内存中的地址)来识别的。二是AddressOf,这个不是函数,它只是VB新加入的标识符,你不可以用“AddressOf

(EnumWindowsProc)”或者其它形式,而必须是“AddressOfEnumWindowProc”(两者之间一个空格),作用是取得某函数的入口地址,而且该函数一定要在公共模块里。三是这个回调函数的类型:AsLong,不必一定是Long(用了也VB也不会说你错,但我建议总是用Long),API中回调基本上都是用长型传递数据,如果你用其它类型,你必须确定当API取回其值时能够取到正确的值,在我的示例中我用的是Boolean而不是Long,但运行仍正常,因为我知道在这里用Boolean也一定不会出错。

当你执行了上面调用EnumWindows的API之后,EnumWindows得到EnumWindowsProc的入口地址,这时EnumWindowsProc就成为回调函数,每当EnumWindows找到一个窗口,它就来调用一次EnumWindowsProc,并把相应的值赋给EnumWindowsProc的两个参数(是按顺序,而不是按参数名,所以不可随意颠倒),在这个函数里,我们要对得到的参数做何用途,则是我们的事了。那么,我的示例利用这两个参数(其实只用到一个)得到

了EnumWindows找到的窗口的标题。

PublicFunctionEnumWindowsProc(ByValhwndAsLong,ByVallParam

AsLong)AsLong

DimsSaveAsString,RetAsLong

Ret=GetWindowTextLength(hwnd)

sSave=Space(Ret)

GetWindowTexthwnd,sSave,Ret 1

Form1.List1.AddItemhwnd&""&sSave

’注意这里:

EnumWindowsProc=1



EndFunction

首先,EnumWindows找到了第一个窗口,它就调用我这个函数,并把这个窗口的句柄赋传递了hwnd参数,我用GetWindowTextLength得到窗口标题的长度,再用Space为sSave字串分配足够多的内存,然后GetWindowText取回窗口的标题(类似这两个函数的调用形式前面讲过,这里就不浪费口水了),然后我给EnumWindowsProc返回了1(可以是“非零”——0以外的值),EnumWindows得到我的返回值,知道我还想继续枚举,所以找到第二个窗口,并再次调用我这个函数⋯⋯直到我给EnumWindowsProc返回0或者EnumWindows把所有窗口都找过了才结束。

在这个例子里,lParam一直没有用到,而EnumWindows的第二个参数是作什么用的呢?其实这两个参数都没什么大用处,如果你调用EnumWindows时,给lParam传递了某个值,那么EnumWindows调用你的回调函数时,会把这个值原封不动的传回来,赋给lParam。最多也就是让你可以判别当前的调用是在自己的程序哪个地方,比如在两个地方调用EnumWindows,一个给lParam传递1,一个传递2,在这里就可以知道是哪个地方在调用而作出不同处理,而不必为每一次调

用都写一个回调函数。

第三部分:回调和指针的应用

以前我讲指针时,说过API中经常遇到取回一个长型值时实际是另一个对象的指针,这里就结合

图3

回调举一个例子。

图3是显示系统中字体的程序,用的是EnumFonts:

PublicDeclareFunctionEnumFontsLib"gdi32"Alias"EnumFontsA"(ByValhDCAsLong,ByVallpszAsString,ByVallpFontEnumProcAsLong,

ByVallParamAsLong)AsLong EnumFonts第一个参数是对象的DC,可以是Form的DC,也可以是PictureBox的DC,其实无论是用Form的DC还是用PictureBox的DC结果都没有什么区别,不过这却是不可缺少的;第二个参数是字体的家族名

(FamilyName),比如找属于Arial家族的,或是其它家族的,如果传递NULL,则是所有字体;第三个参数就是回调函数的入口地址;第四个函数和上一个例子一样,是由你的程序来指定的,结果也是由你的程序自行处理,EnumFonts只负责把它原封不动的传递给回调函数。

EnumFonts的回调函数形式是这样的:

FunctionEnumFontProc(ByVallplfAsLong,ByVallptmAsLong,ByValdwTypeAsLong,ByVallpDataAsLong)AsLong

当EnumFonts调用该函数时,lplf是指向一个LOGFONT结构(自定义类型)的指针,lptm是指向一个TEXTMETRIC结构的指针,dwType是字体的类型,lpData则是你传递给EnumFonts时的lParam的值。


  


                  




我们还需要一个LOGFONT的自定义类型,其声明是这样的:

PrivateConstLF_FACESIZE=32

PrivateTypeLOGFONT

lfHeightAsLong

lfWidthAsLong

lfEscapementAsLong

lfOrientationAsLong

lfWeightAsLong

lfItalicAsByte

lfUnderlineAsByte

lfStrikeOutAsByte

lfCharSetAsByte

lfOutPrecisionAsByte

lfClipPrecisionAsByte

lfQualityAsByte

lfPitchAndFamilyAsByte

lfFaceName(LF_FACESIZE)AsByte



EndType

LOGFONT结构记录了一个字体的各种信息:高度、宽度、风格、名字(叫FaceName)、字符集等。

调用EnumFonts可以这么写:

PrivateSubForm_Load()

EnumFontsMe.hDC,vbNullString,AddressOfEnumFontProc,0

EndSub

下面是回调函数:

FunctionEnumFontProc(ByVallplfAsLong,ByVallptmAsLong,ByValdwTypeAsLong,ByVallpDataAsLong)AsLong

DimLFAsLOGFONT,FontNameAsString,ZeroPosAsLong

CopyMemoryLF,ByVallplf,LenB(LF)

FontName=StrConv(LF.lfFaceName,vbUnicode)

ZeroPos=InStr(1,FontName,Chr$(0))

IfZeroPos>0ThenFontName=Left$(FontName,ZeroPos-1)

Form1.List1.AddItemFontName

EnumFontProc=1

EndFunction

我为EnumFonts的第二个参数传递了vbNullString,可以让EnumFonts枚举所有字体。在EnumFontProc中,我又用CopyMemory通过指向一个LOGFONT结构的指针把数据复制到了一个新定义的LOGFONT结构中,这里是这一部分的重点。要注意lplf之前的ByVal。至于LenB,在这里也可以用Len,因为只有Long和Byte型的数据,不必担心一些VB本身带来的问题,虽然这里用LenB得64,用Len 得61,但实际上的确是61,只是VB内部用了一种称“内存对齐”的技术,这段程序按VB的处理,保留了64字节给它,所以LenB得到它实际占用内存是64。一般对只包含Long、Integer和Byte(或变长String)类型的数据,我们可以用Len,一般是不会有问题的,如果有定长String或更复杂的数据,还是应优先使用LenB,出错的机会较少(我也不敢肯定LenB永远都是正确的)。

取回了lplf指向的对象的数据后,lfFaceName就存储了字体的名字。由于VB内部使用了UNICODE,而Windows95、Windows98以及部分WindowsNT/2000的API都是使用ANSI字符集,所以我们需要通过StrConv()把ANSI字符转换成UNICODE才能得到正确的文字。ZeroPos查找第一个NULL字符,它代表字体名字的结尾,如果不取回这前面的一段,我们得到的字体名字会很长,结尾有许多个NULL字符(数量视乎lfFaceName的长度而定),这是一个细节的问题,其实你不想去掉NULL也不是不可以啦。


  


                  




至于dwType,可以让我们判别该字体是何种字体:DEVICE_FONTTYPE(设备相关字体)、RASTER_FONTTYPE(光栅字体)和TRUETYPE_FONTTYPE(TrueType字体,Windows中广泛使用的与设备无关的字体)。

函数最后为EnumFontProc返回1仍和上一个例子一样,当EnumFonts发现你的回调函数返回0或所有字体都已经枚举过,它将结束运行。

这里我保留了lptm,它和lplf一样是一个指针,所指向的对象是一个TEXTMETRIC结构,TEXTMETRIC结构是这样的:

PublicTypeTEXTMETRIC

tmHeightAsLong

tmAscentAsLong

tmDescentAsLong

tmInternalLeadingAsLong

tmExternalLeadingAsLong

tmAveCharWidthAsLong

tmMaxCharWidthAsLong

tmWeightAsLong

tmOverhangAsLong

tmDigitizedAspectXAsLong

tmDigitizedAspectYAsLong

tmFirstCharAsByte

tmLastCharAsByte

tmDefaultCharAsByte

tmBreakCharAsByte

tmItalicAsByte

tmUnderlinedAsByte

tmStruckOutAsByte

tmPitchAndFamilyAsByte

tmCharSetAsByte



EndType

它记录了一个字体更详细的信息。取回这个结构的数据的部分就留给读者自己去完成吧。

文中程序在Win98/Win2000 VB6下调试通过。

TOP

发新话题