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
要执行的操作,可以采用以下值之一:
值 | 含义 |
---|---|
|
允许消息通过筛选器。 这使 hWnd 能够接收消息,无论消息的来源如何,即使消息来自较低特权进程也是如此。 |
|
阻止消息从较低特权进程传递到 hWnd ,除非使用 ChangeWindowMessageFilter 函数或全局允许该消息在进程范围内传递。 |
|
将 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