15

I have two applications called SENDER and RECEIVER.

RECEIVER will be launched by SENDER with the System.Diagnostics.Process.Start method

RECEIVER will be launched in hidden mode so it does not have a MainWindowHandle.

Then we could use Win32.WM_COPYDATA in order send message to RECEIVER, but it needs MainWindowHandle so we can't.

What I need is ability to send and receive messages periodically by any method.

I checked the following link for a manual about MainWindowHandle, but it didn't help:

Send message to a Windows process (not its main window)

One solution might be an object from System.Diagnostics.Process which might help us to send messages to a process.

mekb
  • 554
  • 8
  • 22
Hamid
  • 817
  • 1
  • 13
  • 29
  • Please suggest a solution which would not required an installation. like as MSMQ which might be not installed on end user windows. – Hamid Jul 06 '12 at 09:36
  • Sorry, I was assuming that you were having processes in a server. Could you give more info about scenario? Where will be SENDER and RECEIVER installed, size of message, which constrains do you have... – Oscar Foley Jul 06 '12 at 09:42
  • Message is a simple string (maybe in JSON format). I want send a simple string to RECEIVER then RECEIVER collect information from internet then RECEIVER send back populated information to SENDER as a simple string. – Hamid Jul 06 '12 at 09:59
  • @Hamid - Almost 8 years late to the question, hopefully you've got it working by now. Please see my answer anyway. – Paul Williams May 05 '20 at 00:40

5 Answers5

20

There are different ways to share information between 2 processes.

First at all you have to think if both processes are going to be always in the same machine or not when your application scales up.

Different Machines

  • Use TCP/UDP socket connection (Can be the quickest solution)
  • Use MSMQ
  • Use WebServices, WCF or Restful Web Service.
  • Reading from a common entry in a db. (Not recommended)
  • Named Pipes (Check this) (Named pipes can be in same machine or fly over a network)

Always in same machine.

Preferred choice: MSMQ

If I were you I would preserve the ability of having processes in different machines so I would use, as Maarten suggested, two windows services that uses MSMQ to communicate. Why?

  1. MSMQ allows you not to lose messages (in case RECEIVER is down)
  2. MSMQ allows you to have processes in same machine or in different machines
  3. Windows service give you the ability to start/stop the processes easily
  4. Windows service can me monitored my SNMP and in general they integrate easily with windows admin tools.

Second preferred choice: Restful Web Service

If you don't want to use MSMQ I would use two Restful Web Service hosted in IIS to communicate both processes. It can be useful if you have an scenario where RECEIVER is not interested in messages from SENDER if they arrive late.

Oscar Foley
  • 6,817
  • 8
  • 57
  • 90
  • 2
    Named pipes can also be used over the network. Also do note that WCF can be self-hosted by a process, there is no need for IIS. WCF also allows synchronous communication. – ken2k Jul 06 '12 at 09:25
  • +1 to your comment. You are right. Never used named pipes in C#. WCF can be self-hosted or IIS hosted... I am used to host it in IIS. WCF allows synchronous communication by default and asynchronous if you code it this way. Not sure what Hamid needs. I asked him for more info. Thanks :) – Oscar Foley Jul 06 '12 at 09:47
  • I worked with WCF before, but you say that WCF might be works slow, right? Speed is the main subject in my application. – Hamid Jul 06 '12 at 10:02
  • If speed is a requirement then I would go for a TCP or UDP socket connection (TCP probably if you need reliability and UDP only in the case message lose importance with time. For example if you are sending the last datetime your client was a live, UDP makes sense because if message didn't arrived you don't want TCP to resend an old deprecated message... you prefer have another UDP message sent) – Oscar Foley Jul 06 '12 at 10:08
  • Where is SendMessage API in the list? – Martin.Martinsson Jul 27 '18 at 18:52
  • 1
    Do not use FileSystemWatcher for interprocess communication. Because of disk caching and other reasons, it is not reliable. https://stackoverflow.com/a/7191512/420400 – Paul Williams May 04 '20 at 13:42
