首页 > 系统相关 >Windows 消息机制

Windows 消息机制

时间:2023-09-22 21:23:11浏览次数:53  
标签:控件 Windows 队列 线程 消息 msg 机制

目录


Windows 和消息

消息和消息队列

名称 说明
消息和消息队列 本部分介绍消息和消息队列,以及如何在应用程序中使用它们。
关于消息和消息队列 本部分讨论 Windows 消息和消息队列。
使用消息和消息队列 以下代码示例演示如何执行与 Windows 消息和消息队列关联的以下任务。
消息引用 包含 API 引用。

Windows消息类型

根据消息的来源进行分类,可以分为:

根据消息的路由方式分类,可以分为:

Windows系统的整个消息系统分为3个层级

  1. Windows内核的系统消息队列

    Windows内核维护着一个全局的系统消息队列;按照线程的不同,系统消息队列中的消息会分发到应用程序的UI线程的消息队列中;

  2. 应用程序UI线程消息队列

    应用程序的每一个UI线程都有自己的消息循环,会不停地从自己的消息队列取出消息,并发送给Windows窗体对象(WinForm控件);

  3. 每个控件自己的消息处理函数

    WinForm 中的 void WndProc(ref Message m) 消息处理函数

img

img

img

img

img

在这里插入图片描述

Q&A

  1. 进程消息队列(应用程序消息队列)和主线程的消息队列是一回事吗?

在 WinForm 程序中,进程消息队列指的是主线程的消息队列。WinForm 应用程序是单线程的,所有的 UI 操作都必须在主线程中执行。当用户与应用程序的界面交互时,操作系统会将相关的消息发送到应用程序的消息队列中,然后主线程从消息队列中逐个获取并处理这些消息,以更新界面状态和响应用户操作。因此,进程消息队列实际上就是主线程的消息队列

  1. 线程中消息队列消息的结构是怎样的?
typedef struct tagMSG {
  // 接收消息的窗口的句柄。 当消息是线程消息时,此成员为 NULL 。
  HWND   hwnd;
  // 消息的标识符。 应用程序只能使用低字;高字由系统保留。
  UINT   message;
  // 消息的附加信息。 确切含义取决于 消息 成员的值。
  WPARAM wParam;
  // 消息的附加信息。 确切含义取决于 消息 成员的值。
  LPARAM lParam;
  // 消息的发布时间。
  DWORD  time;
  // 发布消息时的光标位置(以屏幕坐标表示)。
  POINT  pt;
  // 
  DWORD  lPrivate;
} MSG, *PMSG, *NPMSG, *LPMSG;

msg 结构 (winuser.h)

  1. 在WinForm程序中线程Id和托管线程Id是一回事吗?

线程Id和托管线程Id是不一样的!

可以通过以下方法获取线程的Id

        /// <summary>
        /// 获取操作系统线程ID,Windows 10下的偏移量为0x022C(x64-bit-Application)和0x0160(x32-bit-Application):
        /// https://www.ibckf.com/questions/1679243
        /// </summary>
        /// <param name="thread"></param>
        /// <returns></returns>
        public static int GetNativeThreadId(Thread thread)
        {
            var f = typeof(Thread).GetField("DONT_USE_InternalThread",
                BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);

            var pInternalThread = (IntPtr)f.GetValue(thread);
            var nativeId = Marshal.ReadInt32(pInternalThread, (IntPtr.Size == 8) ? 0x022C : 0x0160); // found by analyzing the memory
            return nativeId;
        }

获取当前线程的托管线程Id

        public static string CurrentThreadId
        {
            get
            {
                var thread = Thread.CurrentThread;
                var threadId = GetNativeThreadId(thread);
                return $"{thread.ManagedThreadId} - ({threadId}, 0x{threadId.ToString("x2")})";
            }
        }
  1. 知道控件的句柄如何获取该控件所在的进程和线程信息?
    public static class Extensions
    {
        // 导入 Windows API 函数

        /// <summary>
        /// 检索创建指定窗口的线程的标识符,以及创建该窗口的进程(可选)的标识符。
        /// GetWindowThreadProcessId 函数 : https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-getwindowthreadprocessid
        /// </summary>
        /// <param name="hWnd">控件句柄</param>
        /// <param name="processId">控件所属进程Id</param>
        /// <returns>控件所属线程Id</returns>
        [DllImport("user32.dll")]
        private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int processId);

        public static void ConsoleHandleInfo(this Control ctrl, string name)
        {
            var handle = ctrl.Handle;
            var threadId = GetWindowThreadProcessId(handle, out int processId);
            Console.WriteLine($"{ThreadInfo.CurrentThreadId} -> 创建 {name} 的进程Id = {processId},线程Id = {threadId}");
        }
    }

