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:
- 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
- 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
- When RECEIVER starts, you could save off the thread ID in shared memory.
- (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);
}