发送/接收消息到/从两个运行的应用程序

时间:2012-07-06 08:45:36

标签: c# message

我有两个名为SENDER和RECEIVER的应用程序。

RECEIVER将由SENDER以System.Diagnostics.Process对象

启动

RECEIVER将以隐藏模式启动,因此它没有MainWindowHandle

然后我们无法使用Win32.WM_COPYDATA向RECEIVER发送邮件,因为它需要MainWindowHandle

我需要的是能够通过任何方法定期发送和接收消息。

我检查了手动MainWindowHandle的以下链接,但没有帮助:

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

一个解决方案可能是System.Diagnostics.Process的有用对象,它可以帮助我们将消息发送到进程。

5 个答案:

答案 0 :(得分:14)

有两种方法可以在两个流程之间共享信息。

首先,当你的应用程序扩展时,你必须考虑两个进程是否总是在同一台机器上。

不同的机器

  • 使用TCP / UDP套接字连接(可以是最快的解决方案)
  • 使用MSMQ
  • 使用WebServices,WCF或Restful Web Service。
  • 从数据库中的公共条目读取。 (不推荐)
  • 命名管道(检查this)(命名管道可以在同一台机器上或通过网络飞行)

始终在同一台机器上。

首选:MSMQ

如果我是你,我会保留在不同机器中使用进程的能力,因此我会像Maarten建议的那样使用两个使用MSMQ进行通信的Windows服务。为什么呢?

  1. MSMQ允许您不丢失消息(如果RECEIVER已关闭)
  2. MSMQ允许您在同一台机器或不同机器中拥有流程
  3. Windows服务使您能够轻松启动/停止流程
  4. Windows服务可以监控我的SNMP,通常它们可以轻松地与Windows管理工具集成。
  5. 第二选择:Restful Web Service

    如果您不想使用MSMQ,我将使用IIS中托管的两个Restful Web Service来传达这两个进程。如果你有一个场景,如果他们迟到,RECEIVER对来自SENDER的消息不感兴趣,那么它会非常有用。

答案 1 :(得分:1)

CreateFromFile方法从磁盘上的现有文件创建内存映射文件。下面的示例为一个很大的文件的一部分创建一个内存映射视图,并对其一部分进行操作。

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);
    }
}

下面的示例为另一个进程打开相同的内存映射文件。

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 :(得分:1)

对于在同一台计算机上运行的进程,最轻量的解决方案可能是使用PostThreadMessage()。我真的很惊讶没有人给出这个答案,这是老式的Windows编程。 OP非常接近。观察:

  • 每个进程都有一个主线程(本地线程)。
  • 主线程有一个消息队列。
  • 主线程具有系统全局的线程ID。

所有成分都在那里,只需将它们放在一起即可。从概念上讲很简单,棘手的部分是将RECEIVER的主线程ID传递给SENDER。您有几种选择:

  1. 从SENDER,在Win32中,您可以从RECEIVER的线程信息块中挖掘线程ID。 https://stackoverflow.com/a/8058710/420400
  2. 启动RECEIVER时,您可以将线程ID保存在其Process.StartInfo.Environment中。它确实存在,并且可以在SysInternals的Process Explorer中看到-但您很难掌握它。再次有一个Win32解决方案。 https://www.codeproject.com/Articles/25647/Read-Environment-Strings-of-Remote-Process
  3. RECEIVER启动时,您可以将线程ID保存在共享内存中。
  4. (或更好的东西...)

选项1和2似乎是安全漏洞,因此在此示例中,我选择了选项3,并在一个很小的内存映射文件中共享了线程ID。

RECEIVER看起来像这样

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://docs.microsoft.com/en-us/dotnet/api/system.idisposable?view=netframework-4.7.2
    #region
    ...
    #endregion
}

SENDER看起来像这样

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 serviceThreadId);
        }

        PostThreadMessage(threadId, (uint)WM.USER, UIntPtr.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);
}

答案 3 :(得分:0)

我认为MSMQ是个不错的选择。

答案 4 :(得分:0)

我知道老问题了。
偶然发现了它,因为我有类似的任务。
一个应用程序是从另一个应用程序开始的-它会再次结束,但没人知道何时。 1.应用可以再次启动2.,但必须等到2.的先前实例退出后才能启动。
始终在同一台PC(和Windows)上。

在程序运行时,使用注册表设置一个简单的值很容易。在退出时,请再次将其删除/重置。
1.应用程序可以检查注册表,以查看是否可以启动2.应用程序的另一个实例。

您还可以使用注册表在应用程序之间传递值。 缺点是,应用程序必须轮询注册表,而不是发送消息。简单但效果较差。

因此可能取决于所需的条件。