(Visual Studio 2010 Express)

まず重い。低スペックパソコンでは、ストレスない開発など不可能。
Celeron 2.0GHz + 1.97GB RAM、グラボなし環境下では、XAMLデザイナがちょくちょくフリーズする。あるいは画面が乱れて、現状が見えなくなる。
Core i7 + 6.0GB RAM、Radeon環境下でも、時々表示が狂う。バグか?

Intellisenseに至っては、エディタのリアルタイム描画を邪魔する始末。
記述中にIntelilsenseが開いていると、記述中の文字列が見えない。

.NET Framework 4.0 / C#

なんでPathってクラスを増やした・・・!
なんだかんだファイルシステムを扱う機会は多いので、そのたびにSystem.IO.PathSystem.Windows.Shapes.Path が衝突を起こして、System.IO.Pathと書く必要が生じる。

名づけ参照(だっけ? 「using IOPath = System.IO.Path」みたいな)をすればいいんでしょうけど、そんな馬鹿なことって。
System.IO.Path自体は、Path.Combine(params string[] ~なんてのが増えてたりして、ありがたい。
というか、全体的にLinq重視のメソッド増強・メンバ増強が成されてて嬉しいところが多い。String.Joinなんかも、
String.Join メソッド (String, IEnumerable(String)) (System) – MSDN ライブラリ
こんな風に増強されてて、今までIEnumerable<T>.Cast()だのIEnumerable<T>.ToArray()だのを頻発していた部分が、よりスマートに書けるようになっている。
String.IsNullOrWhiteSpace() など、基本的なクラスにさりげない増強が施されているので、調査続行の予定。

進化した結果、使い勝手は落ちている部分がいくらかある。
いつだって進化に犠牲はつきもの・・・が、System.IO.Path <-> System.Windows.Shapes.Path だけはどうにかならなかったものか。

その他の新機能覚書

Linq (Linq to object) はとても便利なものです。
今までいちいちforeach (or for, while, do – while) を使って、予備の変数を用意して、云々・・・とやっていた処理が、
(一部とはいえ)一行の式と、メソッド呼び出しで書けてしまう。
実にクール。
ですが、これを使うに当たっては、注意しなくてはならないこともあります。

「効率」

効率はいつだって(結論に差はあれ)考えなくてはならないことで、それを無視してコーディングするようでは、どんなに便利な道具でも時に凶器と化すように、Linq もまた単なる「お荷物」になりかねません。せっかくの便利な道具を、お荷物にしてしまわないためには、その内部をある程度知っておく必要があるでしょう。逆に、Linqを便利で、かつ効率の良いツールとして活用するためにも、それは必須のことと思います。

かといって、私はLinqの中身をILで・・・とか、そんなことは考えるのも億劫なので、「実際に動かして結果を見てみればいいじゃない!」という手抜き工程を踏みます。ゆえに、「一歩」の踏み込みには及ばずながらも、「半歩」程度は踏み込めてるかなあ、と。

さて、サンプルコードです。

using System;
using System.Collections.Generic;
using System.Linq;

namespace Kyoh.Tips.LinqExtensions
{
	static class Program
	{
		static void Main(string[] args)
		{
			var enum1 = enumTest("enum1", 1, 5);
			var enum2 = enumTest("enum2", 3, 7);
			var enum3 = enumTest("enum3", 2, 3);

			Console.WriteLine("Aggregate:");
			enum1.Aggregate((x, y) => x);
			Console.WriteLine();

			Console.WriteLine("All:");
			enum1.All(x => x != 3);
			Console.WriteLine();

			Console.WriteLine("Any:");
			enum1.Any(x => x == 3);
			Console.WriteLine();

			Console.WriteLine("AsEnumerable.run:");
			enum1.AsEnumerable().Run();
			Console.WriteLine();

			Console.WriteLine("AsQueryable.run:");
			enum1.AsQueryable().Run();
			Console.WriteLine();

			Console.WriteLine("Average:");
			enum1.Average();
			Console.WriteLine();

			Console.WriteLine("Concat.run:");
			enum1.Concat(enum2).Run();
			Console.WriteLine();

			Console.WriteLine("Contains(3):");
			enum1.Contains(3);
			Console.WriteLine();

			Console.WriteLine("Count:");
			enum1.Count();
			Console.WriteLine();

			Console.WriteLine("Distinct.run:");
			enum1.Distinct().Run();
			Console.WriteLine();

			Console.WriteLine("ElementAt(3):");
			enum1.ElementAt(3);
			Console.WriteLine();

			Console.WriteLine("ElementAtOrDefault(-1):");
			enum1.ElementAtOrDefault(-1);
			Console.WriteLine();

			Console.WriteLine("Except.run:");
			enum1.Except(enum3).Run();
			Console.WriteLine();

			Console.WriteLine("First:");
			enum1.First(i => i == 3);
			Console.WriteLine();

			Console.WriteLine("Intersect.run:");
			enum1.Intersect(enum2).Run();
			Console.WriteLine();

			Console.WriteLine("Last:");
			enum1.Last(i => i == 3);
			Console.WriteLine();

			Console.WriteLine("LastOrDefault:");
			enum1.LastOrDefault(i => i == 3);
			Console.WriteLine();

			Console.WriteLine("Max:");
			enum1.Max();
			Console.WriteLine();

			Console.WriteLine("OrderBy:");
			enum1.OrderBy(i => i);
			Console.WriteLine();

			Console.WriteLine("Reverse.run:");
			enum1.Reverse().Run();
			Console.WriteLine();

			Console.WriteLine("SequenceEqual:");
			enum1.SequenceEqual(enum3);
			Console.WriteLine();

			Console.WriteLine("Single:");
			enum1.Single(i => i == 3);
			Console.WriteLine();

			Console.WriteLine("SingleOrDefault:");
			enum1.SingleOrDefault(i => i == 3);
			Console.WriteLine();

			Console.WriteLine("Skip(3).run:");
			enum1.Skip(3).Run();
			Console.WriteLine();

			Console.WriteLine("Sum:");
			enum1.Sum();
			Console.WriteLine();

			Console.WriteLine("Take(3).run:");
			enum1.Take(3).Run();
			Console.WriteLine();

			Console.WriteLine("ToArray:");
			enum1.ToArray();
			Console.WriteLine();

			Console.WriteLine("ToDictionary:");
			enum1.ToDictionary(i => i);
			Console.WriteLine();

			Console.WriteLine("ToList:");
			enum1.ToList();
			Console.WriteLine();

			Console.WriteLine("ToLookup:");
			enum1.ToLookup(i => i % 2);
			Console.WriteLine();

			Console.WriteLine("Union.run:");
			enum1.Union(enum2).Run();
			Console.WriteLine();

			Console.WriteLine("Where.run:");
			enum1.Where(i => i != 3).Run();
		}

		public static void Run<T>(this IEnumerable<T> enumerable)
		{
			foreach (var item in enumerable)
				Console.WriteLine("      (running)");
		}
		static void empty() { }

		static IEnumerable<int> enumTest(string key, int min, int max)
		{
			try
			{
				Console.WriteLine("  start(" + key + ")");
				for (int i = min; i <= max; i++)
				{
					Console.WriteLine("    " + i.ToString());
					yield return i;
				}
			}
			finally { Console.WriteLine("  final(" + key + ")"); }
		}
	}
}

結論はここには書きませんが、実際にこれを動かしてみると、「思っていたのと違う!」というのもあるのではないでしょうか。
今後Linqを使う場合に、これがひとつ「効率」の問題に何か投じることができれば、それはとても嬉しいことです。

実に1年ぶりですね。あんまり書くネタないんだよな。調べれば大抵のことは他の人が書いてるし。

二重起動を禁止する、っていうエントリーは、いろんなところで見かけるんだけど、
二重起動を禁止して、尚かつ既に起動しているプロセスに対して何かする、っていうエントリーは、いまいち回答が見つかりにくい。
ならいっそ、共通処理化出来たら意外とニーズはあるんじゃないのか?という企み。

キーワードは、「Ipc」である。ここに書いてあることで理解できないことや、もっと発展的にやってみたい、という方は、「Ipc C#(またはVB)」で検索すれば、結構それなりに引っかかる。

まあ、ともあれ、まずはクラスのソースコードから。
まず肝となる「ExtendetMutex」クラスから。本来の「Mutex」が意味するところとは若干違う役割を持っているけど、適切なクラス名が思いつかなかったので、最初につけた名前でずるずるときてしまっている。ご容赦。

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Serialization.Formatters;
using System.Threading;

namespace ExMutex
{
	using BinaryClient = BinaryClientFormatterSinkProvider;
	using BinaryServer = BinaryServerFormatterSinkProvider;

	/// <summary>同プロセスが呼び出されたことを通知するデリゲート</summary>
	/// <typeparam name="TMessage">通知するメッセージの型</typeparam>
	/// <param name="holder">通知するメッセージ</param>
	public delegate void CadetStartedHandler<TMessage>(IMessageHolder<TMessage> holder);

	/// <summary>プロセス間通信により、起動オプションなどのメッセージのやりとりをサポートするMutex</summary>
	/// <typeparam name="TMessage">プロセス間でやりとりするメッセージの型</typeparam>
	public sealed class ExtendedMutex<TMessage> : IMessageHolder<TMessage>, IDisposable
	{
		/// <summary>IPCで利用する共通メッセージクラス</summary>
		private sealed class MutexMessage : MarshalByRefObject, IMessageHolder<TMessage>
		{
			#region ISyncHolder<T> メンバ

			private bool mIsCadet = false;

			public bool IsCadet
			{
				get
				{
					try { return mIsCadet; }
					finally { mIsCadet = true; }
				}
			}

			public TMessage Message { get; set; }

			public event CadetStartedHandler<TMessage> CadetStarted;

			#endregion

			public void RaiseCadetStarted(TMessage e)
			{
				Message = e;
				if (CadetStarted != null)
					CadetStarted(this);
			}
		}

		private IChannel mChannel;
		private MutexMessage mMessage;
		private ExtendedMutex(IChannel channel, MutexMessage message)
		{
			this.mChannel = channel;
			this.mMessage = message;
			this.IsCadet = message.IsCadet;
		}

		#region ISyncHolder<T> メンバ

		/// <summary>起動したプロセスが、すでに他に存在するかどうかを取得します。</summary>
		public bool IsCadet { get; private set; }

		/// <summary>プロセス間通信オブジェクト。</summary>
		public TMessage Message { get { return mMessage.Message; } }

		/// <summary>他のプロセスが後から起動した場合に発生するイベント。</summary>
		public event CadetStartedHandler<TMessage> CadetStarted
		{
			add
			{
				if (mMessage != null)
					mMessage.CadetStarted += value;
			}
			remove
			{
				if (mMessage != null)
					mMessage.CadetStarted -= value;
			}
		}

		#endregion

		/// <summary>プロセス開始通知</summary>
		/// <param name="name">開始プロセスのユニークな名称文字列</param>
		/// <param name="eventArg">先に開始していた同名のプロセスがあった場合、当該プロセスに送信するメッセージ</param>
		public static ExtendedMutex<TMessage> Start(string name, TMessage eventArg)
		{
			// 開始プロセス自体をMutexでプロセス間同期
			using (var mutex = new Mutex(true, typeof(MutexMessage).FullName + "[" + name + "]"))
			{
				bool isServer = false;
				mutex.WaitOne();
				try
				{
					#region サーバ

					// IPCチャネルの作成と登録
					var channel = new IpcServerChannel(name, "remote", new BinaryServer() { TypeFilterLevel = TypeFilterLevel.Full, });
					ChannelServices.RegisterChannel(channel, true);

					// サーバとして起動できる
					isServer = true;

					RemotingConfiguration.RegisterWellKnownServiceType(typeof(MutexMessage), name, WellKnownObjectMode.Singleton);

					// メッセージキャプチャを開始
					var message = new MutexMessage() { Message = eventArg, };
					RemotingServices.Marshal(message, name, typeof(MutexMessage));
					return new ExtendedMutex<TMessage>(channel, message);

					#endregion
				}
				catch (RemotingException)
				{
					// サーバの起動に失敗
					if (isServer)
						throw;

					#region クライアント

					var channel = new IpcClientChannel(name, new BinaryClient());
					ChannelServices.RegisterChannel(channel, true);

					RemotingConfiguration.RegisterActivatedClientType(typeof(MutexMessage), "ipc://remote/" + name);

					// メッセージを発行する
					MutexMessage message = (MutexMessage)Activator.GetObject(typeof(MutexMessage), "ipc://remote/" + name);
					message.RaiseCadetStarted(eventArg);
					return new ExtendedMutex<TMessage>(channel, message);

					#endregion
				}
			}
		}

		#region IDisposable メンバ

		public void Dispose()
		{
			try { ChannelServices.UnregisterChannel(mChannel); }
			catch { }
		}

		#endregion
	}
}

中で利用している「IMessageHolder」クラス

namespace ExMutex
{
	/// <summary><seealso cref="Kyoh.ExtendedMutex"/>で用いる、プロセス間メッセージのラッパー。</summary>
	/// <typeparam name="T">メッセージに用いるオブジェクトの型</typeparam>
	public interface IMessageHolder<T>
	{
		bool IsCadet { get; }
		T Message { get; }
		event CadetStartedHandler<T> CadetStarted;
	}
}

と、まあ。
「T」やら「TMessage」っていう型アーギュメントがあるけれど、これはプロセス間でやりとりしたいオブジェクトの型を指定する。
型は特に制約を持たない。基本的にはclassであることが望ましい(相互にやりとりできるし)けれど、一方通行のナニであればstructでも問題は無いと思う。
また、単に通知したいだけなら、メッセージを飛ばす必要もないので、T(TMessage)にobjectを指定して、渡すメッセージはnull、でも何ら問題ない。

次に、どういう風に使うか、の例。
GUIの場合とCUIの場合両方で書いてみた。

GUIの方は単に、先に走っていたプロセスがあった場合にはそのプロセスの画面(MainForm)をアクティブにする(実際にはタスクバーで点滅が起きる)という処理。ただし、CUIとして動かすために全面的にコメントアウトされているので注意。実際にはちゃんとMainFormとして画面を作らなくては動かない。

CUIの方では、メッセージとして

Environment.CommandLine

を渡している。つまり、呼び出しコマンドをそのまま文字列として渡している。

using System;

namespace ExMutex
{
	public class Program
	{
		/*
		static MainForm mainForm;

		[STAThread]
		static void Main()
		{
			using(var mutex = ExtendedMutex<object>.Start("testProcessName", null))
			{
				if (!mutex.IsCadet)
				{
					Application.EnableVisualStyles();
					Application.SetCompatibleTextRenderingDefault(false);
					Program.mainForm = new MainForm();
						mutex.CadetStarted += new CadetStartedHandler<object>(CadetStarted);
					Application.Run(Program.mainForm);
				}
			}
		}

		static void CadetStarted(IMessageHolder<object> holder)
		{
			try { Program.mainForm.Activate(); }
			catch { }
		}
		/*/
		static void Main(string[] args)
		{
			// メッセージを文字列型で受け取るMutexに
			// コマンドラインをそのまま渡す
			using (var mutex = ExtendedMutex<string>.Start("testProcessName", Environment.CommandLine))
			{
				if (mutex.IsCadet)
					return;

				// 後から起動されたプロセスを処理
				mutex.CadetStarted += new CadetStartedHandler<string>(CadetStarted);

				// ポーリング
				string input;
				do { input = Console.ReadLine().Trim(); } while (string.IsNullOrEmpty(input));
			}
		}

		/// <summary>後から起動されたプロセスのメッセージを表示する</summary>
		/// <param name="holder">表示するメッセージ</param>
		static void CadetStarted(IMessageHolder<string> holder)
		{
			Console.WriteLine(holder.Message);
		}
		//*/
	}
}

詳しい話はそのうち気が向いたら(=十中八九気は向かない)書くけど、
要するに「先に起動した方がサーバー」「あとに起動した方はクライアント」として動作する、クライアント・サーバーモデルなあん畜生で通信してるんですよ。

もしかしたら、うまくいかないケースもあるかもしれない。

Win32API周りの実装を、ひとまとめにしたクラス。ちょくちょく追加・修正する予定です。 続きを読む »

C#/.NET Frameworkにおける、二重起動の防止については、様々なサイトで解説が成されています。パッと探してみたところでもこれだけの解説サイトが見つかりました。

他にも、”C# 二重起動“でGoogleにて検索を行えば、数多くの解説ページを見つけることができます。

しかし、これらの解説で行えるのは、ソフトウェアの二重起動の抑制のみで、それ以上の動作が欲しい場合には一工夫が必要となります。
今回私が直面した要求は、次の2点でした。

  • 二重起動時に、既存のプロセスのメインウインドウをアクティブにする。
  • 二重起動時に渡されたコマンドライン引数を、既存のプロセスのメインウインドウに渡す。

これらについても、それぞれ様々なサイトで解説が行われています。
しかしながら、両者の要求を同時に満たせる解説は見あたらず、また何だかんだと問題があったりしたため、自分で作ってしまうことにしました
※とはいえ、多くの解説サイトを参考にさせていただいています。 続きを読む »