下面一个A程序向B程序发送一个消息 。
在发送前,在Clipbroad中设置文本,B程序接到消息后,取出ClipBroad的文本
发送很好处理:Findwindow找到B的句柄
SendMessage发送消息。
接受就有点麻烦了。
按VB的惯性,这个消息来的时候,是什么事件来触发B知道消息来了呢?
这就会进入死胡同。因为VB是事件触发。而API是消息触发。
那怎么知道API发送的消息来了呢?
一个消息要进入一个程序B(图中打成了A)。它必须统一通过入口函数处来进入。如果我们把入口函数的地址变
成下面的winPro,那么消息就只能从winPro进入。于是我们用SetWindowLong来改变入口函数的地址。这样消息
就会自动传到我们自己定义的一个函数WinPro中。
注意:自己定义的函数winPro的参数肯定是和发送来时传送的参数是一致的。只是过程加入我们自己的判断。
这样,消息一来,自动就会到WinPro,于是我们就起到了“响应”的作用。就好像是事件响应一样。
另一个细节是,我既要处理自己定义消息的函数,但又不想破坏其它现在的情况怎么办?
用API,CallwindowProc。它可以临时指定入口地址,但并不改变入口地址值,意思是入口地址仍然是原处,
只是这个时候进行“转发”消息到另一个函数入口地址了。
下面两个程序,同时打开时,可以看到效果。
程序A模块:
Public Const WM_USER = &H400 '自定义范围的消息值
Declare Function FindWindow _
Lib "user32" _
Alias "FindWindowA" (ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Declare Function SendMessage _
Lib "user32" _
Alias "SendMessageA" (ByVal hWnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
lParam As Any) As Long
A主程序:
Private Sub Command1_Click()
Dim hWndRecv As Long
Clipboard.SetText Text1.Text
hWndRecv = FindWindow(vbNullString, "Receive1 - 资料接收者")
SendMessage hWndRecv, WM_USER + 1001, 0, ByVal 0&
End Sub
Private Sub Command2_Click()
Dim hWndRecv As Long
Clipboard.SetText Text2.Text
hWndRecv = FindWindow(vbNullString, "Receive1 - 资料接收者")
SendMessage hWndRecv, WM_USER + 1002, 0, ByVal 0&
End Sub
程序B模块:
Public Const GWL_WNDPROC = (-4)
Public Const WM_USER = &H400
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 GetWindowLong _
Lib "user32" _
Alias "GetWindowLongA" (ByVal hwnd As Long, _
ByVal nIndex 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 prevWndProc As Long
Function WndProc(ByVal hwnd As Long, _
ByVal Msg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
If Msg = WM_USER + 1001 Then
Form1.Text1.Text = Clipboard.GetText
ElseIf Msg = WM_USER + 1002 Then
Form1.Text2.Text = Clipboard.GetText
Else
WndProc = CallWindowProc(prevWndProc, hwnd, Msg, wParam, lParam) '转发
End If
End Function
程序B主程序:
Private Sub Form_Load()
prevWndProc = GetWindowLong(Me.hwnd, GWL_WNDPROC) '取出原入口地址值
Call SetWindowLong(Me.hwnd, GWL_WNDPROC, AddressOf WndProc) '改变现有入口地址值
End Sub
Private Sub Form_Unload(Cancel As Integer)
Call SetWindowLong(Me.hwnd, GWL_WNDPROC, prevWndProc) '恢复成原来的入口地址值
End Sub
线程
'取得当前线程伪句柄
Private Declare Function GetCurrentThread Lib "kernel32" () As Long
'取得线程优级级,1-31,31等级最高
Private Declare Function GetThreadPriority Lib "kernel32" (ByVal hThread As Long) As Long
'设置纯种优先级
Private Declare Function SetThreadPriority _
Lib "kernel32" (ByVal hThread As Long, _
ByVal nPriority As Long) As Long
Private Sub Command1_Click()
Dim p As Long
p = GetThreadPriority(GetCurrentThread) '取得当前线程优先级
Text1.Text = p
SetThreadPriority GetCurrentThread, 3 '设置当前线程优先级
Text2.Text = GetThreadPriority(GetCurrentThread) '再取
SetThreadPriority GetCurrentThread, p '恢复
End Sub
===========================
再看回调函数:
一般我们是调用函数是从A处调用B处,然后就完了。
回调函数,回调,就是回来再调用一个函数。和上面不同的是A处调用B处后,这不算完,紧接着还要从B处“回来”调用另一个函数C。这一连串动作完成,就是整个回调函数的实现过程。
那怎么,B怎么知道是调用了C呢?
于是在A处调用B时,就得从A处把C函数的地址传给B,这样B就知道,完成自己功能后,还得再紧接着调用C函数。
怎么取得C函数的地址? VB中有一个AddressOf来取得一个函数的地址。(在C++中叫函数指针)
为什么 要有回调函数?
因为程序讲究模块化,一个模块定后一般是不会再变化。然后有些功能用模块无法实现这样的功能。怎么办?
回调函数,就是加入自己定义的功能达到丰富、完善,或者说满足自己的要求,而又不影响模块的功能。
如上面A调用B处,是固定的东西,并不能满足自己的要求,于是在C中加入自己的东西,这样,最后的结果就符合自己了。
再看一下VB的机制。
VB是事件驱动, windows是消息驱动,那么消息是怎么传递到VB中而产生事件的呢?
VB程序中有一个窗口程序(window procedure),它就象一个院子的大门。所有windows消息要进入VB就得从中经过。
这个消息进来后,就对应相应的事件,如果这个事件有代码,就会对事件进行实现,如果没有代码,尽管有消息来,也会被
扔掉,因为没有写实现的代码。
有两个注意点:
1、大门很关键,如果我们控制了大门这个战略要点,就可以“吃掉消息 ”,或者假传消息 ,激发我们自己的东西。
吃掉消息 :尽管有消息来,我们在门处,把消息扔掉,可以导致本来的事件不会触发。
假传消息 :如果有吃饭的消息来了,我们在大门处可以假传是干活的消息,可触发干活的事件发生。
转发消息 :如果有按键的消息来了,我们可以转发到自己的函数,这样,按键没触发原来的事件,只是触发了我们的函数。
2、VB本身不会处理全部的消息 ,很多消息 都是由windows来代替,即由DefWindowProc这个API来处理。这样大大地减轻
了VB自身的工作量,减少了资源的浪费。
当我们在窗体上按下了左健,windows系统检测到这个消息,并将其传递给VB的大门,VB再根据这一消息触发对应的消息。
可以参看上面的例子中B程序。B程序就是在大门处设置了一个间谍(回调函数),这个间谍就来过滤windows的消息 。
如果是我们想要的消息 ,就处理,不是我们的消息 ,就放行让VB自己去处理。
仔细看一下B程序过程:
上面三个框。第一个是原来的传递消息的过程。
B程序是怎么做到过滤消息的呢?(第二个框)
从A处传来windows消息,由于B处的用API: setwindowlong把大门的地址E处改向了C处,C就是我们自己处理的函数地址。
这样我们在C处就把要过滤的消息 进行处理(想怎么样就怎么样),然后在C中用CallWindowsproc(拷我又标成了C),把地址
再次转向E,E就是VB的窗口程序。
由此可以看到C实际上起到插队、拦截的作用。
第三个框,就象网上上监听一样,在不影响别的程序(无论是不是VB窗口程序),监听里面的东西,查看别人的数据。至于用什么API, 此处略过。
下面是一个回调函数例子。
例举窗体,回调函数是EnumWindowsProc.
模块:
Option Explicit
'列举所有窗体句柄
'lpEnumFunc是回调函数的地址
Public Declare Function EnumWindows Lib "user32" (ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
'取得窗体的标题
Public Declare Function GetWindowText _
Lib "user32" _
Alias "GetWindowTextA" (ByVal hwnd As Long, _
ByVal lpString As String, _
ByVal cch As Long) As Long
'回调函数,与API:EnumWindow保持一致,且必须放置于标准模块中
Function EnumWindowsProc(ByVal hwnd As Long, ByVal lParam As Long) As Boolean
Dim s As String
s = String$(80, 0)
GetWindowText hwnd, s, 80
If InStr(1, s, 0) <> 0 Then
Form1.List1.AddItem hwnd & " " & Left$(s, InStr(1, s, 0) - 1)
End If
EnumWindowsProc = True
End Function
主程序:
Option Explicit
Private Sub Command1_Click()
EnumWindows AddressOf EnumWindowsProc, 0
End Sub
==================================================
常见消息 :
一、键盘消息:
Public Const WM_KEYDOWN = &H100 '按键按下KeyDown
Public Const WM_KEYUP = &H101 '按键弹上(放开)KeyUP
Public Const WM_CHAR = &H102 '按键按下 KeyPress
Public Const WM_SYSKEYDOWN = &H104 'Alt键按下
Public Const WM_SYSKEYUP = &H105 'Alt键弹起
Public Const WM_SYSCHAR = &H106 '在Alt键按下时,另一个被按的键,如ALT+a 指的是a
二、鼠标消息
Public Const WM_SETCURSOR = &H20 '设置鼠标形状,发生在移动之前。即先设置鼠标指针再捕获位置
Public Const WM_MOUSEMOVE = &H200 '鼠标移动
Public Const WM_LBUTTONDOWN = &H201 '左键按下
Public Const WM_LBUTTONUP = &H202
Public Const WM_LBUTTONDBLCLK = &H203 '左键双击
Public Const WM_MBUTTONDOWN = &H207 '中键按下
Public Const WM_MBUTTONUP = &H208 '中键弹上
Public Const WM_MBUTTONDBLCLK = &H209 '中键双击
Public Const WM_RBUTTONDOWN = &H204 '右键按下
Public Const WM_RBUTTONUP = &H205
Public Const WM_RBUTTONDBLCLK = &H206 '右键双击