WinForm程序中消息处理的相关方法

按调用顺序依次介绍,可以对消息进行拦截与处理

  1. IMessageFilter的消息处理

    IMessageFilter 接口

    一般处理逻辑,在构造函数中调用Application.AddMessageFilter(this);添加消息筛选器,在析构函数中调用Application.RemoveMessageFilter(this);移除消息筛选器,然后重写PreProcessMessage消息处理函数。

    返回值:如果消息已处理,则为 true;否则为 false

    影响级别:应用程序级

  2. Control.PreProcessMessage

    在调度键盘或输入消息之前,在消息循环内对它们进行预处理(键盘消息预处理函数)。

    返回值:如果消息已由控件处理,则为 true;否则为 false

    影响级别:应用程序级

    重写 PreProcessMessage时,控件返回 true 以指示它已处理消息。 对于控件未处理的消息,应返回 base.PreProcessMessage 的结果 。 控件通常会替代更专用的方法之一,例如 IsInputCharIsInputKeyProcessCmdKeyProcessDialogCharProcessDialogKey ,而不是重写 PreProcessMessage

    Control.PreProcessMessage(Message) 方法

  3. 键盘消息的其他处理函数,如 protected override bool IsInputKey(Keys keyData)

  4. 控件的消息处理函数

            protected override void WndProc(ref Message m)
            {
                // 控件内消息处理函数
                //Console.WriteLine($"WndProc-> {m}");
                base.WndProc(ref m);
            }
    

其他代码片段

bool PreProcessMessage(ref Message msg)实现

    /// <summary>
    ///  This method is called by the application's message loop to pre-process input messages before they
    ///  are dispatched. If this method processes the message it must return true, in which case the message
    ///  loop will not dispatch the message.
    /// </summary>
    /// <remarks>
    ///  <para>
    ///   The messages that this method handles are WM_KEYDOWN, WM_SYSKEYDOWN, WM_CHAR, and WM_SYSCHAR.
    ///  </para>
    ///  <para>
    ///   For WM_KEYDOWN and WM_SYSKEYDOWN messages, this first calls <see cref="ProcessCmdKey(ref Message, Keys)"/>
    ///   to check for command keys such as accelerators and menu shortcuts. If it doesn't process the message, then
    ///   <see cref="IsInputKey(Keys)"/> is called to check whether the key message represents an input key for the
    ///   control. Finally, if <see cref="IsInputKey(Keys)"/> indicates that the control isn't interested in the key
    ///   message, then <see cref="ProcessDialogKey(Keys)"/> is called to check for dialog keys such as TAB, arrow
    ///   keys, and mnemonics.
    ///  </para>
    ///  <para>
    ///   For WM_CHAR messages, <see cref="IsInputChar(char)"/> is first called to check whether the character
    ///   message represents an input character for the control. If <see cref="IsInputChar(char)"/> indicates that
    ///   the control isn't interested in the character message, then <see cref="ProcessDialogChar(char)"/> is
    ///   called to check for dialog characters such as mnemonics.
    ///  </para>
    ///  <para>
    ///   For WM_SYSCHAR messages, this calls <see cref="ProcessDialogChar(char)"/> to check for dialog characters
    ///   such as mnemonics.
    ///  </para>
    ///  <para>
    ///   When overriding this method, a control should return true to indicate that it has processed the message.
    ///   For messages that aren't  processed by the control, the result of "base.PreProcessMessage()" should be
    ///   returned.
    ///  </para>
    ///  <para>
    ///   Controls will typically override one of the more specialized methods (<see cref="IsInputChar(char)"/>,
    ///   <see cref="IsInputKey(Keys)"/>, <see cref="ProcessCmdKey(ref Message, Keys)"/>, <see cref="ProcessDialogChar(char)"/>,
    ///   or <see cref="ProcessDialogKey(Keys)"/>) instead of overriding this method.
    ///  </para>
    /// </remarks>
    public virtual bool PreProcessMessage(ref Message msg)
    {
        bool result;
 
        if (msg.MsgInternal == PInvoke.WM_KEYDOWN || msg.MsgInternal == PInvoke.WM_SYSKEYDOWN)
        {
            if (!GetExtendedState(ExtendedStates.UiCues))
            {
                ProcessUICues(ref msg);
            }
 
            Keys keyData = (Keys)(nint)msg.WParamInternal | ModifierKeys;
            if (ProcessCmdKey(ref msg, keyData))
            {
                result = true;
            }
            else if (IsInputKey(keyData))
            {
                SetExtendedState(ExtendedStates.InputKey, true);
                result = false;
            }
            else
            {
                result = ProcessDialogKey(keyData);
            }
        }
        else if (msg.MsgInternal == PInvoke.WM_CHAR || msg.MsgInternal == PInvoke.WM_SYSCHAR)
        {
            if (msg.MsgInternal == PInvoke.WM_CHAR && IsInputChar((char)(nint)msg.WParamInternal))
            {
                SetExtendedState(ExtendedStates.InputChar, true);
                result = false;
            }
            else
            {
                result = ProcessDialogChar((char)(nint)msg.WParamInternal);
            }
        }
        else
        {
            result = false;
        }
 
        return result;
    }

