下面一个A程序向B程序发送一个消息 。
在发送前,在Clipbroad中设置文本,B程序接到消息后,取出ClipBroad的文本
发送很好处理:Findwindow找到B的句柄
SendMessage发送消息。
接受就有点麻烦了。
按VB的惯性,这个消息来的时候,是什么事件来触发B知道消息来了呢?
这就会进入死胡同。因为VB是事件触发。而API是消息触发。
那怎么知道API发送的消息来了呢?
一个消息要进入一个程序B(图中打成了A)。它必须统一通过入口函数处来进入。如果我们把入口函数的地址变
成下面的winPro,那么消息就只能从winPro进入。于是我们用SetWindowLong来改变入口函数的地址。这样消息
注意:自己定义的函数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,ByVal 0& End Sub Private Sub Command2_Click() Dim hWndRecv As Long Clipboard.SetText Text2.Text hWndRecv = FindWindow(vbNullString,WM_USER + 1002,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,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处后,这不算完,紧接着还要从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,0) <> 0 Then Form1.List1.AddItem hwnd & " " & Left$(s,InStr(1,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 '右键双击