Reputation: 600
I'm working on implementing communicate with another process via HwndSource
in WPF.
I want My Program (Let's call it A
) to communicate with My Program (also A
) that is on another process on windows.
following images will help me to explain what I've been doing and what I want to implement.
two windows are exactly same program (just different background-color), and they know exactly their Handle each other. If I notice two process have same title, then I call sendMessage
which is defined in user32.dll
with Message -Raw input message constant that can pump message globally- 0x0100
WM_KEYDOWN.
// if same program - but different process
IntPtr lpData = Marshal.StringToHGlobalAnsi("Hi, Im Sender");
MessageBox.Show(Marshal.PtrToStringAnsi(lpData));
DisplayAnotherProcessHandle.Text = p.MainWindowHandle.ToString();
SendMessage(p.MainWindowHandle, WM_RawInput_key, IntPtr.Zero, lpData);
I successfully pass WM_RawInput_Key
, IntPtr.Zero
to opponent program. but I have problem with lParam. I can't get lParam data. I tried to Marshal c# string
with Marshal.StringToBSTR
or Marshal.StringToAuto
... but in following method I just get Empty.String
.
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg != WM_RawInput_key) return IntPtr.Zero;
if (wParam == IntPtr.Zero) // <-- opponent process hit this conditional syntax nicely.
{
var str = Marshal.PtrToStringAnsi(lParam); // but I'm failed pass lParam, or Marshalling lParam I don't know ...
MessageBox.Show(str);
DisplaySucceedOrNot.Text = str;
}
return IntPtr.Zero;
}
I don't know which memory parameter IntPtr lParam
indicates. I don't know even this technique is valid. I need your help. thank you for reading
following source code is my entire source code of example program.
<Window x:Class="wndProc_IPC_WPF.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:wndProc_IPC_WPF"
mc:Ignorable="d"
Title="WndProc-IPC-WPF" Height="450" Width="800">
<StackPanel>
<Button Name="AddHandler" Width="Auto" Height="100" Content="Add Handler to This Handle" Click="AddHandler_Click"></Button>
<Button Name="CreateNewProcess" Width="Auto" Height="100" Content="Create New Process Window" Click="CreateNewProcess_Click"></Button>
<Button Width="Auto" Height="100" Content="SendMessage To Another Process" Name="SendMessageBtn" Click="SendMessage_Click"></Button>
<StackPanel Orientation="Horizontal">
<TextBlock Text="MyHandle : "></TextBlock>
<TextBlock x:Name="DisplayMyProcessHandle" Margin="50 0 0 0"></TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Another Handle : "></TextBlock>
<TextBlock x:Name="DisplayAnotherProcessHandle" Margin="50 0 0 0"></TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Succed?"></TextBlock>
<TextBlock x:Name="DisplaySucceedOrNot" Margin="50 0 0 0"></TextBlock>
</StackPanel>
</StackPanel>
</Window>
public partial class MainWindow : Window
{
[DllImport("user32.dll")]
private static extern bool SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
string AppName;
public static UInt32 WM_RawInput_key = 0x0100;
public MainWindow()
{
InitializeComponent();
AppName = this.Title;
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg != WM_RawInput_key) return IntPtr.Zero;
if (wParam == IntPtr.Zero)
{
var str = Marshal.PtrToStringAnsi(lParam);
MessageBox.Show(str);
DisplaySucceedOrNot.Text = str;
}
return IntPtr.Zero;
}
private void CreateNewProcess_Click(object sender, RoutedEventArgs e)
{
if (Process.GetProcessesByName(AppName).Length > 1)
{
MessageBox.Show("Already 2 Process Exist.");
return;
}
var path = (System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
Process p = new Process();
p.StartInfo.FileName = path;
p.Start();
}
private void SendMessage_Click(object sender, RoutedEventArgs e)
{
foreach (var p in Process.GetProcessesByName(AppName))
{
if (p.MainWindowHandle == Process.GetCurrentProcess().MainWindowHandle) continue;
{
IntPtr lpData = Marshal.StringToHGlobalAnsi("Hi, Im Sender");
MessageBox.Show(Marshal.PtrToStringAnsi(lpData));
DisplayAnotherProcessHandle.Text = p.MainWindowHandle.ToString();
SendMessage(p.MainWindowHandle, WM_RawInput_key, IntPtr.Zero, lpData);
}
}
}
private void AddHandler_Click(object sender, RoutedEventArgs e)
{
HwndSource source = HwndSource.FromHwnd(Process.GetCurrentProcess().MainWindowHandle);
source.AddHook(new HwndSourceHook(WndProc));
}
}
Upvotes: 2
Views: 1394
Reputation: 600
Thank you Mihaeru, actually I was on finding example source of sendMessage/wndProc
with WM_COPYDATA
in Wpf after posting my question. Like Raw-Input message WM_KEYDOWN
, WM_COPYDATA
also provide global message pump(right?, I don't know exact theory on old windows... -_-;;)
I failed many times. and I found that I should allocate cbData
with Length of string. if I add Attribute [MarshalAs(UnmanagedType.LpWStr)]
which 2 byte per character, then I need allocate string.length*2
to cbData.
I succeed to pass message to opponent process.
following source is answer source
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
[MarshalAs(UnmanagedType.LPStr)]
public string lpData;
}
[DllImport("user32.dll")]
private static extern bool SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, ref COPYDATASTRUCT lParam);
string AppName;
public static UInt32 WM_COPYDATA = 0x004A;
public MainWindow()
{
InitializeComponent();
AppName = this.Title;
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg != WM_COPYDATA) return IntPtr.Zero;
if (wParam == IntPtr.Zero)
{
COPYDATASTRUCT cd = new COPYDATASTRUCT();
cd = (COPYDATASTRUCT)Marshal.PtrToStructure(lParam, typeof(COPYDATASTRUCT));
MessageBox.Show(cd.lpData);
DisplaySucceedOrNot.Text = cd.lpData;
}
return IntPtr.Zero;
}
private void CreateNewProcess_Click(object sender, RoutedEventArgs e)
{
if (Process.GetProcessesByName(AppName).Length > 1)
{
MessageBox.Show("Already 2 Process Exist.");
return;
}
var path = (System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
Process p = new Process();
p.StartInfo.FileName = path;
p.Start();
}
private void SendMessage_Click(object sender, RoutedEventArgs e)
{
foreach (var p in Process.GetProcessesByName(AppName))
{
if (p.MainWindowHandle == Process.GetCurrentProcess().MainWindowHandle) continue;
{
IntPtr lpData = Marshal.StringToHGlobalAnsi("Hi, Im Sender");
COPYDATASTRUCT cd = new COPYDATASTRUCT();
cd.lpData = "Hi, Im Sender";
cd.dwData = p.MainWindowHandle;
cd.cbData = cd.lpData.Length+1;
MessageBox.Show(cd.lpData);
DisplayAnotherProcessHandle.Text = p.MainWindowHandle.ToString();
SendMessage(p.MainWindowHandle, WM_COPYDATA, IntPtr.Zero, ref cd);
}
}
}
private void AddHandler_Click(object sender, RoutedEventArgs e)
{
HwndSource source = HwndSource.FromHwnd(Process.GetCurrentProcess().MainWindowHandle);
source.AddHook(new HwndSourceHook(WndProc));
DisplayMyProcessHandle.Text = Process.GetCurrentProcess().MainWindowHandle.ToString();
}
Upvotes: 0
Reputation: 533
The wparam
and lparam
are just a pointers to your local process memory, which the other process cannot access. The wparam
works well in your example as you just process the pointer value and not the data the it points to.
For a similar purpose I used WM_COPYDATA
message where you provide a pointer to a COPYDATASTRUCT
. There you can encapsulate also complex data structs, which can be handled read-only in your receiving process.
Microsoft provides a useful example here: https://learn.microsoft.com/en-us/windows/win32/dataxchg/using-data-copy
Upvotes: 2