4

For a process running on the same machine, probably the lightest weight solution is to use PostThreadMessage(). I'm really surprised no one gave this answer, it's old school Windows programming. The OP was very close. Observe:

  • Every process has a main thread (native thread).
  • The main thread has a message queue.
  • The main thread has a thread ID which is global to the system.

All of the ingredients are there, it's a matter of putting them together. Conceptually it's straightforward, the tricky part is communicating the RECEIVER's main thread ID to the SENDER. You have a few options:

  1. From the SENDER, in Win32 you could dig out the thread ID from the RECEIVER's Thread Information Block. https://stackoverflow.com/a/8058710/420400
  2. When RECEIVER starts, you could save off the thread ID in its Process.StartInfo.Environment. It's really there, and it will be visible in SysInternals' Process Explorer - but you'll have difficulty getting at it. Again there is a Win32 solution for this. https://www.codeproject.com/Articles/25647/Read-Environment-Strings-of-Remote-Process
  3. When RECEIVER starts, you could save off the thread ID in shared memory.
  4. (Or something better...)

Options 1 & 2 seem like security exploits, so for this example I went with option 3 and shared the thread ID in a tiny memory mapped file.

The RECEIVER looks something like this

enum WM { USER = 0x400 }

class MyMessageFilter : IMessageFilter
{
    public bool PreFilterMessage(ref Message m)
    {
        if ((WM)m.Msg == WM.USER)
        {
            Console.WriteLine("WM_USER received.");
            return true;
        }

        return false;
    }
}

class RECEIVER : IDisposable
{
    MemoryMappedFile mmf;
    bool disposed = false;

    public void MyMessageLoop()
    {
        uint mainThreadId = GetCurrentThreadId();
        Console.WriteLine(mainThreadId);
        mmf = MemoryMappedFile.CreateNew(Constants.ThreadIDFileName, IntPtr.Size, MemoryMappedFileAccess.ReadWrite);
        using (var accessor = mmf.CreateViewAccessor(0, IntPtr.Size, MemoryMappedFileAccess.ReadWrite))
        {
            accessor.Write(0, mainThreadId);
        }
        Application.AddMessageFilter(new MyMessageFilter());
        Application.Run();
    }

    [DllImport("kernel32.dll")]
    static extern uint GetCurrentThreadId();

    // Implement IDisposable and ~RECEIVER() to delete the semaphore, omitted for brevity
    // https://learn.microsoft.com/en-us/dotnet/api/system.idisposable?view=netframework-4.7.2
    #region
    ...
    #endregion
}

And the SENDER looks something like this

enum WM { USER = 0x400 }

class Program
{
    static void Main(string[] args)
    {
        string procName = "RECEIVER";
        Process[] processes = Process.GetProcesses();

        Process process = (from p in processes
                           where p.ProcessName.ToUpper().Contains(procName)
                           select p
                          ).First();

        uint threadId;
        using (var mmf = MemoryMappedFile.OpenExisting(Constants.ThreadIDFileName, MemoryMappedFileRights.Read))
        using (var accessor = mmf.CreateViewAccessor(0, IntPtr.Size, MemoryMappedFileAccess.Read))
        {
            accessor.Read(0, out threadId);
        }

        PostThreadMessage(threadId, (uint)WM.USER, IntPtr.Zero, IntPtr.Zero);
    }

    [return: MarshalAs(UnmanagedType.Bool)]
    [DllImport("user32.dll", SetLastError = true)]
    public static extern bool PostThreadMessage(uint threadId, uint msg, IntPtr wParam, IntPtr lParam);
}
Paul Williams
  • 3,099
  • 38
  • 34
  • Imho, this is the best answer. While the accepted answer, written by Oscar Foley, yields some generally nice information, this answer by Paul Williams was actually the SINGLE ONE answer from a bunch of related questions that got me where I needed. In particular, most answers use `PostMessage` and `override WndProc` - WHICH WAS NOT VIABLE FOR ME. If the window in question is a child and the process does NOT have a main window handle this won't work. But `PostThreadMessage + IMessageFilter` will. The idea of using shared memory is a good bonus. – somedotnetguy May 02 '22 at 14:45
  • In the sender, where is the variable threadId set? Where is the variable serviceThreadId set – Aheho May 21 '23 at 21:34
