WPF 调用 ChangeWindowMessageFilterEx 修改指定窗口 (UIPI) 消息筛选器的用户界面特权隔离

一、回顾

上一篇博客:记录一下 WPF进程 SendMessage 发送窗口消息进行进程间通信,存在进程权限无法接受消息的问题 - wuty007 - 博客园

说到了 发送端是普通权限的窗体 给 接收端是 管理员权限的窗体,通过 Win32 API的方式调用 SendMessage 发送窗口消息,管理员权限的窗体的钩子消息回传接受不到发送端的数据。

如下图所示:

这是由于 Windows系统在Windows NT6.0 开始,引入了受保护模式,阻止进程将所选窗口消息和其他 USER API 发送到运行完整性较高的进程

详情请看微软的详细说明:

受保护的模式 - Win32 apps | Microsoft Learn

Windows 完整性机制设计 | Microsoft Learn

二、函数说明

针对于以上的问题,微软也提供了相对应的接口来规避: ChangeWindowMessageFilterEx

ChangeWindowMessageFilterEx 函数 (winuser.h)

修改指定窗口 (UIPI) 消息筛选器的用户界面特权隔离。

语法

复制代码
BOOL ChangeWindowMessageFilterEx(
  [in]                HWND                hwnd,
  [in]                UINT                message,
  [in]                DWORD               action,
  [in, out, optional] PCHANGEFILTERSTRUCT pChangeFilterStruct
);

参数

[in] hwnd

类型:HWND

要修改其 UIPI 消息筛选器的窗口的句柄。

[in] message

类型: UINT

消息筛选器允许通过 或 阻止的消息。

[in] action

类型:DWORD

要执行的操作,可以采用以下值之一:

含义
MSGFLT_ALLOW 1 允许消息通过筛选器。 这使 hWnd 能够接收消息,无论消息的来源如何,即使消息来自较低特权进程也是如此。
MSGFLT_DISALLOW 2 阻止消息从较低特权进程传递到 hWnd ,除非使用 ChangeWindowMessageFilter 函数或全局允许该消息在进程范围内传递。
MSGFLT_RESET 0 hWnd 的窗口消息筛选器重置为默认值。 允许全局或进程范围内的任何消息都将通过,但任何未包含在这两个类别中以及来自较低特权进程的消息都将被阻止。

[in, out, optional] pChangeFilterStruct

类型: PCHANGEFILTERSTRUCT

指向 CHANGEFILTERSTRUCT 结构的可选指针。

返回值

类型: BOOL

如果函数成功,则返回 TRUE;否则,它将返回 FALSE。 要获得更多的错误信息,请调用 GetLastError。

注解

UIPI 是一项安全功能,可防止从较低完整性级别的发件人接收消息。 可以使用此函数允许将特定消息传递到窗口,即使消息源自较低完整性级别的进程也是如此。 与控制进程消息筛选器的 ChangeWindowMessageFilter 函数不同, ChangeWindowMessageFilterEx 函数控制窗口消息筛选器。

应用程序可以使用 ChangeWindowMessageFilter 函数以进程范围的方式允许或阻止消息。 如果进程消息筛选器或窗口消息筛选器允许该消息,则会将其传递到窗口。

请注意,不允许 SECURITY_MANDATORY_LOW_RID 或以下的进程更改消息筛选器。 如果这些进程调用此函数,它将失败并生成扩展错误代码, ERROR_ACCESS_DENIED。

无论筛选器设置如何,值小于 WM_USER 的某些消息都需要通过筛选器传递。 尝试使用此函数允许或阻止此类消息时,将不起作用。

三、如何使用

1、WPF 的接受端窗口增加 对 ChangeWindowMessageFilterEx 函数的定义和封装

