C# プロセス間通信をしてみたのでメモ。
正道を行くならIPCチャンネルを使う選択が正しいのかもしれないが、面倒くさいしよく分からないので比較的簡単に実装できるウインドウメッセージを用いた方法にしてみた。
ただしこの方法には次の制限アリ。
- 同じ PC 上のプロセス間のみ
- GUI のあるアプリケーション
ウインドウ宛てにメッセージ送るわけなので当然といえば当然。
やりかたそのものは検索すれば出てくる方法とほぼ同じ。
取り回しが良いように DLL にしてみた。
サンプルプロジェクトの構成
まず Visual Studio のソリューションにプロジェクトを3つ作成。
プロジェクト名 | 種類 | 参照設定 | 備考 |
---|---|---|---|
Server | Windows フォームアプリケーション | HogeHoge | |
Client | Windows フォームアプリケーション | HogeHoge,Server | スタートアッププロジェクト |
HogeHoge | クラスライブラリ | System.Windows.Forms |
送受信まわりを簡単な DLL(HogeHoge) にして Server・Client から参照する。
Client で Server を参照設定しているのは、実行フォルダに Server.exe を作成する為だけの目的なので本来は必要ない。HogeHoge の System.Windows.Forms 参照はウインドウメッセージを扱うので必要。
Server にはテキストボックスを1つ、Client にはボタンを2つ追加すれば準備完了。
HogeHoge.dll
まずは DLL の中身。
using System; using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; namespace HogeHoge { public class Hoge { [DllImport("User32.dll")] private static extern Int32 SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, ref COPYDATASTRUCT lParam); private struct COPYDATASTRUCT { public IntPtr dwData; public Int32 cbData; public string lpData; } private const int WM_COPYDATA = 0x4A; public event EventHandler<ReceiveDataEventArgs> OnReceiveData; public void ReceiveData(Message m) { if (this.OnReceiveData != null) { COPYDATASTRUCT cds = (COPYDATASTRUCT)m.GetLParam(typeof(COPYDATASTRUCT)); this.OnReceiveData(this, new ReceiveDataEventArgs(cds.lpData)); } } public void SendData(IntPtr hwnd, string data) { byte[] buf = Encoding.Default.GetBytes(data); COPYDATASTRUCT cds = new COPYDATASTRUCT(); cds.dwData = IntPtr.Zero; cds.cbData = buf.Length + 1; cds.lpData = data; SendMessage(hwnd, WM_COPYDATA, IntPtr.Zero, ref cds); } } public class ReceiveDataEventArgs : EventArgs { public ReceiveDataEventArgs(string data) { this.ReceiveData = data; } public string ReceiveData { get; private set; } } }
本来 COPYDATASTRUCT の lpData は string ではなくポインタ型だと思う。
ただ、私のようにC言語での開発経験が乏しい人間は得てしてポインタの概念をあいまいにしか理解しておらず敬遠しがちなので string でやってみたw
無理矢理シリアル化すれば独自のクラス型を送れないこともないし・・・
Server.exe
サーバー側のソース。
using HogeHoge; using System; using System.Windows.Forms; namespace Server { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private const int WM_COPYDATA = 0x4A; private Hoge Hoge { get; set; } private void Form1_Load(object sender, EventArgs e) { this.Hoge = new Hoge(); this.Hoge.OnReceiveData += (_o, _e) => textBox1.Text += _e.ReceiveData; } protected override void WndProc(ref Message m) { if (m.Msg == WM_COPYDATA) this.Hoge.ReceiveData(m); base.WndProc(ref m); } } }
起動時に Hoge クラスのインスタンスを作成。
OnReceiveData イベントに処理を追加するのと、WndProc をオーバーライドして WM_COPYDATA が来たときのメッセージを Hoge に渡すだけ。
Client.exe
クライアントのソース。
using HogeHoge; using System; using System.Diagnostics; using System.Windows.Forms; namespace Client { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private Process MyProcess { get; set; } private void button1_Click(object sender, EventArgs e) { this.MyProcess = Process.Start("Server.exe"); this.MyProcess.WaitForInputIdle(); } private void button2_Click(object sender, EventArgs e) { Hoge hoge = new Hoge(); hoge.SendData(this.MyProcess.MainWindowHandle, "波平"); } } }
ボタン1のクリックで Server を起動し、ボタン2をクリックでサーバーに波平を送る。
うまくいけばサーバーのテキストボックスに波平が増殖していく。
まとめ
基本的にクライアント→サーバーの一方通行。
サーバー起動直後にデータ送信までを一気にやってしまうと、なぜか波平が出てこない・・・
WaitForInputIdle で待機しても変化なし。
仕方がないので処理を「サーバー起動」と「データ送信」のニ段階に分けることにしたが、クライアントからサーバーを起動する場合は少し間隔をあけるしかないのか?
また、クライアントから起動せず既に起動しているサーバーに送信する場合は、ウインドウタイトルを探すような手段になってしまいスマートではないかも。
簡易的な目的でなければ、やはりIPCチャンネルがベストか・・・