C#:WPF:プロセス間通信まとめ。
背景
①.NETリモーティングを使用して実装
⇒チョーカンタン!
しかし、全てのプロセスがサーバにもクライアントにもなる場合は。。。どうすればいいんだ?
単純に、同じAPが動いていればその全てにメッセージを投げたいんだ!!
②Win32API SendMessageによる実装
⇒C慣れないから見よう見まねだけど。。。とりあえずできた!!
ので忘れないうちにメモしておく。
実装ポイント
1.複数メッセージを送りたいのでメッセージNoのEnumを定義
public enum MessageCode
{
MessageA = 0,
MessageB = 1,
MessageC = 2
}
2.送受信に使用する構造体を定義
※良くわかっていないですが、以下のようにしました。
・dwDataにメッセージNoを入れて
・lpDataに送信したい文字列を入れて
・cbDataにlpDataのサイズを入れる
// SendMessageで送る構造体(Unicode文字列送信に最適化したパターン)
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpData;
}
// 上記構造体だと処理で扱いにくいので、扱いやすいようにクラスを定義した。
public class DataObject
{
public string data { get; private set; }
public MessageCode dataType { get; private set; }
public DataObject(string relData MessageCode mesCode)
{
this.data = relData;
this.dataType = mesCode;
}
}
3.メッセージ送信用クラスを定義
今回は、以下のように使えるようにした。
MessageSender.sendMessageA(data,自身のウインドウハンドル);
※IDisposable実装してメモリ解放した方が良いんだよね!? ほんとは。。。
public class MessageSender
{
// --- ●メッセージ送信用メソッド
public static void sendMessageA(String sendData, IntPtr srcHandle)
{
new MessageSender(MessageCode.MessageA, sendData, srcHandle);
}
public static void sendMessageB(String sendData, IntPtr srcHandle)
{
new MessageSender(MessageCode.MessageB, sendDatasendData, srcHandle);
}
public static void sendMessageC(String sendData, IntPtr srcHandle)
{
new MessageSender(MessageCode.MessageC, sendData, srcHandle);
}
// ---------------------------
private const uint WM_COPYDATA = 0x004A;
public MessageSender(MessageCode code, String sendData, IntPtr srcHandle)
{
COPYDATASTRUCT sendData = new COPYDATASTRUCT();
sendData.dwData = new IntPtr((int)code);
sendData.cbData = sendData.Length * sizeof(char);
sendData.lpData = sendData;
foreach (Process targetProcess in GetPreviousProcess())
{
SendMessage(targetProcess.MainWindowHandle, WM_COPYDATA, srcHandle, ref sendData);
}
}
/// <summary>
/// 実行中の同じアプリケーションのプロセスを取得します。
/// </summary>
/// <returns></returns>
public static List<Process> GetPreviousProcess()
{
Process curProcess = Process.GetCurrentProcess();
Process[] allProcesses = Process.GetProcessesByName(curProcess.ProcessName);
var targetProcesses = new List<Process>();
foreach (Process checkProcess in allProcesses)
{
// 自分自身のプロセスIDは無視する
if (checkProcess.Id != curProcess.Id)
{
// プロセスのフルパス名を比較して同じアプリケーションか検証
if (String.Compare(checkProcess.MainModule.FileName, curProcess.MainModule.FileName, true) == 0)
{
// 同じフルパス名のプロセスを取得
targetProcesses.Add(checkProcess);
}
}
}
return targetProcesses;
}
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
public static extern int SendMessage(IntPtr hwnd, uint msg, IntPtr wParam, ref COPYDATASTRUCT lParam);
}
4.メッセージ受信のためにWndProcをフックする。
private IntPtr myHandle;
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// WndProcを処理する。
myHandle = new WindowInteropHelper(this).Handle;
HwndSource source = HwndSource.FromHwnd(myHandle);
source.AddHook(new HwndSourceHook(WndProc));
}
5.メッセージ受信部の実装をする。
メッセージ送信側へ任意の戻り値を返すことも可能。
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case 0x004A: // WM_COPYDATA
MessageBox.show(ReceiveString(lParam).data);
handled = true;
}
// 送信者はこの戻り値を取得することができる。(ここでは、Zero固定)
return IntPtr.Zero;
}
private data ReceiveString(IntPtr lParam)
{
DataObject data = null;
try
{
COPYDATASTRUCT cds = (COPYDATASTRUCT)Marshal.PtrToStructure(lParam, typeof(COPYDATASTRUCT));
// ここで受信したデータが扱えるようになる。
data = new DataObject(cds.lpData.Substring(0, cds.cbData / 2), (MessageCode)cds.dwData);
}
catch { data = null; }
return data;
}
以上、いったんこれで期待した動きを実現することができた。
良くわかっていないから手直しが必要だろうが。。。