.NET Framework4.0からメモリマップドファイルがサポートされたみたいですが、実務で4.0ってまだあまり使われてないよね。多分。
で、実際仕事で.NET Framework2.0でメモリマップドファイル使う事になって、ちょっと手こずったのでメモ。
言語はVBだけど、C#でも同じようにできます。
前置き。
今回の仕事はVC++で作られた(まだ開発中だけど)のサービスプロセスの状態をモニタしたりするツールをVB2005で作ることになった。
実行環境はWindowsServer2008で、サービスプロセスはユーザ権限の子プロセスを作って、そのなかで共有メモリを作るんだけど、その辺は別の機会に。
前置き終わり。
で、まぁ今回はモニタするだけなので、ロックとかイベントとかは使いません。
サービスプロセス側でCreateFileMappingで確保したメモリマップドファイルをモニタツールから見るだけです。
まずはWin32APIを使うためにDllImportします。
あ、下記ソース中の< は全角の<で書いてるので注意。
WordPressが勝手に閉じタグ書いちゃうのはどうにかならんものか・・・
<DllImport ("kernel32.dll", EntryPoint:="CreateFileMapping")> _
Private Shared Function CreateFileMapping( _
ByVal hFile As IntPtr, _
ByVal lpFileMappingAttributes As IntPtr, _
ByVal flProtect As UInt32, _
ByVal dwMaximumSizeHigh As UInt32, _
ByVal dwMaximumSizeLow As UInt32, _
ByVal lpName As String) As IntPtr
End Function
<DllImport("kernel32.dll", EntryPoint:="OpenFileMapping")> _
Private Shared Function OpenFileMapping( _
ByVal flProtect As UInt32, _
ByVal bInheritHandle As Boolean, _
ByVal lpName As String) As IntPtr
End Function
<DllImport("kernel32.dll", EntryPoint:="MapViewOfFile")> _
Private Shared Function MapViewOfFile( _
ByVal hMap As IntPtr, _
ByVal AccessMode As UInt32, _
ByVal OffsetHigh As UInt32, _
ByVal OffsetLow As UInt32, _
ByVal MapSize As UInt32) As IntPtr
End Function
<DllImport("kernel32.dll", EntryPoint:="UnmapViewOfFile")> _
Private Shared Function UnmapViewOfFile( _
ByVal hMap As IntPtr _
) As Boolean
End Function
<DllImport("kernel32.dll", EntryPoint:="CloseHandle")> _
Private Shared Function CloseHandle( _
ByVal hHandle As IntPtr _
) As Boolean
End Function
今回モニタツールは、サービスプロセスが作ったメモリマップドファイルを見るだけなので、CreateFileMappingを使うことはないんだけど、一応おまけで書いておいた。
Privateなのは気にしないで。
で、実際使う場合だけど、
Private m_Handle As IntPtr
Private m_Map As IntPtr
Public Sub OpenMappedMemory(ByVal strObjectName As String)
m_Handle = IntPtr.Zero
m_Map = IntPtr.Zero
m_Handle = OpenFileMapping(FILE_MAP_READ, False , "Global" + strObjectName)
m_Map = MapViewOfFile(m_Handle, FILE_MAP_READ, 0, 0, 0)
End Sub
Public Sub CloseMappedMemory()
If m_Map <> IntPtr.Zero Then
UnmapViewOfFile(m_Map)
m_Map = IntPtr.Zero
End If
If m_Handle <> IntPtr.Zero Then
CloseHandle(m_Handle)
m_Handle = IntPtr.Zero
End If
End Sub
こんな感じかな。
OpenFileMappingの第3引数に”Global”を付けてるのは、相手がサービスプロセスだから。
普通のプログラム同士だったら付けなくていいっぽい。
今回は、サービスプロセスが起動済みで、メモリマップドファイルもサービスプロセスが作ってくれるからOpenFileMappingでいいんだけど、もし自分でメモリマップドファイルを作るなら、OpenFileMappingのとこが
Dim hFile As IntPtr = DirectCast(&HFFFFFFFF, IntPtr)
m_Handle = CreateFileMapping(hFile, Nothing, _
PAGE_READONLY, 0, size, "Global" + strObjectName)
とかになってるかな。
で、値の取得はMarshalクラスを使って色々するんだけど、今回は構造体の配列として取得します。
C++の定義で
typedef struct {
DWORD dwStatus; //状態
CHAR chName[64]; //名称
} S_SERVICE_STAT;
こんな感じの構造体が定義してあったとして、コレの配列をメモリマップドファイルに書き込んでたとすると、VB側は
<Structlayout (LayoutKind.Sequential)> _
Private Structure S_SERVICE_STAT
<Marshalas (UnmanagedType.U4)> _
Public dwStatus As UInt32
<Marshalas (UnmanagedType.ByValArray, SizeConst:=64)> _
Public chName() As Byte
End Structure
こんな感じで構造体を定義しておいて
Public Function GetStatusList() As S_SERVICE_STAT()
Dim lst As New List(Of S_SERVICE_STAT)
If m_Map = IntPtr.Zero Then
'共有メモリアクセス失敗
Return Nothing
End If
Dim buff() As Byte = New Byte(64 + 4) {}
For i As Integer = 0 To 32
Dim ptrNext As IntPtr = New IntPtr(m_Map.ToInt32 + (i * (64 + 4)))
Dim _stat As S_SERVICE_STAT = _
DirectCast(Marshal.PtrToStructure(ptrNext, GetType(S_SERVICE_STAT)), S_SERVICE_STAT)
If _stat.dwStatus = 0 Then
’データがある場合はサービスプロセスによってdwStatus に0以外が書き込まれてる仕様
Exit For
End If
lst.Add(_stat)
Next
Return lst.ToArray()
End Function
こんな感じ。
ポイントは
Dim ptrNext As IntPtr = New IntPtr(m_Map.ToInt32 + (i * (64 + 4)))
この部分かな。
コレはC言語のポインタ演算みたいな方法でアクセスしてみた例。
この例ではchNameがByte配列なので、このままだとVBでは使いづらいので、実際には
System.Text.Encoding.GetEncoding("Shift_JIS").GetString(_stat.chName)
とかを挟んで文字列化します。
サービスプロセス側が文字列をUnicode使ってくれるなら、chNameはByte配列じゃなくて
<Marshalas (UnmanagedType.BStr, SizeConst:=64)> _
Public chQueName() As String
とかで行けるっぽい。
もし書き込みもやりたい場合は、OpenFileMappingやMapViewOfFileにFILE_MAP_ALL_ACCESS指定して、
Marshal.StructureToPtrでも使えばいけるんじゃないかと思うけど試してない。
ロックやイベントも同時に使うことになるだろうけど、ソレも調べてないので、機会があったら調べてみよう。