使用 Visual Basic 的注册消息进行进程间通信






4.39/5 (15投票s)
本文介绍了如何注册自定义 Windows 消息并创建专门用于处理这些消息的窗口,并使用它们在您的应用程序之间进行通信。
引言
在 Visual Basic 中实现多任务处理的最简单方法之一是创建一个单独的可执行程序来执行每个任务,并简单地使用 Shell 命令根据需要运行它们。 这样做唯一的问题是,一旦程序运行,您需要与其通信才能控制其操作。 实现此目的的一种方法是使用 RegisterWindowMessage
和 SendMessage
API 调用来创建您自己的特定窗口消息,并在窗口之间发送它们,从而允许您创建两个或多个相互 通信 的程序。 在此示例中,服务器的任务是监视打印队列,并在发生任何事件(作业添加、驱动程序更改、作业打印等)时向每个感兴趣的客户端发送消息。
指定您自己的唯一消息
Windows 通过相互发送标准 Windows 消息(例如 WM_CLOSE
用于关闭和终止窗口)来进行通信。 存在大量标准消息,涵盖了可以由不同窗口执行和对不同窗口执行的大多数标准操作。 但是,如果您想实现自己的自定义通信,则需要创建自己的自定义消息。 这是通过 RegisterWindowMessage
API 调用完成的
'\\ Declaration to register custom messages
Private Declare Function RegisterWindowMessage Lib "user32" Alias _
"RegisterWindowMessageA" (ByVal lpString As String) As Long
此 API 调用接受一个唯一的字符串并将其注册为已定义的 Windows 消息,从而返回该消息的系统范围内的唯一标识符作为结果。 此后,任何应用程序中指定相同字符串的 RegisterWindowMessage
调用都将返回相同的唯一消息 ID。 由于此值在每个会话期间是恒定的,因此将其存储在全局变量中以加快执行速度是安全的
Public Const MSG_CHANGENOTIFY = "MCL_PRINT_NOTIFY"
Public Function WM_MCL_CHANGENOTIFY() As Long
Static msg As Long
If msg = 0 Then
msg = RegisterWindowMessage(MSG_CHANGENOTIFY)
End If
WM_MCL_CHANGENOTIFY = msg
End Function
由于此消息需要为每个使用它进行通信的应用程序所知,因此最好将其放入所有项目共用的共享代码模块中。
创建窗口以侦听这些消息
要在 Visual Basic 中创建窗口,您通常使用表单设计器并将新表单添加到您的项目中。 但是,由于我们的通信窗口没有可见的组件,也没有与用户的交互,这有点多余。 相反,我们可以使用 CreateWindowEx
API 调用来创建一个专门用于我们通信的窗口
Private Declare Function CreateWindowEx _
Lib "user32" Alias "CreateWindowExA"
(ByVal dwExStyle As Long, _
'\\ The window class, e.g. "STATIC","BUTTON" etc.
ByVal lpClassName As String, _
'\\ The window's name (and caption if it has one)
ByVal lpWindowName As String, _
ByVal dwStyle As Long, _
ByVal x As Long, _
ByVal y As Long, _
ByVal nWidth As Long, _
ByVal nHeight As Long, _
ByVal hWndParent As Long, _
ByVal hMenu As Long, _
ByVal hInstance As Long, _
lpParam As Any) As Long
如果此调用成功,它将返回一个唯一的窗口句柄,该句柄可用于引用该窗口。 可以在 SendMessage
调用中使用它来向其发送消息。
在典型的客户端/服务器通信中,您需要为客户端和服务器创建一个窗口。 同样,这可以使用每个应用程序通用的少量代码来完成
Public Const WINDOWTITLE_CLIENT = "Merrion Computing IPC - Client"
Public Const WINDOWTITLE_SERVER = "Merrion Computing IPC - Server"
Public Function CreateCommunicationWindow(ByVal client As Boolean) As Long
Dim hwndThis As Long
Dim sWindowTitle As String
If client Then
sWindowTitle = WINDOWTITLE_CLIENT
Else
sWindowTitle = WINDOWTITLE_SERVER
End If
hwndThis = CreateWindowEx(0, "STATIC", sWindowTitle,_
0, 0, 0, 0, 0, 0, 0, App.hInstance, ByVal 0&)
CreateCommunicationWindow = hwndThis
End Function
显然,对于您自己的应用程序,您应该对 WINDOWTITLE_CLIENT
和 WINDOWTITLE_SERVER
使用与以上不同的文本,以确保您的窗口名称是唯一的。
处理自定义消息
就这样,您有了一个自定义消息,并创建了一个可以向其发送该消息的窗口。 但是,由于此消息对窗口来说是全新的,因此当它收到它时,它不会做任何事情。 要实际处理该消息,您需要对窗口进行子类化以截获消息并对其做出反应。 要对窗口进行子类化,您可以创建一个处理 Windows 消息的过程,并将其替换为该窗口的默认消息处理过程。 您的过程必须具有与默认窗口过程相同的参数和返回类型
Private 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
'\\ --[VB_WindowProc]---------------------------------
'\\ 'typedef LRESULT (CALLBACK* WNDPROC)(HWND,
'\\ UINT, WPARAM, LPARAM);
'\\ Parameters:
'\\ hwnd - window handle receiving message
'\\ wMsg - The window message (WM_..etc.)
'\\ wParam - First message parameter
'\\ lParam - Second message parameter
Public Function VB_WindowProc(ByVal hwnd As Long, _
ByVal wMsg As Long, ByVal wParam As Long,_
ByVal lParam As Long) As Long
If wMsg = WM_MCL_CHANGENOTIFY Then
'\\Respond to the custom message here
Else
'\\Pass the message to the previous
'\\window procedure to handle it
VB_WindowProc = CallWindowProc(hOldProc, hwnd, _
wMsg, wParam, lParam)
End If
End Function
然后,您需要通知 Windows 将此过程替换为现有的窗口过程。 为此,您调用 SetWindowLong
以更改存储在 GWL_WINDPROC
索引中的过程的地址。
Public Const GWL_WNDPROC = (-4)
Public Declare Function SetWindowLongApi Lib "user32" _
Alias "SetWindowLongA" _
(ByVal hwnd As Long, ByVal nIndex As Long, _
ByVal dwNewLong As Long) As Long
'\\ Use (after creating the window...)
hOldProc = SetWindowLongApi(hwndThis, _
GWL_WNDPROC, AddressOf VB_WindowProc)
您将先前窗口过程的地址保留在 hOldProc
中,以便传递您未处理以进行默认处理的所有消息。 最好在关闭窗口之前将窗口过程设置回此地址。
发送自定义消息
将自定义消息发送到服务器窗口有两个步骤:首先,您需要使用 FindWindowEx
API 调用找到该窗口的窗口句柄。 然后,您需要使用 SendMessage
API 调用发送消息。
'\\ Declarations
Public Declare Function SendMessageLong Lib "user32" Alias "SendMessageA" _
(ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam _
As Long, ByVal lParam As Long) As Long _
Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
'\\ use....
Dim hwndTarget As Long
hwndTarget = FindWindow(vbNullString, WINDOWTITLE_SERVER)
If hwndTarget <> 0 Then
Call SendMessageLong(hwnd_Server, _
WM_MCL_CHANGENOTIFY, 0,0)
End If
这将向服务器窗口发送 WM_MCL_CHANGENOTIFY
消息,并在处理完毕后返回。