Jeong Yo Han
Jeong Yo Han

Reputation: 600

communicate with another process (same source code) via wndProc in WPF

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.

image1

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- 0x0100WM_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

Answers (2)

Jeong Yo Han
Jeong Yo Han

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.

enter image description here

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

Mihaeru
Mihaeru

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

Related Questions