いつも参考にさせて頂いています。
早速ですが、質問させていただきます。
環境:XP ProS P3
VB2008にて.netFrameWork2.0を使用して開発
私は、今、SDカードの抜き差しをトリガーとしたプログラムを作成しています。
調べたところ、ウィンドウメッセージのWM_DEVICECHANGEを捕捉すれば、できそうだとわかったので、作成をしてみました。
ですが、USB接続のリーダライタを使用するとき、元からSDがささったものを接続すれば、メッセージが発生するのですが、リーダライタをさした後にSDを差し込んでもメッセージが発生しませんでした。
そこで、もう少し調べてみたところ、題名にもあるように「SHCChangeNotifyRegister」を使えば、WM_DEVICECHANGEで捕捉できない抜き差しも捕捉できるとのことなので、下記のウェブサイト(C#の例)を参考にVBに移植を行いました。
参考にしたサイト
http://d.hatena.ne.jp/ohyajapan/20081123/p2
ですが、SHCChangeNotifyRegister実行時に、「AccessViolationException 保護されているメモリに読み取りまたは書き込み操作を行おうとしました。他のメモリが壊れていることが考えられます。」が発生してしまいます。
理由を探してはみたのですが、SHCChangeNotifyRegisterに関しての記述自体が少ないため、ここで質問させていただきました。
ウィンドウが必要とのことなので、フォームのLoadイベントにてSHCChangeNotifyRegisterを使用しています。
Activedなどにも移してみましたが、変わりませんでした。
よろしくお願いします。
>下記のウェブサイト(C#の例)を参考にVBに移植を行いました。
現象を再現可能な移植後のコードを提示できますか?
遅くなって申し訳ありません。
移植後のソースを抜粋して、提示致します。
Imports System.IO
Imports System.Runtime.InteropServices
Imports Microsoft.Win32.SafeHandles
Public Class Form1
<DllImport("shell32.dll", EntryPoint:="#2", CharSet:=CharSet.Auto)> _
Private Shared Function SHChangeNotifyRegister( _
ByVal hWnd As IntPtr, _
ByVal fSources As SHCNF, _
ByVal fEvents As SHCNE, _
ByVal wMsg As UInt32, _
ByVal cEntries As Integer, _
ByVal pFsne As SHChangeNotifyEntry) As UInt32
End Function
<DllImport("shell32.dll", EntryPoint:="#4", CharSet:=CharSet.Auto)> _
Private Shared Function SHChangeNotifyUnregister( _
ByVal hNotify As UInteger) As Boolean
End Function
<DllImport("shell32.dll", CharSet:=CharSet.Auto)> _
Private Shared Function SHGetPathFromIDList( _
ByVal pidl As IntPtr, _
ByVal StringBuilder As Path) As Boolean
End Function
Private Enum SHCNF As UInteger
SHCNF_IDLIST = &H0
SHCNF_PATHA = &H1
SHCNF_PRINTERA = &H2
SHCNF_DWORD = &H3
SHCNF_PATHW = &H5
SHCNF_PRINTERW = &H6
SHCNF_TYPE = &HFF
SHCNF_FLUSH = &H1000
SHCNF_FLUSHNOWAIT = &H2000
End Enum
Private Enum SHCNE As UInteger
SHCNE_MEDIAINSERTED = &H20
SHCNE_MEDIAREMOVED = &H40
End Enum
Private Structure SHNOTIFYSTRUCT
Public dwItem1 As UInteger
Public dwItem2 As UInteger
End Structure
Private Structure SHChangeNotifyEntry
Public pIdl As IntPtr
Public Recursively As Boolean
End Structure
Private Enum WM As UInteger
WM_SHNOTIFY = &H401
WM_DEVICECHANGE = &H219
WM_CONTEXTMENU = &H7B
WM_SYSCOMMAND = &H112
WM_COPYDATA = &H4A
End Enum
Private notifyId As UInteger
Private isActived As Boolean = False
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim notifyEntry = New SHChangeNotifyEntry()
notifyEntry.pIdl = IntPtr.Zero
notifyEntry.Recursively = True
ここでエラー→ Dim notifyIdLocal = SHChangeNotifyRegister(Handle, _
SHCNF.SHCNF_TYPE Or SHCNF.SHCNF_IDLIST, _
SHCNE.SHCNE_MEDIAINSERTED Or SHCNE.SHCNE_MEDIAREMOVED, _
CUInt(WM.WM_SHNOTIFY), 1, notifyEntry)
notifyId = CType(notifyIdLocal, UInteger)
End Sub
End Class
Dim notifyIdLocal = SHChangeNotifyRegisterの部分でエラーが発生します。
元の定義が LONG なものを、わざわざ UInteger にしているのは何故でしょうか?
サイズが一緒なので型自体の互換性は保たれていますが、不自然な印象を受けますし、
符号無し整数型にするメリットも思い当たりません(逆パターンならまだ分かりますが)。
たとえば、SHCNE_MEDIAINSERTED の元の定義は
#define SHCNE_MEDIAINSERTED 0x00000020L
となっています。0x00000020L であって 0x00000020UL ではありません。
またそれを利用しようとしている第 3 引数も、元の定義は ULONG ではなく LONG です。
もうひとつ。「UInteger」だったり「UInt32」だったりといった表記揺れが気になります。
意味としては一緒ですが、統一しておいた方が良いと思いますよ。
で。何よりも問題なのは第 6 引数です。
そもそも、SHChangeNotifyRegister の定義は
戻り値: ULONG
引数1: HWND
引数2: int
引数3: LONG
引数4: UINT
引数5: int
引数6: SHChangeNotifyEntry*
ですよね。
http://msdn.microsoft.com/en-us/library/bb762120%28VS.85%29.aspx
一方、蒼月さんの定義は、
戻り値: UInt32
引数1: ByVal IntPtr
引数2: ByVal SHCNF(As UInteger)
引数3: ByVal SHCNE(As UInteger)
引数4: ByVal UInt32
引数5: ByVal Integer
引数6: ByVal SHChangeNotifyEntry(Structure)
となっています。少なくとも第 6 引数はこのままだとマズイでしょう。
とりあえず、SHChangeNotifyEntry の定義を修正して、
<StructLayout(LayoutKind.Sequential)> Private Class SHChangeNotifyEntry
Public pIdl As IntPtr
<MarshalAs(UnmanagedType.Bool)> Public Recursively As Boolean
End Class
のようにクラスとして用意してみてください。
とりあえずこれで、AccessViolationException は回避できると思います。
> Public Recursively As Boolean
上記にも書きましたが、属性 <MarshalAs(UnmanagedType.Bool)> を付与しましょう。
SHChangeNotifyUnregister や SHGetPathFromIDList の戻り値についても同様です。
また、SHGetPathFromIDList の定義も大幅に間違っています。引数に System.IO.Path クラスを
受け取ることは(アンマネージ API でもマネージ API でも)まずありえないです。
>魔界の仮面弁士 様
レス、ありがとうございます。
遅くなってしまい、申し訳ありません。
まだ、アドバイス頂いた内容を試せていない状態です。
明日、明後日くらいまでには、試すことができると思いますので、結果をお伝え致します。
ありがとうございました。
>魔界の仮面弁士 様
ご指摘頂いた内容を確認してみました。
>とりあえず、SHChangeNotifyEntry の定義を修正して、
><StructLayout(LayoutKind.Sequential)> Private Class >SHChangeNotifyEntry
> Public pIdl As IntPtr
> <MarshalAs(UnmanagedType.Bool)> Public Recursively As Boolean
>End Class
>のようにクラスとして用意してみてください。
>とりあえずこれで、AccessViolationException は回避できると思います。
ひとまずこれを反映させたところ、エラーの発生が無くなり、メッセージの取得が可能となりました。
また、定義があいまいだという件については、修正中です。
少し時間を頂くかもしれませんが、修正が終わり次第、
修正後のソースを掲載したいと思います。
ツイート | ![]() |