引言:
上篇文章里简单的解释了C#的消息泵原理,这里我们以winform为例详细地了解一下实现代码。
底层实现
[DllImport(ExternDll.User32, ExactSpelling=true, CharSet=CharSet.Auto)]
[ResourceExposure(ResourceScope.None)]
public static extern bool TranslateMessage([In, Out] ref NativeMethods.MSG msg);
在System.Windows.Forms.UnsafeNativeMethods里,通过DLLImport的方法,引入了TranslateMessage方法。该方法的作用是将虚拟密钥消息转换为字符消息。 字符消息将发布到调用线程的消息队列,下次线程调用 GetMessage 或 PeekMessage 函数时要读取。
那么TranslateMessage是在什么情况下被调用的呢?
在System.Windows.Forms.Application中,有一个UnsafeNativeMethods.IMsoComponentManager.LocalModalMessageLoop,代码如下:
private bool LocalModalMessageLoop(Form form) {
try {
// Execute the message loop until the active component tells us to stop.
//
NativeMethods.MSG msg = new NativeMethods.MSG();
bool unicodeWindow = false;
bool continueLoop = true;
while (continueLoop) {
bool peeked = UnsafeNativeMethods.PeekMessage(ref msg, NativeMethods.NullHandleRef, 0, 0, NativeMethods.PM_NOREMOVE);
if (peeked) {
// If the component wants us to process the message, do it.
// The component manager hosts windows from many places. We must be sensitive
// to ansi / Unicode windows here.
//
if (msg.hwnd != IntPtr.Zero && SafeNativeMethods.IsWindowUnicode(new HandleRef(null, msg.hwnd))) {
unicodeWindow = true;
if (!UnsafeNativeMethods.GetMessageW(ref msg, NativeMethods.NullHandleRef, 0, 0)) {
continue;
}
}
else {
unicodeWindow = false;
if (!UnsafeNativeMethods.GetMessageA(ref msg, NativeMethods.NullHandleRef, 0, 0)) {
continue;
}
}
if (!PreTranslateMessage(ref msg)) {
UnsafeNativeMethods.TranslateMessage(ref msg);
if (unicodeWindow) {
UnsafeNativeMethods.DispatchMessageW(ref msg);
}
else {
UnsafeNativeMethods.DispatchMessageA(ref msg);
}
}
if (form != null) {
continueLoop = !form.CheckCloseDialog(false);
}
}
else if (form == null) {
break;
}
else if (!UnsafeNativeMethods.PeekMessage(ref msg, NativeMethods.NullHandleRef, 0, 0, NativeMethods.PM_NOREMOVE)) {
UnsafeNativeMethods.WaitMessage();
}
}
return continueLoop;
}
catch {
return false;
}
}
我们可以看到,该方法先检索了一下当前form下是否存在消息,如果存在消息,则取出并TranslateMessage方法发布到调用线程的消息队列。
接着我们看一下LocalModalMessageLoop会在什么情况下被使用。
还是在System.Windows.Forms.Application中,有一个RunMessageLoopInner方法。代码过多,我就不贴了,免得有水博文之嫌。简单来说,主要业务是处理from的初始化工作,比如绑定线程事件等等,当然也有我们这篇博客的主角——消息泵。它的上层方法就很简单了,直接看代码吧。
internal void RunMessageLoop(int reason, ApplicationContext context) {
// Ensure that we attempt to apply theming before doing anything
// that might create a window.
IntPtr userCookie = IntPtr.Zero;
if (useVisualStyles) {
userCookie = UnsafeNativeMethods.ThemingScope.Activate();
}
try {
RunMessageLoopInner(reason, context);
}
finally {
UnsafeNativeMethods.ThemingScope.Deactivate(userCookie);
}
}
最后就会看到我们非常眼熟的一个方法:
public static void Run(Form mainForm) {
ThreadContext.FromCurrent().RunMessageLoop(NativeMethods.MSOCM.msoloopMain, new ApplicationContext(mainForm));
}
至此,就完成了winform消息泵启动的全流程梳理,时隔一年,这个坑终于填了。
相关资料:
https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-peekmessagea
https://baike.baidu.com/item/%E6%B6%88%E6%81%AF%E5%BE%AA%E7%8E%AF/4970437?fr=aladdin