请问控件子类化是什么意思?

yeyi198172 2004-01-27 12:45:17
我是一只莱莱莱莱鸟,但我想飞得高
...全文
212 4 打赏 收藏 转发到动态 举报
写回复
用AI写文章
4 条回复
切换为时间正序
请发表友善的回复…
发表回复
zhoujiamurong 2004-07-20
  • 打赏
  • 举报
回复
mark
goodname008 2004-01-27
  • 打赏
  • 举报
回复

AddressOf 运算符


一个一元运算符,它将其后面的过程的地址传递给一个 API 过程,该 API 过程在参数表对应位置中需要一个函数指针。

语法

AddressOf procedurename

必需的 procedurename 指定要传递的地址是哪一个过程的地址。这个过程必须是发出调用命令的工程中的一个标准模块模块里的一个过程。

说明

当一个过程的名称出现在一个参数列表中时,通常该过程已经被计算,并且该过程返回值的地址也会被传递。AddressOf 允许该过程的地址被传递给动态链接库 (DLL) 中的一个 Windows API 函数,而不是传递该过程的返回值。API 函数然后就可以使用这个地址来调用相应的 Basic 过程,这个过程就是我们所知道的回调。AddressOf 运算符只出现在对 API 过程的调用中。

尽管可以使用 AddressOf 运算符在 Basic 过程之间传递过程指针,却不能通过这样的一个指针从 Basic 内部调用一个函数。这就是说,例如,一个使用 Basic 编写的类 不能使用这样的一个指针来回调自己的控制器。当使用 AddressOf 运算符在 Basic 内的过程之间传递一个过程的指针时,被调用过程的参数类型必须定义为 As Long。

警告 如果不能完全理解函数回调的概念,则使用 AddressOf 运算符可能会导致意想不到的结果。必须理解回调的 Basic 部份是如何工作的,以及接受所传递的函数地址的 DLL 的代码是如何工作的。调试这样的交互操作是非常困难的,因为该程序和开发环境运行在相同的进程中。在某些情况下,系统的调试也许是不可能的。

注意 可以使用 Microsoft Visual C++ (或者类似的工具)编译的 DLLs 来创建自己的回调函数原型。要使用 AddressOf 运算符来工作,您的原型必需使用 __stdcall 调用约定。缺省的调用约定 (__cdecl) 不能与 AddressOf 运算符一起工作。

因为一个回调的调用程序不在用户自己的程序中,所以很重要的一点是要保证回调过程中的错误不被回传到调用者。可以通过把 On Error Resume Next 语句放置于回调过程的起始处来达到这个要求。
goodname008 2004-01-27
  • 打赏
  • 举报
回复
将函数指针传递到 DLL 和类型库


熟悉 C 语言的程序员一定会熟悉函数指针的概念。对于不熟悉 C 语言的读者,有必要对此进行一番解释。函数指针是一种约定,程序员可以用它将一个自定义的函数的地址作为参数传递到另一个函数。后面一个函数可以不是自己编写的,但是已经进行了声明,所以可以在应用程序中使用。利用函数指针,可以调用 EnumWindows 等函数列出系统中打开的窗口,利用 EnumFontFamilies 列出所有的当前字体。利用函数指针还可以访问 Win32 API 中的其它许多函数,早期的 Visual Basic 没有提供对它们的支持。

在 Visual Basic 5.0 中,使用函数指针时仍然存在若干限制。详细信息,请参阅本帮助主题后面的“函数指针的局限与风险”。

有关函数指针的知识
使用例子可以很好地说明函数指针的用法。首先,看一看 Win32 API 中的 EnumWindows 函数:

Declare Function EnumWindows lib "user32" _
(ByVal lpEnumFunc as Long, _
ByVal lParam as Long ) As Long

EnumWindows 是一个枚举函数,它能够列出系统中每一个打开的窗口的句柄。EnumWindows 的工作方式是重复地调用传递给它的第一个参数(lpEnumFunc,函数指针)。每当 EnumWindows 调用函数,EnumWindows 都传递一个打开窗口的句柄。