复制代码
 // 定义MessageFilterAction 结构体
 public enum MessageFilterAction : uint
 {
     MSGFLT_RESET = 0,     // 重置过滤器
     MSGFLT_ALLOW = 1,     // 允许消息
     MSGFLT_DISALLOW = 2   // 禁止消息
 }
 // 定义 消息过滤器状态结构体
 [StructLayout(LayoutKind.Sequential)]
 public struct CHANGEFILTERSTRUCT
 {
     public uint cbSize;
     public uint ExtStatus;
 }

 // 导入 user32.dll 中的函数
 [DllImport("user32.dll", SetLastError = true)]
 private static extern bool ChangeWindowMessageFilterEx(
     IntPtr hWnd,
     uint msg,
     MessageFilterAction action,
     ref CHANGEFILTERSTRUCT pChangeFilterStruct);

 /// <summary>
 /// 设置消息过滤
 /// </summary>
 /// <param name="hWnd"></param>
 /// <param name="message"></param>
 /// <param name="action"></param>
 /// <returns></returns>
 /// <exception cref="System.ComponentModel.Win32Exception"></exception>
 public static bool SetMessageFilter(IntPtr hWnd, uint message, MessageFilterAction action)
 {
     // 初始化结构体
     CHANGEFILTERSTRUCT changeFilter = new CHANGEFILTERSTRUCT
     {
         cbSize = (uint)Marshal.SizeOf(typeof(CHANGEFILTERSTRUCT)),
         ExtStatus = 0
     };

     // 调用 API
     bool result = ChangeWindowMessageFilterEx(hWnd, message, action, ref changeFilter);

     if (!result)
     {
         // 获取错误信息(可选)
         int error = Marshal.GetLastWin32Error();
         throw new System.ComponentModel.Win32Exception(error);
     }
     return result;
 }

2、在接收数据的 FramworkReceieve 窗口 的Loaded时间调用 SetMessageFilter,最主要的是第三个参数需要设置:MessageFilterAction.MSGFLT_ALLOW

复制代码
 private void MainWindow_Loaded(object sender, RoutedEventArgs e)
 {
     _customMessageId = RegisterWindowMessage("MyApp");

     // 获取窗口句柄并添加消息钩子
     _hwndSource = PresentationSource.FromVisual(this) as HwndSource;

     if (_hwndSource != null)
     {
         var handle = _hwndSource.Handle;
         SetMessageFilter(handle, WM_COPYDATA, MessageFilterAction.MSGFLT_ALLOW);
         _hwndSource.AddHook(WndProc);
     }
 }

3、完整代码如下:

消息发送端:

复制代码
<Window x:Class="FramworkSender.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:FramworkSender"
        mc:Ignorable="d"
        Title="FramworkSender" Height="450" Width="800">
    <Grid>
        <Button Width="100" Height="100" Content="发送" Click="ButtonBase_OnClick"></Button>
    </Grid>
</Window>
复制代码
namespace FramworkSender
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
        {
            // 获取接收窗口的句柄

            IntPtr hwnd = FindWindow(null, "FramworkReceieve");
            if (hwnd == IntPtr.Zero)
            {
                MessageBox.Show("找不到窗口");
            }
            else
            {
                SendMessageString(hwnd, "123");
            }
        }

        #region CopyData

        [DllImport("user32.dll")]
        public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, ref COPYDATASTRUCT lParam);


        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        public const int WM_COPYDATA = 0x004A;

        // 定义 COPYDATASTRUCT 结构
        [StructLayout(LayoutKind.Sequential)]
        public struct COPYDATASTRUCT
        {
            public IntPtr dwData;
            public int cbData;
            public IntPtr lpData;
        }

        public static void SendMessageString(IntPtr hWnd, string message)
        {
            if (string.IsNullOrEmpty(message)) return;

            byte[] messageBytes = Encoding.Unicode.GetBytes(message + '\0');

            COPYDATASTRUCT cds = new COPYDATASTRUCT();
            cds.dwData = IntPtr.Zero;
            cds.cbData = messageBytes.Length;
            cds.lpData = Marshal.AllocHGlobal(cds.cbData);
            Marshal.Copy(messageBytes, 0, cds.lpData, cds.cbData);
            try
            {
                var result = SendMessage(hWnd, WM_COPYDATA, IntPtr.Zero, ref cds);
            }
            finally
            {
                //释放分配的内存,即使发生异常也不会泄漏资源
                Marshal.FreeHGlobal(cds.lpData);
            }
        }

        #endregion

    }
}

消息接收端:

复制代码
<Window x:Class="FramworkReceieve.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:FramworkReceieve"
        mc:Ignorable="d"
        Title="FramworkReceieve" Height="450" Width="800">
    <Grid>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="接收到的数据:"/>
            <TextBlock Text="" x:Name="txtMessage"/>
        </StackPanel>
        <Button Height="100" Width="100" Content="清空" Click="ButtonBase_OnClick"></Button>
    </Grid>
