C#でリムーバブルメディアの着脱を検知する方法 その2
その1ではリムーバブルメディアの着脱を検知する方法を説明した。この方法はUSBメモリなどではうまく機能するが、多数のメディアの読み書きに対応したメディアリーダ(例: google:image:USB メディア リーダー)では問題が起こるかもしれない。
例えば、私が使っているBuffaloのメディアリーダでは、リーダーをUSBポートに接続した時点で6つぐらいのドライブが認識される。同時にWindowメッセージ(WM_DEVICECHANGE)もドライブの数だけ飛ぶ。そして、実際にSDカードなどのメディアをメディアリーダーにセットした時には、何も検知できない。そういう場合にどうするかというのがここで説明する内容である。
やり方はその1の方法と似ていて、Windowメッセージを使う。今度使うのは次のメッセージ。
#define WM_SHNOTIFY 0x0401
これを受け取るのにウィンドウが必要だが、トップレベルウィンドウである必要はない。
その代わり、事前に関数SHChangeNotifyRegisterを呼んでおく必要がある。
private enum SHCNF { SHCNF_IDLIST = 0x0000, SHCNF_PATHA = 0x0001, SHCNF_PRINTERA = 0x0002, SHCNF_DWORD = 0x0003, SHCNF_PATHW = 0x0005, SHCNF_PRINTERW = 0x0006, SHCNF_TYPE = 0x00FF, SHCNF_FLUSH = 0x1000, SHCNF_FLUSHNOWAIT = 0x2000, } private enum SHCNE : uint { SHCNE_MEDIAINSERTED = 0x00000020, SHCNE_MEDIAREMOVED = 0x00000040, //... } var notifyEntry = new SHChangeNotifyEntry() { pIdl = IntPtr.Zero, Recursively = true }; var notifyId = SHChangeNotifyRegister(Handle, SHCNF.SHCNF_TYPE | SHCNF.SHCNF_IDLIST, SHCNE.SHCNE_MEDIAINSERTED | SHCNE.SHCNE_MEDIAREMOVED, (uint) WM.WM_SHNOTIFY, 1, ref notifyEntry);
逆にメッセージを受け取らないようにするには、SHChangeNotifyUnregisterを呼ぶ。
SHChangeNotifyUnregister(notifyId);
メッセージを受け取れる間は、WndProcでイベントを処理する。
private enum WM : uint { WM_SHNOTIFY = 0x0401, //... } protected override void WndProc(ref Message m) { switch ((WM)m.Msg) { case WM.WM_SHNOTIFY: //ここでイベントを処理する break; } base.WndProc(ref m); }
メディアが接続されたのか、取り外されたのかは、Message構造体のLParamの値を見ればわかる。
switch ((SHCNE)m.LParam) { case SHCNE.SHCNE_MEDIAINSERTED: //メディアがセットされた break; case SHCNE.SHCNE_MEDIAREMOVED: //メディアが取り外された break; }
イベントが発生したドライブは、WParamから取得する。WParamにはSHNOTIFYSTRUCT構造体へのポインタが入っている。SHNOTIFYSTRUCT構造体経由でドライブのパス名(例. "D:\\")を取得する。
struct SHChangeNotifyEntry { public IntPtr pIdl; public Boolean Recursively; } struct SHNOTIFYSTRUCT { public uint dwItem1; public uint dwItem2; } var shNotify = (SHNOTIFYSTRUCT)Marshal.PtrToStructure(m.WParam, typeof(SHNOTIFYSTRUCT)); var driveRootPathBuffer = new StringBuilder("A:\\"); SHGetPathFromIDList((IntPtr)shNotify.dwItem1, driveRootPathBuffer);
ここで示した方法は、RemovableStorageMonitor.csで実装されている。