メッセージングと匿名(その2)
匿名を一意とする方法を模索していたが、artonさんのアドバイスがドンピシャだったので、実際に試してみた。
古のDDEの手法はどうですか? (というか読んでいてDDEを思い出したわけですが)
利用するのはATOM。GlobalAddAtom(LPCTSTR)で作成、GlobalGetAtomNameで取得。マシングローバルな文字列のintern表と考えることができます。
なるほど、自分自身は遥か昔(VB2やDelphi2の頃)に一度使ったことがあったATOMだが、すっかり失念していた。名前に一意のIDを振るには持ってこいの方法ではないか。
ATOMも他のWin32API同様に.NETからでもP/Invoke経由で簡単に使えるので、サンプルの実装としては、まずP/Invoke部分をユーティリティ等で書いて、
public static class Util { #region using P/Invoke Win32 [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] static extern ushort GlobalAddAtom(string lpString); [DllImport("kernel32.dll", SetLastError=true, ExactSpelling=true)] static extern ushort GlobalDeleteAtom(ushort nAtom); #endregion public static ushort GetGlobalId(string key) { return GlobalAddAtom(key); } public static bool ReleaseGlobalId(ushort id) { return GlobalDeleteAtom(id) == 0; } }
あとは、サブスクライバ側でコンストラクタで文字列から一意のidを生成し、Disposeで開放してやれば良いだろう。
public class IpcSubscriber : IDisposable { protected string channelName; protected ushort id; protected Form form; public IpcSubscriber(Form form) :base(form, Assembly.GetEntryAssembly().GetName().Name) {} public IpcSubscriber(Form form, string channelName) { this.channelName = channelName; this.form = form; this.id = Util.GetGlobalId(this.ChannelName); form.Disposed += delegate(object sender, EventArgs args) { this.Dispose(); }; } public void Dispose() { Util.ReleaseGlobalId(this.id); } }
これで、名前とサブスクライバの一意性が保たれたオブジェクトを生成できる。
なお、GlobalAtomはシステムリソースなので明示的な開放が必要だ。従ってサブスクライバはIDisposableを実装して、必ずアトムを開放することが必要になることに注意する。実際には、Formと一緒に使うことになるので、その場合はサンプルの様に、Disposedeイベントをフックして自らのDisposeを呼ぶと良いだろう。また、引数"channelName"を指定しないコンストラクタが匿名を使用するケースだが、通信は実行アセンブリ(アプリケーション)毎に行うこととして、エントリアセンブリの名前からIDを生成している。(これで、同一アプリケーションを複数起動した場合も同一サブスクライバの扱いとなる)
そういえば"GlobalAdd(Delete)Atom"でぐぐったことなど無かったので、一通り調べてみたが、そもそものDDEを叩く際の作法から、ATOM文字列を使ってプロセス間通信を行う用途(これなんて懐かしかった。確か256文字限定だったような)、登録ショートカットを一意にする用途から、セマフォ的な用途まで、いろいろな使い方があるものだなと興味深かった。相変わらず深いWin32の世界だ。