</Window>
复制代码
namespace FramworkReceieve
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Loaded += MainWindow_Loaded;
        }

        private HwndSource _hwndSource;
        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            // 获取窗口句柄并添加消息钩子
            _hwndSource = PresentationSource.FromVisual(this) as HwndSource;

            if (_hwndSource != null)
            {
                var handle = _hwndSource.Handle;
                SetMessageFilter(handle, WM_COPYDATA, MessageFilterAction.MSGFLT_ALLOW);
                _hwndSource.AddHook(WndProc);
            }
        }


        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            #region CopyData
            if (msg == WM_COPYDATA)
            {
                COPYDATASTRUCT cds = (COPYDATASTRUCT)Marshal.PtrToStructure(lParam, typeof(COPYDATASTRUCT));
                string receivedMessage = Marshal.PtrToStringUni(cds.lpData);

                this.Dispatcher.Invoke(() =>
                {
                    txtMessage.Text = receivedMessage;
                });

                handled = true;
            }

            #endregion
            return IntPtr.Zero;
        }


        #region CopyData


        public const int WM_COPYDATA = 0x004A;

        // 定义 COPYDATASTRUCT 结构
        [StructLayout(LayoutKind.Sequential)]
        public struct COPYDATASTRUCT
        {
            public IntPtr dwData;
            public int cbData;
            public IntPtr lpData;
        }

        #endregion

        // 定义MessageFilterAction 结构体
        public enum MessageFilterAction : uint
        {
            MSGFLT_RESET = 0,     // 重置过滤器
            MSGFLT_ALLOW = 1,     // 允许消息
            MSGFLT_DISALLOW = 2   // 禁止消息
        }
        // 定义 消息过滤器状态结构体
        [StructLayout(LayoutKind.Sequential)]
        public struct CHANGEFILTERSTRUCT
        {
            public uint cbSize;
            public uint ExtStatus;
        }

        // 导入 user32.dll 中的函数
        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool ChangeWindowMessageFilterEx(
            IntPtr hWnd,
            uint msg,
            MessageFilterAction action,
            ref CHANGEFILTERSTRUCT pChangeFilterStruct);

        /// <summary>
        /// 设置消息过滤
        /// </summary>
        /// <param name="hWnd"></param>
        /// <param name="message"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        /// <exception cref="System.ComponentModel.Win32Exception"></exception>
        public static bool SetMessageFilter(IntPtr hWnd, uint message, MessageFilterAction action)
        {
            // 初始化结构体
            CHANGEFILTERSTRUCT changeFilter = new CHANGEFILTERSTRUCT
            {
                cbSize = (uint)Marshal.SizeOf(typeof(CHANGEFILTERSTRUCT)),
                ExtStatus = 0
            };

            // 调用 API
            bool result = ChangeWindowMessageFilterEx(hWnd, message, action, ref changeFilter);

            if (!result)
            {
                // 获取错误信息(可选)
                int error = Marshal.GetLastWin32Error();
                throw new System.ComponentModel.Win32Exception(error);
            }
            return result;
        }



        protected override void OnClosed(EventArgs e)
        {
            _hwndSource?.RemoveHook(WndProc);
            base.OnClosed(e);
        }

        private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
        {
            txtMessage.Text = "";
        }
    }
}

4、运行结果:

接收端是管理员权限,发送端是普通权限,可以收发数据了

四、总结

1、调用 ChangeWindowMessageFilterEx 需要设置第三个参数 action 的值为 枚举 :MessageFilterAction.MSGFLT_ALLOW,也就是数值 "1"。

参考资料:

1、ChangeWindowMessageFilterEx 函数 (winuser.h) - Win32 apps | Microsoft Learn

2、受保护的模式 - Win32 apps | Microsoft Learn

3、Windows 完整性机制设计 | Microsoft Learn

相关推荐
攻城狮CSU11 小时前
WPF中核心接口 INotifyPropertyChanged
wpf
c#上位机11 小时前
wpf之Interaction.Triggers
c#·wpf
是木子啦15 小时前
wpf passwordbox控件 光标移到最后
c#·wpf
The Sheep 202315 小时前
wpf 命令理解
wpf
布伦鸽15 小时前
C# WPF DataGrid使用Observable<Observable<object>类型作为数据源
开发语言·c#·wpf
分布式存储与RustFS1 天前
告别复杂配置:用Milvus、RustFS和Vibe Coding,60分钟DIY专属Chatbot
wpf·文件系统·milvus·对象存储·minio·rustfs·vibe
攻城狮CSU2 天前
WPF 绑定机制实现原理
wpf
攻城狮CSU2 天前
WPF 之数据绑定一(Data Binding)
wpf
wuty0072 天前
记录一下 WPF进程 SendMessage 发送窗口消息进行进程间通信,存在进程权限无法接受消息的问题
wpf·进程间通信·sendmessage·进程权限