2008年1月29日火曜日

XMLファイルを読み込む(C#)

プログラムを開発する上で、設定ファイルを作成することは非常に重要なことです。

ソースコードに直接埋め込む場合と違って、設定ファイルからパラメータを
読み込むことで、実行環境の違いを吸収したりできます。
(逆に言えば、設定ファイルを変更することで、プログラムが動かなくなったり
 します。)

Windowsでは古くから設定ファイルとして"INIファイル"が用いられてきました。
拡張子が".ini"となってるファイルです。

ところが、ネットワークの発展と共に、XMLが普及しだすと、
設定ファイルとしてもXMLファイルが広く用いられるようになりました。

そこで、今回はXMLファイルを読み込む方法を紹介します。


"コンソール アプリケーション"プロジェクトを作成します。
今回は"XMLParse"とします。

メニューの"プロジェクト"から"新しい項目の追加"を選択します。

"XML ファイル"を選択して、適当にファイル名をつけます。
例では、"server.xml"としておきます。

/*** server.xml ***/
<?xml version="1.0" encoding="utf-8" ?>
<server>
<address>
192.168.10.1
</address>
<port>
80
</port>
</server>

ネットワーク上に配置されたサーバの設定を保持していると想定して下さい。

次に、XMLファイルを解読するクラスを作成します。

先ほどと同様の手順で、クラスファイルを選択して下さい。
名前は"XmlParser.cs"としておきます。

"XmlParser"クラスは、最初にXMLファイルを読み込み、
その値を保持するユーティリティクラスとして作成したいと思います。

/*** XmlParser ***/
class XmlParser
{
private static IPAddress ip;
private static int port;

public static IPAddress Ip
{
get { return ip; }
}

public static int Port
{
get { return port; }
}
}

このクラスには静的フィールド変数として、IPアドレスを表す"ip"と、
ポート番号を表す"port"があります。

さらに、それらの値を返す静的プロパティが2つあります。

ユーティリティクラスのため、全てのメンバが静的です。

このクラスにXMLファイルを解析する処理を持たせるわけですが、
読み込むためのメソッドを作成して、それを呼び出せば簡単に済みます。

しかし、今回のようにファイルを一度だけ読み込む場合、
どこで一番最初にXMLファイルを解析させるかを考える必要があります。

もし、その順番が間違ってしまえば、不正な値を持つかもしれません。

そこで、今回のような場合は"静的コンストラクタ"を用います。

静的コンストラクタ
静的コンストラクタとは、クラスを初期化したい場合に利用します。
静的コンストラクタは以下の場合に呼び出されます。
  • 最初のインスタンスが生成される前

  • 静的メンバが参照される前
静的コンストラクタを用いれば、静的プロパティにアクセスするだけで、
何らかの処理を行わせることができます。


今回のケースにはぴったりですね。

静的コンストラクタは通常のコンストラクタと違い、
修飾子として"static"のみを付与します。

なお、"System.Xml"を"using"を持ちいて省略しておいて下さい。

/*** XmlParser.cs ***/
static XmlParser()
{
// 空白を無視するように設定する
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreWhitespace = true;

// リーダー
XmlReader reader = XmlReader.Create("../../server.xml", settings);

while (reader.Read())
{
// ノードの種類が要素かどうかを判定
if(reader.NodeType == XmlNodeType.Element)
{
if(reader.LocalName.Equals("address"))
{
Console.WriteLine(reader.ReadString() + "を読み込みました。");
// TryParseを利用してIPアドレスを読み込む
bool flag = IPAddress.TryParse(reader.ReadString(), out ip);
if (!flag)
ip = IPAddress.None;
}
else if (reader.LocalName.Equals("port"))
{
Console.WriteLine(reader.ReadString() + "を読み込みました。");
// TryParseを利用してポート番号を読み込む
bool flag = int.TryParse(reader.ReadString(), out port);
if (!flag)
port = int.MaxValue;
}
}
}
reader.Close();
}

"reader.LocalName"にはタグの要素名が入るため、
文字列比較を行うことで判定を行うことができます。


また、"TryParase"メソッドは、第一引数に与えられた文字列を、
指定したクラスに合うよう変換しつつ、それが正しいかを判定してくれます。
変換後の値は、第二引数で与えられたフィールドに格納されます。

out
"out"キーワードを用いると、"ref"と同様、参照渡しを行うことができます。

"ref"との違いは、"out"に渡すフィールドを明示的に初期化する必要が無い点です。
ただし、そのメソッド内で必ず"out"で参照されたフィールドに対して、
何らかの値を代入してやる必要があります。

"ref"が参照するために参照渡しを実現するのに対し、
"out"は出力するために参照渡しを実現しています。


"XmlReader"を作成する箇所で、XMLファイルへは相対パスで指定しています。

プロジェクト内の"server.xml"ですが、指定には"../"を2つ必要です。

これはプロジェクトの先頭を表すフォルダと、
デバッグ / ビルドを行う実行ファイルを持つフォルダが違うためです。

実際、プロジェクトが収まっているフォルダを開いてみて下さい。
(プロジェクトフォルダが無い場合は、メニューの"ファイル"から
 "全てを保存"を選択して下さい。)

"マイ ドキュメント" > "Visual Stuidio 2008" > "Project" > "XMLParse" >
"XMLParse"と選んだ先がプロジェクトのトップ階層です。

さらに、"bin" > "Debug"と選んだ先に、デバッグモードでの実行ファイルがあります。

ここが基点として、相対パスは認識されます。

よって、その2階層上にある"sever.xml"を指定する場合には、
"../../server.xml"と記述する必要があります。


出力用の処理を"Main"メソッド内に記述します。

/*** Program.cs ***/
static void Main(string[] args)
{
Console.WriteLine("ip = " + XmlParser.Ip + "を取得しました。");
Console.WriteLine("port = " + XmlParser.Port + "を取得しました。");
System.Threading.Thread.Sleep(5000);
}

実行します。















あれ?IPアドレスやポートがエラー値を示している。

と言うよりは、なぜか正しく設定してあるはずなのに、空白も読み込んでる?

・・・なぜ?


"XmlReader"の方で対処できないのであれば、読み込んだ後に対処しましょう。

全ての"reader.ReadString()"を以下のようにして下さい。

/*** XmlParser.cs ***/
string tmp = reader.ReadString().Trim();
reader.ReadString -> tmp
(宣言せずにメソッドに直接メソッドの結果を渡すと
 うまくいかない場合があります。)

"Trim"メソッドは、指定の文字列の先頭、及び末尾にある全ての空白を
削除してくれる素晴らしいメソッドです。

また、"TrimStart"、"TrimEnd"を用いれば、先頭、末尾のどちらかを
指定して削除することもできます。


再び、実行します。















今度は正しく動きました。
(原因が未だにわからないんですが、良しとして下さい^^;)


XMLは、今後インターネットの世界だけではなく、
様々なアプリケーションに使われていきます。

それも、今回のような設定ファイルだけではなく、
互換性の向上のために、メタ情報を記述した保存用ファイルや、
データ転送のための共通規格として利用されます。

XMLを用いることでプログラムに柔軟性と拡張性を持たせることができます。


参照:
out (C#) : msdn

2008年1月28日月曜日

IPアドレスやMACアドレスを取得する(C#) その2

前回の記事ではWMIインスタンスである"ManagementObject"を
利用しました。

しかし、クエリの発行やインデクサに対して名前を指定するなど、
全てのデバイス情報にアクセスできる分、プログラムが容易ではありませんでした。

そこで、今回はネットワークのみを扱うクラスを用いたいと思います。

と言っても、難しいものではないので早速ソースを見てみましょう。
(プロジェクトは前回同様"コンソール アプリケーション"で結構です。)


まず、名前空間の修飾省略定義を記述します。

ソースコード中に多く出てくる名前空間はここで省略して書いてしまいます。

/*** Program.cs ***/
using System.Net;
using System.Net.NetworkInformation;

名前空間の省略は、記述した空間内の、クラスにのみ適用されます。

つまり、"System.Net"名前空間の省略を宣言しても、"NetworkInformation"
名前空間の前が省略されるようになるわけではありません。

"using System.Net"のみの宣言を行った場合は、
例えば"System.Net.NetworkInformation.GetIsNetworkAvailable()"
のように、頭から全て記述しなければなりません。

/*** Program.cs ***/
static void Main(string[] args)
{
// ネットワークが利用できない場合は終了する
if (!NetworkInterface.GetIsNetworkAvailable())
return;

// 各ネットワークインタフェース毎に処理を行っていく
NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();
foreach (NetworkInterface adapter in adapters)
{
if (adapter.OperationalStatus.Equals(OperationalStatus.Up))
{
IPInterfaceProperties properties = adapter.GetIPProperties();
foreach (IPAddressInformation ipInfo in properties.UnicastAddresses)
{
IPAddress ip = ipInfo.Address;
if (!IPAddress.IsLoopback(ip))
{
Console.WriteLine("IP = " + ip);
Console.WriteLine("MAC = " + adapter.GetPhysicalAddress());
}
}
}
}
System.Threading.Thread.Sleep(5000);
}

実行します。















ネットワークの状態は前回と同じです。

今回は、"OpenVPN"ネットワークに関する情報が表示されていません。

これは、ソース中の"adapter.OperationalStatus"で
"OperationalStatus.Up"と「ネットワークインタフェースが
正常に稼働している」ものに限定したためです。

この方が、より正確に現在利用しているネットワークインタフェースの情報を
取得できます。

2008年1月25日金曜日

IPアドレスやMACアドレスを取得する(C#)

ネットワークを介したシステムを作る上で、マシンのIPアドレスやMACアドレスは
重要になってくる。

C#ではIPアドレスをはじめとして、様々なデバイス情報を取得することができます。

今回は、この取得をWindows Management Instrumentation (WMI)を
用いて行います。

WMIはWindows管理技術の中核を担っています。
また、ローカルのマシンだけでなく、リモートのマシンも管理することが可能です。

C#には、このWMIを表すクラス"ManagementObject"があります。

実際に使ってみてから説明していきます。


"コンソール アプリケーション"プロジェクトを作成して下さい。

"ManagementObject"は"System.ManagementObject"ライブラリ下に
あります。

メニューの[プロジェクト] → [.NET]タブ → "System.Management"を
クリックして、[OK]ボタンを押して下さい。

"using"ディレクティブを用いて、名前空間の修飾省略定義を行います。

/*** Program.cs ***/
using System.Management;
IPアドレスとMACアドレスを標準出力するソースを記述します。

/*** Program.cs ***/
static void Main(string[] args)
{
string query = "SELECT * FROM Win32_NetworkAdapterConfiguration";
ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);

ManagementObjectCollection queryCollection = searcher.Get();

foreach (ManagementObject mo in queryCollection)
{
if ((bool)mo["IPEnabled"])
{
Console.WriteLine(mo["Caption"]);
foreach (string ip in (string[])mo["IPAddress"])
{
Console.WriteLine("IP = " + ip);
}
Console.WriteLine("MAC = " + mo["MacAddress"].ToString() + "\n");
}
}

System.Threading.Thread.Sleep(5000);
}

実行します。















実際に"ネットワーク接続"画面を開いて確認してみます。














接続状態が"無効"もしくは"切断"の接続は、コンソールに表示されていません。

これは、"IPEnabled"がfalseになっているためです。

また、「ネットワークケーブルが接続されていません」と表示されている接続は、
IPアドレスが"0.0.0.0"と表示されています。

ちなみに、このネットワークは"OpenVPN"を使った仮想ネットワークです。

他のネットワークアダプタの状態や振る舞いを取得する場合は、
msdn : Win32_NetworkAdapterConfiguration Class」を
参照して下さい。


このようにWMIを用いることで、マシンの情報を容易に取得することができます。


参照:
Windows Management Instrumentation の秘密 : Microsoft TechNet
ManagementObject クラス (System.Management) : msdn
how do i get the MAC address in c# ? : C# Friends

画面をキャプチャする(C#) その2

前回の最後で、
「何らかの方法でアクティブなウィンドウの左上隅の座標とウィンドウの大きさを
取得すれば、"CopyFromScreen"メソッドが利用できる。」
と話しましたが、もちろんできます。


前回のソースをそのまま改変していきます。
(新たにプロジェクトを作成する場合は、"button1"をダブルクリックする所まで
 進めて下さい。)

まずは、長方形を表す構造体を宣言します。
("using"ディレクティブを用いて、"System.Runtime.InteropServices"名前
 空間の修飾省略定義を行って下さい。)

/*** Form1.cs ***/
public partial class Form1 : Form
{
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
(省略)
}

構造体"RECT"は長方形の左上隅と右下隅とで、長方形の形を定義します。

左上隅に (x, y) = (RECT.left, RECT.top)
右上隅に (x, y) = (RECT.right, RECT.bottom)
が入ります。

続いて、「アクティブなウィンドウを取得する」メソッドと、
「ウィンドウの長方形座標を取得する」メソッドを"user32.dll"より呼び出します。

// 現在ユーザーが作業しているウィンドのハンドルを返します。
[DllImport("user32")]
private static extern IntPtr GetForegroundWindow();

// 指定されたウィンドウ左上端と右下端の座標をスクリーン座標で取得します。
[DllImport("user32")]
private static extern int GetWindowRect(IntPtr hWnd, ref RECT lpRect);

"GetWindowRect"の第一引数には、もちろん座標を取得したいウィンドウのハンドルが入ります。

第二引数に渡した変数に対して、取得された座標が代入されます。

このとき、"ref"がついてることがポイントです。

値渡しと参照渡し
引数の渡し方は2種類あります。1つは"値渡し"、もう1つは"参照渡し"です。

値渡しはメソッドを呼び出すときに、値のコピーを渡す方式です。
メソッド内で、引数の値に対して操作を行った場合でも、元の値には影響しません

参照渡しはメソッドを呼び出すときに、値の参照を渡す方式です。
メソッド内で、引数の値に対して操作を行うと、元の値にも影響します

C#ではメソッドの引数は基本的には値渡しです。
参照渡しにする時は、開発者が"ref"を用いて明示的に示さなければなりません。
この"ref"は引数の宣言の時にも、渡す時にも必要になります。


実装は以下の通りです。

/*** Form1.cs ***/
private void button1_Click(object sender, EventArgs e)
{
// アクティブなウィンドウのデバイスコンテキストを取得
IntPtr hWnd = GetForegroundWindow();

// ウィンドウの大きさを取得
RECT wRect = new RECT();
GetWindowRect(hWnd, ref wRect);

int width = wRect.right - wRect.left;
int height = wRect.bottom - wRect.top;

Bitmap bmp = new Bitmap(width, height);
using (Graphics g = Graphics.FromImage(bmp))
{
g.CopyFromScreen(new Point(wRect.left, wRect.top), new Point(0, 0), bmp.Size);
}

this.pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
this.pictureBox1.Image = bmp;
}

実行します。




















参照:
Capture a Screen Shot - Developer Fusion

2008年1月24日木曜日

画面をキャプチャする(C#)

デスクトップ画面をキャプチャする。

キーボードなら[PrtSc]キーを押せば画面全体はキャプチャできる。
[Alt]キーと一緒に押せば、アクティブなウィンドウのみをキャプチャできる。

もっと細かくキャプチャしたいとなると・・・プログラムするしかない。
(フリーソフトを使えば?ってツッコミは無しで^^;)

.NET Frameworkには画面のキャプチャを容易にしてくれる、
"CopyFromScreen"というメソッドがある。


早速、使ってみる。

まずは、新規で"Windows フォーム アプリケーション"プロジェクトを作成する。

表示されたフォームに、"Button"と"PictureBox"を追加する。
この時、"pictureBox1"の"Size"を「400, 400」、"BorderStyle"を"FixedSingle"にしておく。












"button1"をダブルクリックして、メソッドを作成。

/*** Form1.cs ***/
private void button1_Click(object sender, EventArgs e)
{
Bitmap bmp = new Bitmap(200, 200);
using (Graphics g = Graphics.FromImage(bmp))
{
g.CopyFromScreen(new Point(100, 100), new Point(0, 0), bmp.Size);
}

this.pictureBox1.SizeMode = PictureBoxSizeMode.Normal;
this.pictureBox1.Image = bmp;
}

実行する。

"button1"をクリックすると、画面の左上の方がキャプチャされ、
"pictureBox1"に表示される。
















"pictureBox1"の左上1/4の所に表示された。

CopyFromScreen
通常、第一引数には、コピーの画像の左上隅の座標を与える。
第二引数には、コピーの画像の左上隅の座標を与える。
第三引数には、コピーするサイズを与える。
第一、二引数に与えた座標を開始点として、第三引数に与えたサイズだけコピー
される。


既にお分かりの人も多いと思うが、ソース内の

this.pictureBox1.SizeMode = PictureBoxSizeMode.Normal;
で、"pictureBox1"内での表示方法を設定している。

表示方法は全部で5つある。
AutoSize
PictureBoxのサイズが、格納しているイメージと同じ大きさになる。

CenterImage
PictureBoxがイメージより大きい場合は、イメージは中央に表示される。
イメージがPictureBoxより大きい場合は、イメージはPictureBoxの中央に
配置され、外にはみ出した分はクリップされる。

Normal
PictureBoxの左上隅に配置される。イメージがPictureBoxより大きい場合は、イメージはクリップされる。

StretchImage
イメージのサイズが、PictureBoxのサイズに合うように調整される。

Zoom
イメージのサイズは、サイズ比率を維持したままで拡大または縮小される。
と、言うわけで、全部試してみる。

AutoSize


















CenterImage


















StretchImage


















Zoom


















残念ながら、上の画像からでは"StretchImage"と"Zoom"の違いがわからない
が、"pictureBox1"の"Size"を「300, 400」などにしてみるとわかると思う。
(作った後に気づいてしまった、申し訳ないです。)


C#を使って、簡単に画面をキャプチャすることができた。

この方法だと、座標指定を指定するため、ほとんどの場合に利用できそうだ。

例えば、前述の「アクティブなウィンドウのみをキャプチャする」の場合、
何らかの方法でアクティブなウィンドウの左上隅の座標とウィンドウの大きさを
取得すれば、"CopyFromScreen"メソッドが利用できる。


参照:
画面をキャプチャする: .NET Tips (VB.NET, C#, Visual Studio...)

2008年1月20日日曜日

FONが我が家にやってきた

皆さんは"FON"をご存知でしょうか?

FONはWiFiを世界中に無料で広げようとするコミュニティです。

ユーザは自分の無線LANアクセスポイントを他のFONユーザと共有し、
代わりに他のFONアクセスポイントを無料で利用できる、というものです。

ちなみに、このFONユーザのことを"Fonero(フォネロ)"と呼びます。

Foneroには3種類あります。
Linus
自分の無線LANルータをAP(アクセスポイント)として無償で公開する代わりに、他のFoneroのAPも無償で利用することができる。
Bill
自分のAPにアクセスがあった場合に50%の使用料を得られるが、他のFoneroのAPを利用するには使用料を支払う必要がある。
Alien
APの公開は不要であるが、他のFoneroのAPを利用するには使用料が必要である。
ちなみに、"Linus"はLinuxの開発者リーナス・トーバルズから、
"Bill"はマイクロソフトの創始者ビル・ゲイツから来てるそうです。

無償利用が"Linus"で、有償利用が"Bill"ってあたりに、
何かのメッセージを感じるのは私だけでしょうか?

FONに参加するには、公式サイトより会員登録を行い、
FONが提供する無線LANルータ"LA FONERA"を購入・設置する必要があります。

"LA FONERA"にはプライベート用と公開用の2つのネットワークを持っているため、
自分のAPを安全に利用することができます。


なぜか、これまで我が家では有線ルータを使用していました。

しかし、最近になってノートパソコンを使う機会が増えたので、
無線LANルータを購入しようと思いました。

そこで、せっかくだから、FONに参加することにしました。


無線LANルータ"LA FONERA+"がこちら。



































なぜかステッカーが大小2つ入ってた。


説明書の通りに設置し、ノートPCからプライベート用ネットワーク"MyPlace"を探す。

接続して、ブラウザを開くと"http://192.168.10.1"に自動的にアクセスされる。

"LA FONERA+"の設定ページが開いたので、FONの登録ページへアクセスする。

・・・。

繋がらない。

よく見たら"No network access"と表示されている。













よく見たら、ノートPCの"ワイヤレスネットワークの選択"にも、
公開用ネットワーク"FON_AP"が見えない。


とりあえず、ルータを元の有線ルータに戻し、FONサイトのヘルプを見てみる。

どうやら、"PPPoE"や"PPTP"の場合は、設定を変えなければいけないらしい。

日本の多くのISPはPPPoEを採用していることを考えれば、
そのように説明を書いておいてくれてもよかったんじゃないかな?


さっきの"LA FONERA+"の設定ページをもう一度開いてみる。

"Advanced" > "Internet Connection Settings"を選択。

"Mode"を"PPPoE"に切り替え、ISPの契約通りユーザ名とパスワードを入力する。













もう一度、ネットワーク接続を開いてみる。















今度はちゃんと"FON_AP"も見えました。

再度、FONへ登録を行う。今度はちゃんとアクセスできた。

無事登録を完了し、晴れてFONメンバの一員となることができました。


もし、私のAPを発見した人は、ぜひ利用してみて下さい。


参照:
FON
Wikipedia : FON

2008年1月18日金曜日

C#でSingletonパターン

「結婚したら、2人の収入は1つの財布(家計)へ」

共働きの家庭ではよくある話ですが、今回はそんな話。(どんなだ?)


あるクラスのインスタンスが1つしか存在しないことが重要になることがあります。

上記の例だと、財布が1つしか無いことは重要です。

"へそくり"があったら大変ですよね(笑)

「財布は1つであって欲しい("財布"というクラスのインスタンスは1つであって欲しい)」

このように、インスタンスが唯一の存在であることを保証するために、
"Singleton"パターンが有効となります。

"Singleton"パターンを実現するには
  1. インスタンスが1つしか存在しない

  2. 別のクラスから直接インスタンスを生成することが不可

  3. インスタンスへは公開されたポイントからのみアクセス可能
を満たさなければなりません。

実際に"Singleton"パターンを用いてみます。
(コンソールアプリケーションプロジェクトを作成して下さい。)

クラスを1つ追加します。名前は例にならって"Wallet.cs"にします。

/*** Wallet.cs ***/
// 1. 唯一のインスタンスを格納する変数
private static Wallet instance = null;

// 2. 直接インスタンス生成できないように、コンストラクタを不可視に
private Wallet() { }

// 3. インスタンスへのアクセスが可能なアクセスポイント
public static Wallet GetInstance()
{
if (instance == null)
instance = new Wallet();
return instance;
}

ソースコードを見ればわかる通り、他のクラスからは③へのみ、
アクセスすることが許されています。

そして"GetInstance"メソッドでは、変数"instance"が初期値の"null"である場合、
コンストラクタを呼び出すことでインスタンスを生成しています。

コンストラクタは"private"修飾子により、他のクラスからは呼び出せません。

これにより、インスタンスが1つしか生成されないことを保証しています。

全てのクラスは、"GetInstance"メソッドを通じて共通の"instance"変数へ
アクセスすることになります。

これが"Singleton"パターンです。


実際に、"instance"変数へアクセスする様子を見てみましょう。

/*** Wallet.cs ***/
// 財布に入ってるお金
private int money = 0;

// 財布にお金を入れる
public void Income(int value)
{
this.money += value;
}

// 財布の中を見る
public int Look()
{
return money;
}

/*** Program.cs ***/
static void Main(string[] args)
{
new Thread(new ThreadStart(Man)).Start();

new Thread(new ThreadStart(Woman)).Start();
}

private static void Man()
{
Wallet.GetInstance().Income(5000);

Console.WriteLine("僕たちの財布には" + Wallet.GetInstance().Look() + "円入ってるよ。");

Thread.Sleep(5000);
}

private static void Woman()
{
Wallet.GetInstance().Income(2000);

Console.WriteLine("私たちの財布には" + Wallet.GetInstance().Look() + "円入ってるわ。");

Thread.Sleep(5000);
}

実行します。




出力のための遅延や、"Income"メソッドと"Look"メソッドのタイミングによって、
表示している画像の結果と多少違うかも知れませんが、
同じインスタンスへアクセスしていることがわかると思います。

このように、"Singleton"パターンを用いることで、
インスタンスが唯一であることを保証することができました。

モジュール結合度と共通結合
モジュールを分割する基準の1つとして、モジュールの独立性が用いられます。
独立性が高いほど、他のモジュールの影響を受けにくく、拡張性などが高くなります。

モジュールの独立性を評価する指標の1つに、モジュール結合度があります。
モジュール結合度が高いほど、そのモジュール同士の独立性は低くなります。
逆に、結合度が低いほど、独立性は高くなります。

例で挙げたメソッド"Man"と"Woman"は、唯一の"instance"インスタンスが持つ
共通の"money"を参照しています。
このように、共通領域のデータを参照するモジュール同士の場合、
その結合方法は"共通結合"と呼ばれます。
共通結合は、モジュール結合度は2番目に高く、独立性は低めです。
例では、互いに"Income"メソッドを呼び出すことで、共通のデータ"money"へ
影響を与えています。

"Singleton"パターンは共有結合の代表的な実装方法です。



今後も、有用なデザインパターンは随時、記事にしていきたいと思います。

C#でのユーザー定義Exception(例外)の作り方

「プログラムにエラーはつきもの」

と言うのは、言い過ぎかも知れませんが、
エラーが起こる可能性を0(ゼロ)にすることはなかなか難しい。

エラーが発生した場合、そのエラーがなぜ発生したのかがわかると、
対処がしやすいものです。

そのため、何のエラーかを識別するために、.NET Frameworkには多くのExceptionが存在します。

しかし、これらのExceptionだけでは足らず(適切なものがなく)、
自ら定義をして例外の種類を追加したいことがあります。

このような場合、ユーザー自身がExceptionを定義することで、
より細かくエラーを識別する手助けをすることができます。


今回は例外を試すだけなので、コンソールアプリケーションで行いましょう。
("ファイル" > "新しいプロジェクト" > "コンソール アプリケーション")

例外のためのクラスを追加します。

例外を作成する場合は、クラスの最後に"Exception"と付けると、
コーディング時に便利です。

クラス名は"OriginalException"としておきましょう。

/*** OriginalException.cs ***/
public class OriginalException : Exception
{
public OriginalException() { }

public OriginalException(string message) : base(message) { }

public OriginalException(string message, Exception inner) : base(message) { }
}

このように、例外を作成する場合は必ず"Exception"クラスを継承しなくてはなりません。

また、推奨される3つのコンストラクタは実装しておくと便利です。

2つ目と3つ目のコンストラクタには、コンストラクタ宣言の後に

: base(...)
が付いています。

これは継承元クラス(baseクラス,この場合"Exception"クラス)のコンストラクタを
呼び出すことを意味しています。
通常、クラスに継承関係がある場合、継承元のコンストラクタが実行された後、
継承先のコンストラクタが実行されます。
しかし、その場合、コンストラクタの引数は継承先のコンストラクタに渡され、
継承元のコンストラクタには渡されません。
そこで、": base"を用いることで、継承元のコンストラクタへ引数を渡すことができます。

実際に作成した"OriginalException"を試してみます。

/*** Program.cs ***/
private static void ThrowTest()
{
Console.WriteLine("Enter ThrowTest method.");
throw new OriginalException("This is an OriginalException.");
Console.WriteLine("Exit ThrowTest method.");
}

[STAThread]
public static void Main()
{
ThrowTest();
}

実行しようとすると、以下のように警告されます。

到達できないコードが検出されました。

これは、例外を投げると、それ以降のコードが処理されず、
"ThrowTest"メソッドの3行目が実行されないことを意味しています。

警告を無視して実行します。


コンソールに「Enter ThrowTest method.」と表示された後、


とエラーが表示されました。

次に、Mainメソッドを変更して、例外処理を行います。

/*** Program.cs ***/
public static void Main()
{
try
{
ThrowTest();
}
catch (OriginalException e)
{
Console.WriteLine("Exception occured : " + e.Message);
}
finally
{
Thread.Sleep(5000);
}
}

実行します。
("using System.Threading;"を追加して、名前空間の修飾省略定義を
 行って下さい。)


正しく例外をキャッチすることができました。


.Net Frameworkでもユーザー独自の例外を作成することができます。

ユーザー独自の例外を用いることで、よい効率的なコーディングができるように
なります。


参照:
MSDN : 方法:ユーザー定義の例外を作成する

2008年1月16日水曜日

マウスの位置を取得し続ける(C#) その4

前回前々回の記事を見ていない人は先にそちらを。


前回までで、ローカルフックを用いてマウスの位置を取得し続けることはできました。

しかし、ローカルフックでは、そのプログラムが動いているスレッド上でしかマウスの
位置が取得できません。
(前回の場合、スレッド上で動いているのは"Form1"のみなので、
 "Form1"上の位置は取得できます。)

そこで、前回のソースを少し改変して、グローバルフックで取得できるようにします。

グローバル(Low-Level)フックを用いて、マウスメッセージを取得するためには、
フックタイプとして以下の値が必要になります。

/*** Form1.cs ***/
private const int WH_MOUSE_LL = 14;
"GetModuleHandle"メソッドをDLLインポートします。

/*** HookMethods.cs ***/
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(String lpModuleName);

"SetMouseHook"メソッドを以下のように変更します。

/*** Form1.cs ***/
private void SetMouseHook(HookMethods.HookProcedureDelegate proc)
{
using(Process process = Process.GetCurrentProcess())
using (ProcessModule module = process.MainModule)
{
hHook = HookMethods.SetWindowsHookEx(WH_MOUSE_LL, proc, HookMethods.GetModuleHandle(module.ModuleName), 0);
}

if (hHook == IntPtr.Zero)
{
MessageBox.Show("SetWindowsHookEx Failed.");
}
}

実行します。



"Form1"以外でも、マウスの位置を取得できました。

上記のソースコードについて解説します。

"GetModuleHandle"メソッドは、呼び出し側プロセスのアドレス空間に
マップされているモジュールのハンドルを返します。
モジュール名の指定は、1つ目の引数"lpModuleName"に指定します。
"SetWindowsHookEx"メソッドの3つ目の引数"hInstance"は、
フックプロシージャを保持しているモジュールのハンドルを指定する必要が
あります。
"GetModuleHandle"が返すハンドルを"SetWindowsHookEx"の
3つ目の引数として与えることで、モジュールを指定します。

usingステートメント
using( オブジェクトの生成 )
{
処理
}

usingステートメントを用いると、using{ }ブロックから制御が離れた時に、
( )内で生成されたオブジェクトのDispose呼び出しが自動的に行われます。

usingステートメントはアンマネージリソースに対して効果を発揮します。

アンマネージリソース
C#を使っていると、オブジェクトに対してメモリ管理を意識することはありません。
これは、自動メモリ管理機能「ガベージコレクタ」が管理してくれているからです。
しかし、一部のオブジェクトはメモリを解放するタイミングを意識して
プログラムしなければなりません。
このオブジェクトをアンマネージリソースと言います。
ファイルやウィンドウ、データベース接続などがその対象です。


今回の場合、"ProcessModule"がアンマネージリソースであるため、
usingステートメントが使用されています。
(モジュールは、".dll"や".exe"などの実行ファイルだから。)


これで、どの位置にあるマウスでも、その位置を取得し続けることができました。

ただ、本当は位置だけではなく、他の情報も取得できます。
(例えば、マウスクリックとか)

マウスの位置を取得し続ける(C#) その3

前回の記事を見ていない方は「マウスの位置を取得し続ける(C#) その2」から
先にどうぞ。

前回は、フックを行うための準備まで実装しました。

今回はいよいよ、フック処理を実装します。

の前に、フック処理の開始 / 終了を切り替えるボタンを設置しましょう。
"Form1.cs"のデザイナを開き、"Button"を"Form1"上に設置します。
作成された"button1"をダブルクリックし"button1_Click"メソッドを作成します。

では、マウスフック処理を行うための準備をします。

まずは、"Form1.cs"に名前空間の修飾省略定義を行います。

/*** Form1.cs ***/
using System.Runtime.Interopesrvices;
次に、位置と、マウスフックを表す構造体を宣言します。

/*** Form1.cs ***/
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int x;
public int y;
}

[StructLayout(LayoutKind.Sequential)]
public class MouseHookStruct
{
public POINT pt;
public uint mouseData;
public uint flags;
public uint time;
public IntPtr dwExtraInfo;
}

構造体"POINT"は画面内の座標を表します。
同じく,"MouseHookStruct"はマウスフックを表します。

フックプロシージャのハンドルを保存しておくための、静的フィールドを用意します。

/*** Form1.cs ***/
private static IntPtr hHook = IntPtr.Zero;
次に,マウスに対するフックタイプを宣言します。

/*** Form1.cs ***/
private const int WH_MOUSE = 7;
フックタイプには,様々なものがあります。詳しくはMSDNを参照して下さい。

マウスフックを設定 / 削除するためのメソッドを作成します。

/*** Form1.cs ***/
private void SetMouseHook(HookMethods.HookProcedureDelegate proc)
{
// マウスフックを設定
hHook = HookMethods.SetWindowsHookEx(WH_MOUSE, proc, IntPtr.Zero, AppDomain.GetCurrentThreadId());
if (hHook == IntPtr.Zero)
{
MessageBox.Show("SetWindowsHookEx Failed.");
}
}

private void RemoveMouseHook()
{
// フックを削除
if (HookMethods.UnhookWindowsHookEx(hHook) == false)
{
MessageBox.Show("UnhookWindowsHookEx Failed.");
}
}


マウスフックプロシージャの実装を行います。

/*** Form1.cs ***/
public IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if(nCode >= 0)
{
// コールバックからのデータを整理する.
MouseHookStruct MyMouseHookStruct = (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));

String strCaption = "x = " + MyMouseHookStruct.pt.x.ToString("d") + " : y = " + MyMouseHookStruct.pt.y.ToString("d");
this.Text = strCaption;
}
return HookMethods.CallNextHookEx(hHook, nCode, wParam, lParam);
}


これで、マウスフックの処理を行うための準備ができました。

では、実際にマウスフック処理を行ってみましょう。
"button1"が押されたらフックを開始し、再度押されたらフックを終了する処理を記述します。

/*** Form1.cs ***/
private void button1_Click(object sender, EventArgs e)
{
if (hHook == IntPtr.Zero)
{
SetMouseHook(MouseHookProc);
this.button1.Text = "Unhook Windows Hook";
}
else
{
RemoveMouseHook();
this.button1.Text = "Set Windwos Hook";
}
}


実行します。

以下のような警告がでますが、後ほど削除する部分なので、無視します。
'System.AppDomain.GetCurrentThreadId()' は古い形式です: 'AppDomain.GetCurrentThreadId has been deprecated because it does not provide a stable Id when managed threads are running on fibers (aka lightweight threads). To get a stable identifier for a managed thread, use the ManagedThreadId property on Thread. http://go.microsoft.com/fwlink/?linkid=14202'


"button1"を押すと、フックが開始されます。

"Form1"内でマウスを動かすと、その座標がタイトルに表示されます。


"Form1"外では、タイトルが変化しません。

もう一度"button1"を押すと、フックが終了します。


ローカルフックを用いて、マウスの位置を取得し続けることはできました。

.NETのみで実装した場合に比べて、動きがスムーズです。


次は、グローバルフックを用いた場合について説明しますが、
今回も長くなってしまったので、次回にまわしたいと思います。

マウスの位置を取得し続ける(C#) その2

前回の最後に、「マウスの位置が○○に来たら」のようにイベントを設定することができない、と書きましたが、これは誤りです。

正確には、「.NET Frameworkだけではできない」です。

このようなイベントを設定するためには、"フック"を用いるそうです。

フックについては「参照:KAB-studio フックのしくみ」が非常にわかりやすいので、知らない人は参照してみて下さい。

まとめると、
"フック"

システム内のメッセージを条件に合わせて拾い上げること

"ローカルフック"

自スレッドのみに対して働くフック

"グローバルフック"

全てのスレッドに対して働くフック

です。

前述のように「(範囲の内外を問わず)マウスの位置に応じて処理を行う」場合は、
グローバルフックを用いなければなりません。

しかし、「グローバルフックは .NET Framework ではサポートされていない」そうです。

・・・「.NET Framework では」?

つまり、.NET Framework 以外でグローバルフックを取得すればいいわけです。


まずはローカルフックのみを試してみます。
前回のプログラムと内容的には類似していますが、精度が違います。)

前回と同様に、新しいプロジェクトを開始します。
("ファイル" > "新しいプロジェクト" > "Windows フォームアプリケーション")

ソースコードを見やすくするために、フックを扱うクラス"HookMethods"を別に定義します。
("プロジェクト" > "クラスの追加" > "クラス" で名前を"HookMethods.cs"と入力)

名前空間の修飾省略定義を行う。

/*** HookMethods.cs ***/
using System.Runtime.InteropServices;
フックを扱うためのメソッドを3つ、"user32.dll"より呼び出します。

/*** HookMethods.cs ***/
// フックプロシージャのためのデリゲート
public delegate IntPtr HookProcedureDelegate(int nCode, IntPtr wParam, IntPtr lParam);

// フックプロシージャ"lpfn"をフックチェーン内にインストールする
// 返り値はフックプロシージャのハンドル
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr SetWindowsHookEx(int idHook, HookProcedureDelegate lpfn, IntPtr hInstance, int threadId);

// "SetWindowHookEx"でインポートされたフックプロシージャを削除する
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(IntPtr idHook);

// 次のフックプロシージャにフック情報を渡す
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);

3つのメソッドについて解説します。

"SetWindowsHookEx"と"UnhookWindowsEx"はセットで使われます。

"SetWindowsHookEx"は、フックタイプに関係するフックチェーンに対して、
フックプロシージャをインストールするためのメソッドです。

監視する対象はフックタイプで指定します。
フックタイプには,マウスを監視する"WH_MOUSE"やキーボードを監視する
"WH_KEYBOARD"があります。
1つ目の引数"iHook"に指定します。

フックした時の処理は、フックプロシージャの実装に委ねられます。
フックプロシージャへのポインタを2つ目の引数"lpfn"に指定します。
そのため、C#ではデリゲートを用います。
フックプロシージャの実装は、デリゲートの実装で行います。

"SetWindowsHookEx"メソッドの返り値は、
メソッドが成功した場合は、フックプロシージャのハンドルが、
メソッドが失敗した場合は、NULLが、
返ります。

"SetWindowsHookEx"で返されたフックプロシージャのハンドルを、
"UnhookWindowsHookEx"の引数として指定することで、フックが削除されます。

"CallNextHookEx"は、現在のフックチェーン内の次のフックプロシージャに、
フック情報を渡します。
他のプログラムが同じフックチェーンに対してフックしている場合、
他のプログラムのフックプロシージャに対してフック情報を渡さなければ、
そのフックプロシージャは実行されません。つまり、フックされません。
"CallNextHookEx"を実行しなければ、他のプログラムに影響を及ぼします。
そのため、フックプロシージャの実装コード内で、"CallNextHookEx"を呼び出す
必要があります。

これらのメソッドは、必ず3つセットで利用する必要があります。

これで、フックを行う準備はできました。


今回は長くなってしまったので、続きは次回にします。


参照:
MSDNフォーラム クリックしただけで別アプリケーション上の TextBox に文字列をペーストしたい
Stephen Toub : Low-Level Mouse Hook in C#
MSDN フック

マウスの位置を取得し続ける(C#)

マウスの位置を取得し続けるプログラムを考えてみる。

.Net Frameworkには"System.Windows.Forms.Cursor"というクラスがあります。

このクラスは「マウスポインタの描画に使用されるイメージを表します。」だそうです。

簡単に言うと、マウスポインタだと。

この"Cursor"クラスを使って,マウスの位置(X座標,Y座標)を取得するには,

Cursor.Position.X
Cursor.Position.Y

を使用します。

これを使って、100ms毎にマウスの位置を取得し,
"Form1"のタイトルに表示し続けるプログラムを作ってみましょう。
("ファイル" > "新しいプロジェクト" > "Windows フォームアプリケーション")

/*** Form1.cs ***/
public Form1()
{
InitializeComponent();

new Thread(new ThreadStart(GetMousePosition)).Start();
}

public void GetMousePosition()
{
while (true)
{
SetText();
Thread.Sleep(100);
}
}

public delegate void SetTextDelegate();

public void SetText()
{
if (InvokeRequired)
{
Invoke(new SetTextDelegate(SetText));
return;
}
this.Text = "x = " + Cursor.Position.X + " : y = " + Cursor.Position.Y;
}

"Thread"クラスを扱うために、"System.Threading"名前空間の修飾省略定義を
入れることをお忘れなく。

using System.Threading;
実行してみる。


どうやらマウスの位置を取得し続けているようだ。

意外なことに"Form1"の範囲外でも、マウスの位置を取得することができた。

ただ、これだと「100ms毎」という制限があり、雑な感じがする。
(だからと言って、1ms毎にすれば良いというわけでもない。)

また、「マウスの位置が○○に来たら」のようにイベントを設定することができない。("Form1"の範囲内でなら可能。)


簡単なものならこれでいいが、複雑なことを行いたい場合は別の方法で取得する必要がありそうだ。

注意:上記のプログラムをそのまま実行し、"Form1"の終了ボタンを押すと、
   "ObjectDisposedException"が投げられます。
   これは、終了ボタンを押したことで、オブジェクト"Form1"が解放(メモリ上から
   消える)されたにも関わらず、"SetText"内でアクセスしようとしたためです。
   必ず、終了ボタンが押された時に、スレッドを抜けるように作成して下さい。

2008年1月15日火曜日

Gmail with Thunderbird その2

前回の日記では、GmailをThunderbirdで利用するメリットについて述べました。

今回は、Thunderbirdの設定を少し変更させて、下書きのメールや送信したメールもGmailに一元管理させます。


通常、書きかけのメールや送信したメールは、Thunderbirdが作ったフォルダに保存されます。

ペンのアイコンがある"下書き"フォルダに書きかけのメールが、
手紙のアイコンがある"送信済みトレイ"フォルダに送信したメールが、それぞれ保存されます。

これらは、Thunderbirdが標準で作成したフォルダです。

しかし、Gmailにも当然、"下書き"や"送信済みメール"のフォルダがあります。

メールを一元的に管理してもらうには、
こちらに保存してもらわなければなりません。

そこで、標準の設定を少し変更させて、
"[Gmail]"の下にある"下書き"、"送信済みメール"に保存させるようにします。

(前置きが長くてすいません^^;)


Thunderbirdのメニューから、"ツール" > "アカウント設定"を選択します。

左のツリーからGmailアカウントの下にある"コピーと特別なフォルダ"を選択します。

まずは"コピー"の欄から。

"メッセージ送信時に自動的にコピーを作成する"にチェックを入れます。
(通常はチェックが入ってます。)
"その他のフォルダを指定して保存"を選択し、隣のボックスから
"user_acount@gmaill.com" > "[Gmail]" > "送信済みメール"を選択します。

"下書きとテンプレート"の欄も同様に。

"下書きの保存先:"の"その他のフォルダを指定して保存"を選択し、隣のボックスから
"user_acount@gmail.com" > "[Gmail]" > "下書き"を選択します。


これで、設定は完了です。

実際にメールを下書き保存してみると、同期がとれていることがわかります。

2008年1月11日金曜日

Gmail with Thunderbird

昨年の10月24日にGmailがIMAPに対応しました。

それまではPOPのみでしたが、POPだとローカルマシンに落としてしまう。
ローカルマシンでメールを見ても、Web上では見ていないことになってたりしたわけです。

ところが、IMAPはメールをサーバ側で一元管理します。
PCで見ても、携帯から見ても、同じ状態を保つことができるようになったわけです。

ちょっと前まではGmailが英語になっていないと設定できなかったのですが、
現時点で、私のGmailアカウントを覗くと、どうやら日本語のままでも設定できるようになったようです。
(できない人は、"言語"の設定を"English(US)"にしてみてください)

設定方法:Gmail Help Center

設定上の注意として、アカウント、メールアドレスの入力では
必ず"○○@gmail.com"と、"@gmail.com"まで含めるようにしなければいけません。

ここまでは単なるGmailの話。

これだけだったらわざわざ書く必要ないよね(笑)


ここからが本題。

わざわざメーラをThunderbird限定で書いてある理由は、
ThunderbirdがGmailのラベルをフォルダとして認識してくれるからです。
(MS Outlookで試したが、ダメでした。できた人いますか?)

このように、各ラベルがフォルダとして表示されるようになります。

左の写真の場合だと、
"Friends"や"Recruit"というラベルがあることになります。

逆に、Thunderbird上でフォルダを作成すると、
Gmail側ではラベルとして認識されます。

ところで、"Friends"と"Recruit"では階層が違います。

これは、"Recruit"がアカウント名をクリックして、
フォルダを生成したのに対し、
"Friends"は[Gmail]をクリックして生成したからです。


Gmail側のラベルでは、これらの違いは以下のように表れます。
"Recruit"フォルダ → "Recruit"ラベル
"Friends"フォルダ → "[Gmail]/Friends"ラベル


もちろん、逆でも(Gmail側のラベルを設定しても)同様に反映されます。

よって、Gmail側でフィルタを用いてラべリングすると、
Thunderbird側では、フォルダ分けされたことになります。

逆に、Thunderbird側でフィルタを用いると、
Gmail側では、ラべリングされたことになります。


きちんと、フォルダ ⇔ ラベルが相互に対応しているということですね。

これは素晴らしい!

皆さんも是非利用してみて下さい(^^)

2008年1月10日木曜日

C#で常駐アプリ






C#で常駐アプリを作ってみよう。


まずは、フォームデザイナを表示させる。

"ツールボックス"から"NotifyIcon"をドラッグし、フォーム(デフォルトは"Form1")にドロップ。
この時、フォーム上には置かれませんが、それは気にしない。











NotifyIconの名前()、アイコン、テキスト(マウスオーバーした際に表示されるバルーンの内容)を設定。

次に、Form1のイベントプロパティの"FormClosed"をクリックしてメソッドを作成し、
フォームを閉じた時の処理(アイコンを非表示にする)を書く。

/*** Form1.cs ***/
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
  // アイコンを非表示にする
  this.notifyIcon1.Visible = false;
}

次に、Form1が呼び出された時の処理(Form1を非表示にする)を記述する。
"FormClosed"と同様に、"Load"をクリック。

/*** Form1.cs ***/
private void Form1_Load(object sender, EventArgs e)
{
  new Thread(new ThreadStart(HideForm)).Start();
}

private void HideForm()
{
  this.Hide();
}

実行してみる。








"InvalidOperationException"が返ってきた。

色々調べてみると、これはVisual C# 2005から実装されたExceptionのようだ。
スレッドセーフでない実装に対して投げられるらしい。

今回の場合、メインスレッドで生成されたForm1に対して、
メインスレッドから派生した別スレッドからコントロールしようとして、怒られたわけですね。
スレッド間を跨いでますからね。

C#では、別スレッドからコントロールに対して操作を行う場合には、デリゲートを挿まないといけない。
デリゲートを挿むことで操作を"委譲"した形となり、スレッドセーフになる。

と言うわけで、上のソースを改編。

/*** Form1.cs ***/
private void Form1_Load(object sender, EventArgs e)
{
  new Thread(new ThreadStart(HideForm)).Start();
}

private delegate void HideFormDelegate();

private void HideForm()
{
  if (InvokeRequired)
  {
    Invoke(new HideFormDelegate(HideForm));
    return;
  }
  this.Hide();
}

これでOK。

実行すると、タスクトレイにアイコンが生成され、フォーム(Form1)が消える。

ただ、フォームがチラッと一瞬だけ表示される(^^;)
フォームが表示されないようにするには、Form1のプロパティを開いて、以下のように設定する。
"ShowInTaskbar"を"False"に。
"WindowState"を"Minimized"に。
ただし、これだとフォームの右上にある3つのボタンが押せなくなるので、要注意。


参照:
緑のバイク ★ 初めてのC#

タスクバーに表示されたウィンドウの取得の違い(C#)

タスクバーに表示されたウィンドウ(下の図のような)を取得しようと思ったが、意外に難しい。




とりあえず、C#のProcessクラスを使ってみる。
(ちなみに、IDEは"Microsoft Visual C# 2008 Express Edition"です)

foreach (Process p in Process.GetProcesses())
{
  // ウィンドウを持つプロセスのみをコンソールに表示
  if (p.MainWindowHandle != IntPtr.Zero)
  {
    Console.WriteLine(p.ProcessName + " : " + p.MainWindowTitle);
  }
}

これを実行すると、ウィンドウを持つプロセスが全て表示される。

しかし、ウィンドウが非表示のプロセスも表示されてしまう。

さらに、フォルダはエクスプローラが1つだけ表示される。
(元々、エクスプローラにフォルダを読み込んでるだけだから当然と言えば当然)


別の方法でやってみる。今度はWin 32を利用する。

// "DllImport"用に名前空間の修飾省略定義を行う
using System.Runtime.InteropServices;

// コールバックメソッドのデリゲート
private delegate int EnumerateWindowsCallback(IntPtr hWnd, int lParam);

// EnumWindows API関数の宣言
[DllImport("user32.dll", EntryPoint = "EnumWindows")]
private static extern int EnumWindows(EnumerateWindowsCallback lpEnumFunc, int lParam);

// GetWindowText API関数の宣言
[DllImport("user32.dll", EntryPoint = "GetWindowText", CharSet = CharSet.Auto)]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCoun//

// IsWindowVisible API関数の宣言
[DllImport("user32.dll", EntryPoint = "IsWindowVisible")]
private static extern int IsWindowVisible(IntPtr hWnd);

// ウィンドウを列挙するためのコールバックメソッド
public static int EnumerateWindows(IntPtr hWnd, int lParam)
{
  const int BUFFER_SIZE = 0x1000;
  StringBuilder sb = new StringBuilder(BUFFER_SIZE);

  // ウィンドウが可視の場合
  if (IsWindowVisible(hWnd) != 0)
  {
    // ウィンドウのキャプションを取得
    if (GetWindowText(hWnd, sb, BUFFER_SIZE) != 0)
    {
      Console.WriteLine(sb);
    }
  }
  // 列挙を継続するには0以外を返す必要がある
  return 1;
}

実行は

EnumWindows(new EnumerateWindowsCallback(EnumerateWindows), 0);
と叩くだけ。

これだと、表示されているウィンドウのみが、コンソールに出力される。
(実際に出力されるのは、ウィンドウのタイトル)
"タスク マネージャ"の"アプリケーション"に表示されているものが取得できる。

しかし、今度は"Program Manager"というタイトルのウィンドウも出力される。
こいつは一体なんだ?
色々試してみると、どうやらデスクトップアイコンを管理しているらしい。
他にも何か管理(Managerだからね)してるかもしれない。


まとめると、
ウィンドウの表示・非表示に関わらず、取得したい場合はC#で。
ウィンドウが表示されているもののみを取得したい場合はWin32で。

2種類の方法は状況に応じて使い分けると良さそうだ。(もしくは混合する)


参照:smdn/プログラミングとか趣味のいろいろ

2008年1月9日水曜日

ブログ、始めました

この度、"Lassyの戯言"というブログを始めさせて頂いたLassyです。

このブログは主にIT関連の技術や、プログラミングに関する内容を扱っていきます。
よって、1日に何度も更新したり、まったく更新が無かったりとかなり不定期なブログになりますが、どうか温かい目で見守って下さい。

なお、記事の内容に関して、保証しませんのであしからず。
実際に試してみたくなった方は自己責任でお願いします。

あと、アドバイス等がありましたら、コメントにお願いします。
と言うか、アドバイスはたくさん欲しいです(笑)