相关参考

  1. msg 结构 (winuser.h)
  2. Message 结构
  3. 关于消息和消息队列
  4. 使用消息和消息队列
  5. Windows消息机制(MFC深度详解)
  6. windows消息机制深入详解
  7. WINDOWS消息,消息的解析可以参考此篇
  8. 线程与消息队列,介绍线程间相互通信相关的函数

标签:控件,Windows,队列,线程,消息,msg,机制
From: https://www.cnblogs.com/lanwah/p/17723395.html

相关文章

  • Windows和Linux中的库、对象、可执行文件后缀名
    中国软件工程师面试常问的问题Justa"Scientific"(Interview)ExplanationforInterviewProblemsforSoftwareEngineers(mostChineseInterviews)Windows和Linux中的库、对象、可执行文件后缀名library,object,executablefilessuffixnameinWindowsandLinuxL......
  • (MongoDB)windows 下使用 MongoDB
    1.安装MongoDBServer 访问 https://www.mongodb.com/try/download/community,找到“MongoDBCommunityServerDownload”并下载  2.安装MongoDB  安装MongoDBServer,并安装随带的MongoDBCompass 安装完成后,MongoDBServer将即时启动(需要重启), 查看......
  • Jenkins问题记录:Windows Server Jenkins修改配置后重启系统,Jenkins被初始化,启动后需要
    现象:WindowsServerJenkins修改配置后重启系统,Jenkins被初始化,启动后需要重新安装插件,配置管理员密码,项目列表丢失原因:修改"C:\ProgramFiles\Jenkins\jenkins.xml"配置,可能是配置异常,系统重启后,Jenkins自动重置了解决:重新安装Jenkins,配置文件就重新初始化,再启动不需要再安......
  • windows中实现本地scp命令向服务器远程免密传输文件
     先安装git,官网:https://git-scm.com/  001、打开git,生成本地密钥文件ssh-keygen##然后全部回车  002、查看密钥文件 ......
  • Windows关闭默认共享
    Windows启动时都会默认打开admin$ipc$和每个盘符的共享,对于不必要的默认共享,一般都会把它取消掉,可当又需要打开此默认共享时,又该从哪里设置呢。经过自己的验证,汇总出一下方法。一:查看window共享资源运行–>cmd–>输入netshare 二:彻底关闭WINDOWS默认共享1、如果你不在......
  • redisde持久化机制
    他的持久化机制有两种一种是(rdb)快照一种是(aof)日志快照的话是全量的一个备份日志是连续的增加备份.快照机制是redis默认开启de,她会根据配置的策略将内存的数据保存在本地的二进制文件中官方提供两种方式生成快照一种是save命令但是有缺点会阻塞我们的主进程当如果数......
  • 一些windows的快捷键
    ctrl+C:复制ctrl+V:粘贴ctrl+A:全选ctrl+X:剪切ctrl+Z:撤销ctrl+S:保存alt+F4:关闭窗口shift+del:删除ctrl+shift+Esc:打开任务管理器Windows+R:打开程序Windows+E:打开我的电脑......
  • 从Android事件分发机制看滑动冲突解决方案
    事件分发机制从ViewGroup的dispatchTouchEvent入手publicbooleandispatchTouchEvent(MotionEventev){...finalbooleanintercepted;if(actionMasked==MotionEvent.ACTION_DOWN||mFirstTouchTarget!=null){fi......
  • Win32编程之通过SetWindowsHookEx注入DLL(十六)
    一、SetWindowsHookEx函数SetWindowsHookEx是用于在Windows操作系统中设置全局或本地的钩子(hook)。钩子是一种用于监视并拦截特定事件或消息的机制,通常用于拦截和处理键盘输入、鼠标操作、窗口消息等。SetWindowsHookEx允许你安装一个全局或本地的钩子过程,以便在事件发生时执行......
  • Kafka怎么保证消息不丢失和重复消费
    (1)生产者发送消息采用异步回调发送,如果发送失败,我们可以通过回调获取消息信息,可以选择记录日志或者重试,同时生产者也可以设置消息重试机制。(2)采用broker的复制机制保证消息在broker中不丢失:开启生产者消息确认机制为all,这样的话,当生产者发送消息到了分区之后,不仅仅只在leader分区......