C#のMemoryMappedFile(共有メモリー)でエラーが出たときの対処法

Windows上でサービスなどのシステム権限やAdminisratorsなどの高い権限でMemoryMappedFile使って共有メモリを作成し、一般ユーザー権限のような権限レベルの異なるプロセスから共有メモリをOpenExistingしようとする場合に出るであろうエラーの対象方法です。

同じ権限上で動くプロセス間では発生しない(=VisualStudioで編集時は気が付きにくい)ので気が付くのが遅れがちなので参考になればと思い書きました。

クライアント側でFileNotFoundExceptionが発生する

サービスで作成したMemoryMappedFileをクライアント側でOpenExisting開こうとしたときに以下のようにFileNotFoundExceptionが発生した場合です。

// こんなエラーが発生する
System.IO.FileNotFoundException: 指定されたファイルが見つかりません。
   場所 System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   場所 System.IO.MemoryMappedFiles.MemoryMappedFile.OpenCore(String mapName, HandleInheritability inheritability, Int32 desiredAccessRights, Boolean createOrOpen)
   場所 System.IO.MemoryMappedFiles.MemoryMappedFile.OpenExisting(String mapName, MemoryMappedFileRights desiredAccessRights, HandleInheritability inheritability)
   場所 ...以下自分のコード

この例外が発生した場合、CreateNew する時のmapNameの頭に"Global\"を付けます。

異なる権限レベルで作成された共有メモリーはデフォルトでは見えません。

// こんな風に先頭にGlobalつけないと下位権限には不可視になる
MemoryMappedFile.CreateNew("Global\\home.map", 1024 * 1024 * 12);

ちなみにこうしてしまうと管理者権限でしか立ち上がらなくなるのでVisualStudioを管理者権限で起動しないといけなくなります。

割と作業に差し支えるため、デバッグ時は以下のように切り分けしたほうがいいかもしれません。

string mapName = "Global\\hoge.map";
if(isDebugMode()) // デバッグもーとの時はGlobalプレフィックスを付けない
{
    mapName = "hoge.map";
}

クライアント側でUnauthorizedAccessExceptionが発生する

サービスで作成したMemoryMappedFileをクライアント側でOpenExisting開こうとしたときに以下のようにUnauthorizedAccessExceptionが発生した場合です。

// こんなエラーが発生する
System.UnauthorizedAccessException: パスへのアクセスは拒否されました。
   場所 System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   場所 System.IO.MemoryMappedFiles.MemoryMappedFile.OpenCore(String mapName, HandleInheritability inheritability, Int32 desiredAccessRights, Boolean createOrOpen)
   場所 System.IO.MemoryMappedFiles.MemoryMappedFile.OpenExisting(String mapName, MemoryMappedFileRights desiredAccessRights, HandleInheritability inheritability)
   場所 ...以下自分のコード

これはサーバー側で適切な権限を付与できていない場合に発生します。

MemoryMappedFile.CreateNewした後に設定が必要です。

// 事前に宣言しておく
using System.Security.AccessControl;

// ...中略...

var map = MemoryMappedFile.CreateNew("Global\\home.map", 1024 * 1024 * 12);

// 既定の権限設定を取得する
MemoryMappedFileSecurity permission = map.GetAccessControl();

// Everyone(= 誰でも)アクセスできるようにする
permission.AddAccessRule(
  new AccessRule<MemoryMappedFileRights>("Everyone", 
    MemoryMappedFileRights.FullControl, AccessControlType.Allow));

// 権限を設定しなおす
map.SetAccessControl(permission);

サンプルなのでEveryone設定してますが、権限がガバなので使用時は適切なユーザーを指定してください。

これでプロセスを起動すると共有メモリを共有できるようになります。