在代码中调用 EnumWindows 时,可以将一个自定义函数作为第一个参数传递给它,用来处理一系列的值。例如,可以编写一个函数将所有的值添加到一个列表框中,将 hWnd 值转换为窗口的名字,以及其它任何操作!

为了表明传递的参数是一个自定义函数,在函数名称的前面要加上 AddressOf 关键字。第二个参数可以是合适的任何值。例如,如果要把 MyProc 作为函数参数,可以按下面的方式调用 EnumWindows:

x = EnumWindows(AddressOf MyProc, 5)

在调用过程时指定的自定义函数被称为回调函数。回调函数(通常简称为“回调”)能够对过程提供的数据执行指定的操作。

回调函数的参数集必须具有规定的形式,这是由使用回调函数的 API 决定的。关于需要什么参数,如何调用它们,请参阅 API 文档。

使用 AddressOf 关键字
如果代码要调用 Visual Basic 5.0 的函数指针,则必须将该代码放到标准的 .BAS 模块中,不可以将其放到类模块中,也不能将其附加到窗体上。在使用 AddressOf 关键字声明函数时,必须注意下列事项:

AddressOf 只能紧接在是参数列表中的参数前;该参数可以是自定义的过程、函数或者属性的名字。


写在 AddressOf 后面的过程、函数、属性必须与有关的声明和过程在同一个工程中。


AddressOf 只能用于自定义的过程、函数和属性,不能将其用于 Declare 语句声明的外部函数,也不能将其用于类型库中的函数。


在声明的 Sub、Function 或自定义的类型定义中,可以将函数指针传递到 As Any 或 As Long 类型的参数。
注意 可创建用 Visual C++ (或类似的工具)编译的 DLL 中的回调函数原型。要使用 AddressOf 时,原型必须使用 __stdcall 调用约定。不能将缺省调用约定与 AddressOf 并用

在变量中存储函数指针
在某些情况下,在将函数指针传递到 DLL 之前需要将其存储在一个中间变量中。如果需要将函数指针从一个 Visual Basic 传递到另一个,这种做法是很有用的。例如,在调用 RegisterClass 之类的函数时就需要用结构 (WndClass) 的成员来传递函数指针。

要将一个函数指针赋予结构中的一个成员,需要编写一个包装函数(wrapper)。例如,下面创建的 FnPtrToLong 就是一个包装函数,使用它可以将函数指针放入任何结构中:

Function FnPtrToLong (ByVal lngFnPtr As Long) As Long
FnPtrToLong = lngFnPtr
End Function

要使用该函数,首先需要声明类型,然后再调用 FnPtrToLong。AddressOf 加上回调函数的名字作为函数的参数。

Dim mt as MyType
mt.MyPtr = FnPtrToLong(AddressOf MyCallBackFunction)

子类派生
利用子类派生技术,可以截取发送到控件或窗体的消息。通过截取这些消息,可以编写自己的代码来改变或者扩展对象的行为。类的派生技术比较复杂,对它的全面讨论将超出本书的范围。下例只能提供该技术的大致轮廓。

重点 当 Visual Basic 处于中断模式时,不允许调用 vtable 方法或 AddressOf 函数。为了保证安全,Visual Basic 仅仅将 0 返回到 AddressOf 函数的调用者。对于子类派生情况,这意味着 WindowProc 将 0 返回到 Windows。Windows 要求它的许多消息返回非 0 值,因此返回的常数 0 将导致 Windows 与 Visual Basic 之间的死锁,从而迫使进程终止。

在下例中,应用程序包括一个简单的窗体,其中只有两个命令按钮。代码的作用是截取发送到窗体的 Windows 消息,并在“立即”窗口中打印出这些消息的值。

代码的第一部分是声明部分,包括 API 函数声明,常数声明和变量声明:

Declare Function CallWindowProc Lib "user32" Alias _
"CallWindowProcA" (ByVal lpPrevWndFunc As Long, _
ByVal hwnd As Long, ByVal Msg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long

Declare Function SetWindowLong Lib "user32" Alias _
"SetWindowLongA" (ByVal hwnd As Long, _
ByVal nIndex As Long, ByVal dwNewLong As Long) As Long

Public Const GWL_WNDPROC = -4
Global lpPrevWndProc As Long
Global gHW As Long

下一步,使用两个例程“钩入”消息流。第一个过程 (Hook) 调用了 SetWindowLong 函数,它使用了 GWL_WNDPROC 索引来创建窗口类的子类,窗口类是用来创建窗口的。然后它使用 AddressOf 关键字和回调函数 (WindowProc) 来截取消息并在“立即”窗口中打印消息的值。第二个过程 (Unhook) 关闭了子类,重新使原来的 Windows 过程成为回调函数。

Public Sub Hook()
lpPrevWndProc = SetWindowLong(gHW, GWL_WNDPROC, _
AddressOf WindowProc)
End Sub

Public Sub Unhook()
Dim temp As Long
temp = SetWindowLong(gHW, GWL_WNDPROC, _
lpPrevWndProc)
End Sub

Function WindowProc(ByVal hw As Long, ByVal uMsg As _
Long, ByVal wParam As Long, ByVal lParam As Long) As _
Long
Debug.Print "Message: "; hw, uMsg, wParam, lParam
WindowProc = CallWindowProc(lpPrevWndProc, hw, _
uMsg, wParam, lParam)
End Function

最后,窗体的代码设置了 hWnd 的初始值,按钮的代码仅仅调用了上面的两个例程:

Private Sub Form_Load()
gHW = Me.hwnd
End Sub

Private Sub Command1_Click()
Hook
End Sub

Private Sub Command2_Click()
Unhook
End Sub

使用函数指针的局限与风险
使用函数指针是有风险的。每当调用 DLL 的时候,就失去了 Visual Basic 开发环境的稳定性,使用函数指针的危险性就更大了,因为它很容易导致应用程序失败并因此而丢失已完成的工作。在工作的时候必须经常地保存和备份工作成果。下面列出了使用函数指针的一些注意事项:

调试。在中断模式下,如果应用程序引发了回调函数,那么回调函数将被正常执行,但是断点和单步设置将被忽略。如果回调函数产生了异常,则可以捕捉到它并返回当前值。在中断模式下,如果堆栈上有回调函数,那么禁止复位。


形实转换程序。形实转换是 Windows 实现代码重定位的手段。如果在中断模式下删除回调函数,它的形实转换程序将被修改返回 0。该值通常总是正确的,但是也有意外的情况。如果在中断模式下先删除了一个回调函数,然后又再次将其加入,那么有些被调用者可能对新的函数地址一无所知。在 .exe 中不使用形实转换程序,指针被直接传递到入口点。


被传递的函数的签名有误。如果函数的参数个数与调用者期望的不一致,或者在调用某个参数时错误地使用 ByRef 或 ByVal,则应用程序可能会失败。因此,在传递函数时签名一定要正确。


将函数传递给不存在的 Windows 过程。在对某个窗口进行子类派生的时候,需要将一个函数指针作为 Windows 过程 (WindowProc) 传递给 Windows。但是,在 IDE 中运行应用程序时,在调用 WindowProc 时下一层函数可能已经被破坏了。这可能导致一般性保护错误,并使 Visual Basic 开发环境遭到破坏。


不支持“Basic 到 Basic”的函数指针。在 Visual Basic 的内部不能传递指向 Visual Basic 函数的指针。目前,只支持从 Visual Basic 到 DLL 函数的指针。


回调过程中包含错误。回调过程中的任何错误都不应回传最初调用它的外部过程,这是很重要的。可以通过在回调过程的开始加上 On Error Resume Next 语句来实现。
goodname008 2004-01-27
  • 打赏
  • 举报
回复
就是设置窗口的入口函数的地址,改成你自己的函数,中途可以做一下处理,然后再传给原来的入口函数。

7,763

社区成员

发帖
与我相关
我的任务
社区描述
VB 基础类
社区管理员
  • VB基础类社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