前言
最近在接手一个同事写的WPF项目,是使用.NetFramwork 开发的,使用的进程间通信没有使用我们之前封装的基于WebSocket的封装组件的,而是使用Win32的方式:发送端用的Windows Api:SendMessage ,接受端使用的是 钩子监听windows 的消息回传。
相信很多做桌面应用的,这种通信应该都是很常用,并且见怪不怪的。可是可能很多没有注意到进程权限的情况,这种通信存在有坑,并且这个坑还埋的挺深的。
遇到的问题
由于该WPF的项目的启动方式存在很多方式,如果桌面点击的方式(普通权限的),右键管理员启动的方式(管理员权限的),开机自启的方式(System权限降权的方式,普通权限),OTA之后启动(管理员权限),这样就会出现该进程窗口可能启动后的权限是不可预见的,并且用户是可以随意的变更用户权限去启动。然而,在一次测试中,做了升级后,启用了该应用,其他跟它通信的进程就无法跟该进程通信的。很诡异,只要是OTA之后,其他进程就无法通信,开机之后(普通权限)就可以通信。观察了日志,又没有报什么异常。
复现问题
一、创建一个WPF消息 发送端
<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>
namespace FramworkSender
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
// 获取接收窗口的句柄
IntPtr hwnd = FindWindow(null, "FramworkReceieve");
if (hwnd == IntPtr.Zero)
{
MessageBox.Show("找不到窗口");
}
else
{
SendMessageString(hwnd, "123");
}
}
#region RegisterWindow
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern uint RegisterWindowMessage(string lpString);
private uint _customMessageId;
#endregion
#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
}
}
二、创建一个WPF 消息 的接收端
<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>
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private uint _customMessageId;
private HwndSource _hwndSource;
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
_customMessageId = RegisterWindowMessage("MyApp");
// 获取窗口句柄并添加消息钩子
_hwndSource = PresentationSource.FromVisual(this) as HwndSource;
_hwndSource?.AddHook(WndProc);
}
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
public string content;
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 RegisterWindows
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern uint RegisterWindowMessage(string lpString);
#endregion
#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
protected override void OnClosed(EventArgs e)
{
_hwndSource?.RemoveHook(WndProc);
base.OnClosed(e);
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
txtMessage.Text = "";
}
}
三、结果
1、俩个都是管理员权限的,是可以接受到数据的

2、俩个进程都是普通权限的,是可以接受到数据

3、发送端是管理员权限,接收端是 普通权限,是可以接受到数据

4、发送端是普通权限,接收端是 管理员权限,是接受不到数据

总结:
1、进程间通信,最好使用无权限限制的方案
2、使用ChangeWindowMessageFilterEx 进行权限过滤