.NET Frameworkで共有メモリ使うメモ

.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でも使えばいけるんじゃないかと思うけど試してない。

ロックやイベントも同時に使うことになるだろうけど、ソレも調べてないので、機会があったら調べてみよう。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

認証のために問題を解いて下さい * Time limit is exhausted. Please reload CAPTCHA.