В .NET, как лучше всего взаимодействовать двум процессам на одной машине? - PullRequest
6 голосов
/ 22 января 2009

Какой лучший (или, может быть, не самый лучший - просто хороший) способ взаимодействия двух процессов на одной машине с использованием .NET?

На самом деле два процесса в приложении, над которым я работаю, даже не две разные программы; это всего лишь два экземпляра одного и того же EXE-файла. Я хотел сделать что-то вроде одноэлементного приложения, но иметь его для каждого пользователя (то есть сервер терминалов или сервер Citrix или App-V с несколькими пользователями должен иметь возможность запускать свою собственную копию приложения). Если тот же пользователь запускает другой экземпляр, он должен просто делегировать задачу уже запущенному экземпляру и завершить работу. Только один экземпляр на пользователя программы должен быть запущен. До сих пор я сделал (благодаря StackOverflow) часть, которая определяет, запущен ли экземпляр приложения, с помощью Mutex. Но мне нужен второй экземпляр приложения, чтобы можно было отправлять данные в первый экземпляр приложения.

Я склоняюсь к использованию именованных каналов и WCN NetNamedPipeBinding для этого, но если у вас есть лучшие идеи, я действительно буду благодарен. Спасибо:)

Ответы [ 7 ]

3 голосов
/ 22 января 2009

IPC - это то, что я использовал в прошлом для этого. И это удивительно легко. .Net remoting - это хороший вариант, но, к сожалению, это ограниченный вариант, потому что вы не можете, например, использовать его на CF.

Ниже приведена копия класса, который я использую для межпроцессного взаимодействия, вы можете использовать его в сочетании с MutEx, если хотите, но это не обязательно. Пока «pMappedMemoryName» и «pNamedEventName» одинаковы в обоих процессах, все должно работать нормально. Я пытался сделать это как можно более управляемым событием.

Просто используйте метод Poke для записи данных и метод Peek для их чтения, хотя я разработал его для автоматического запуска события при появлении новых данных. Таким образом, вы можете просто подписаться на событие IpcEvent и не беспокоиться о дорогих опросах.

  public class IpcService {
    private IServiceContext mContext;
    const int maxLength = 1024;
    private Thread listenerThread;
    private readonly string mMappedMemoryName;
    private readonly string mNamedEventName;
    public event EventHandler<TextualEventArgs> IpcEvent;
    private readonly bool mPersistantListener;

    public IpcService(bool pPersistantListener)
      : this(pPersistantListener, "IpcData", "IpcSystemEvent") {
      ;
    }

    public IpcService(bool pPersistantListener, string pMappedMemoryName, string pNamedEventName) {
      mPersistantListener = pPersistantListener;
      mMappedMemoryName = pMappedMemoryName;
      mNamedEventName = pNamedEventName;
    }

    public void Init(IServiceContext pContext) {
      mContext = pContext;
      listenerThread = new Thread(new ThreadStart(listenUsingNamedEventsAndMemoryMappedFiles));
      listenerThread.IsBackground = !mPersistantListener;
      listenerThread.Start();
    }


    private void listenUsingNamedEventsAndMemoryMappedFiles() {
      IntPtr hWnd = EventsManagement.CreateEvent(true, false, mNamedEventName);
      while (listenerThread != null) {
        if (Event.WAITOBJECT == EventsManagement.WaitForSingleObject(hWnd, 1000)) {
          string data = Peek();
          EventsManagement.ResetEvent(hWnd);
          EventHandler<TextualEventArgs> handler = IpcEvent;
          if (handler != null) handler(this, new TextualEventArgs(data));
        }
      }
      EventsManagement.SetEvent(hWnd);
      Thread.Sleep(500);
      HandleManagement.CloseHandle(hWnd);
    }

    public void Poke(string format, params object[] args) {
      Poke(string.Format(format, args));
    }

    public void Poke(string somedata) {
      using (MemoryMappedFileStream fs = new MemoryMappedFileStream(mMappedMemoryName, maxLength, MemoryProtection.PageReadWrite)) {
        fs.MapViewToProcessMemory(0, maxLength);
        fs.Write(Encoding.ASCII.GetBytes(somedata + "\0"), 0, somedata.Length + 1);
      }
      IntPtr hWnd = EventsManagement.CreateEvent(true, false, mNamedEventName);
      EventsManagement.SetEvent(hWnd);
      Thread.Sleep(500);
      HandleManagement.CloseHandle(hWnd);
    }

    public string Peek() {
      byte[] buffer;
      using (MemoryMappedFileStream fs = new MemoryMappedFileStream(mMappedMemoryName, maxLength, MemoryProtection.PageReadWrite)) {
        fs.MapViewToProcessMemory(0, maxLength);
        buffer = new byte[maxLength];
        fs.Read(buffer, 0, buffer.Length);
      }
      string readdata = Encoding.ASCII.GetString(buffer, 0, buffer.Length);
      return readdata.Substring(0, readdata.IndexOf('\0'));
    }

    private bool mDisposed = false;

    public void Dispose() {
      if (!mDisposed) {
        mDisposed = true;
        if (listenerThread != null) {
          listenerThread.Abort();
          listenerThread = null;
        }
      }
    }

    ~IpcService() {
      Dispose();
    }

  }

Надеюсь, это поможет.

3 голосов
/ 22 января 2009

Именованные каналы, как правило, лучше, но также учитывают MSMQ для сценария «забей и забудь», который обеспечивает доставку сообщений с течением времени. Это также зависит от размера ваших сообщений. TCP станет хитрым, потому что вам понадобится уникальный порт для каждого пользователя.

2 голосов
/ 22 января 2009

Я бы пошел на именованные каналы.

По сути, вам потребуется уникальная конечная точка для каждого пользователя. Как и в случае с вашим Mutex, вы, вероятно, будете использовать имя пользователя, чтобы узнать имя именованного канала для использования. Это может даже заменить использование вами Mutex: попытайтесь выяснить, существует ли именованный канал для этого конкретного объекта, и если это так, значит, вы запускаете второй экземпляр.

Труднее отобразить порт TCP на пользователя.

О, и используя Named Pipes, я на самом деле говорю "Remoting over Named Pipes".

0 голосов
/ 22 января 2009

Другой способ - использовать старый добрый DDE (Dynamic Data Exchange): http://www.codeplex.com/ndde

DDE основывается на сообщениях Windows, но над ним находится уровень абстракции.

(да, мне нравятся мои друзья WIN32-api);)

0 голосов
/ 22 января 2009

Sendmessage / Postmessage и Getmessage - API, которые мне нравятся для этого.

SendMessage:
http://pinvoke.net/default.aspx/user32.SendMessage

PostMessage:
http://pinvoke.net/default.aspx/user32.PostMessage

GetMessage:
http://pinvoke.net/default.aspx/user32.GetMessage

0 голосов
/ 22 января 2009

Вы можете установить область действия Mutex на "локальный сеанс", поставив перед его именем префикс "Local \"; прочитайте MSDN для получения дополнительной информации. Таким образом, вы можете ограничить количество экземпляров процесса для сеанса терминала (хорошо, это не совсем то, что вы просили).

0 голосов
/ 22 января 2009

.Net Remoting также может быть удобно. Это быстро и легко реализовать.

подробнее на MSDN

...