1

Old question, I know.
Stumbled on it, as I have a somewhat similar task.
One app started from the another - it will end again, but nobody knows when.
1. app can start 2. again, but must not until previous instances of 2. has exited.
Always on same PC (and Windows).

The simple thing is off hand to use the registry to set a value, when 2. program is running, and remove/reset it again when it exits.
1. app can check the registry to see if it is OK to start another instance of the 2. app.

You can also use the registry to pass values between the apps. Drawback is, that apps must poll the registry, instead of sending messages. Simpler but less effective.

So probably dependent on what it is needed for.

joce
  • 9,624
  • 19
  • 56
  • 74
1

The CreateFromFile methods create a memory-mapped file from an existing file on disk. The following example creates a memory-mapped view of a part of an extremely large file and manipulates a portion of it.

using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;

class Program
{
    static void Main(string[] args)
    {
        long offset = 0x10000000; // 256 megabytes
        long length = 0x20000000; // 512 megabytes

        // Create the memory-mapped file.
        using (var mmf = MemoryMappedFile.CreateFromFile(@"c:\ExtremelyLargeImage.data", FileMode.Open,"ImgA"))
        {
            // Create a random access view, from the 256th megabyte (the offset)
            // to the 768th megabyte (the offset plus length).
            using (var accessor = mmf.CreateViewAccessor(offset, length))
            {
                int colorSize = Marshal.SizeOf(typeof(MyColor));
                MyColor color;

                // Make changes to the view.
                for (long i = 0; i < length; i += colorSize)
                {
                    accessor.Read(i, out color);
                    color.Brighten(10);
                    accessor.Write(i, ref color);
                }
            }
        }
    }
}

public struct MyColor
{
    public short Red;
    public short Green;
    public short Blue;
    public short Alpha;

    // Make the view brighter.
    public void Brighten(short value)
    {
        Red = (short)Math.Min(short.MaxValue, (int)Red + value);
        Green = (short)Math.Min(short.MaxValue, (int)Green + value);
        Blue = (short)Math.Min(short.MaxValue, (int)Blue + value);
        Alpha = (short)Math.Min(short.MaxValue, (int)Alpha + value);
    }
}

The following example opens the same memory-mapped file for another process.

using System;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;


class Program
{
    static void Main(string[] args)
    {
        // Assumes another process has created the memory-mapped file.
        using (var mmf = MemoryMappedFile.OpenExisting("ImgA"))
        {
            using (var accessor = mmf.CreateViewAccessor(4000000, 2000000))
            {
                int colorSize = Marshal.SizeOf(typeof(MyColor));
                MyColor color;

                // Make changes to the view.
                for (long i = 0; i < 1500000; i += colorSize)
                {
                    accessor.Read(i, out color);
                    color.Brighten(20);
                    accessor.Write(i, ref color);
                }
            }
        }
    }
}

public struct MyColor
{
    public short Red;
    public short Green;
    public short Blue;
    public short Alpha;

    // Make the view brigher.
    public void Brighten(short value)
    {
        Red = (short)Math.Min(short.MaxValue, (int)Red + value);
        Green = (short)Math.Min(short.MaxValue, (int)Green + value);
        Blue = (short)Math.Min(short.MaxValue, (int)Blue + value);
        Alpha = (short)Math.Min(short.MaxValue, (int)Alpha + value);
    }
}
-2

I think MSMQ is a good option.

Maarten
  • 22,527
  • 3
  • 47
  • 68