PrivateNamespaceを使ったAppContainerとのプロセス間通信

以前の記事でAppContainerの内外で1対1プロセス間通信する方法を紹介しました。
しかし、指定したAppContainerに紐づく名前空間を使用する関係上、不特定多数のAppContainerアプリからプロセス間通信を受ける場合などには使用できませんでした。
またそれ以外にも、AppContainerのプロファイルを作った後でなくてはいけなかったり、AppContainerのプロファイル名を知らなければならなかったりと通常のプロセス間通信よりも制限が多いという難点もありました。
そこで今回はPrivateNamespaceを使用して、これらの問題を解決してみようと思います。

いつものようにサンプルコード
http://wordpress.click3.org/garakuta/TestPrivateSharedMemory.zip

PrivateNamespace空間とは

Windowsのハンドルはそれぞれ所属する名前空間があります。
これらは通常WinObjなどを使えば見ることが可能ですが、実は例外としてPrivateNamespaceというものが存在します。

PrivateNamespaceは例外的に誰でも(AppContainerも含む)アクセスできる場所に配置されており、通常の名前空間のように相対パスや絶対パスなどを気にする必要がありません。
そのため、AppContainerへのアクセスを許可しただけではAppContainer側からそこへたどり着く方法がないという従来のハンドル制限を無視することができます。
とはいえ個々のPrivateNamespaceにはアクセスコントロールが効いているので、AppContainerから利用する場合はWinBuiltinAnyPackageSidなどを許可する必要はあります。

実際にやってみよう

サンプルコードのExampleApp.exeをTestPrivateSharedMemory.exeにD&Dすると黒窓が二つ上がり、ExampleAppのほうに数字を打ち込むともう片方にも反映されます。
その状態でさらにExampleApp.exeをダブルクリックして起動し、そちらに数字を打ってもちゃんと反映されます。
D&Dで起動したものはAppContainerで、通常起動は普通のデスクトップアプリとして動作しています。

次は具体的な実装方法を説明します。

コード説明

具体的な動作としては、PrivateNamespaceを作成もしくはオープンし、共有メモリーを開く際にそのPrivateNamespaceをオブジェクト名の前につけて作るだけです。
では実際のコードを見ていきましょう。

まずはPrivateNamespaceの作成から

bool CreatePrivateNamespace(SHARED_HANDLE &privateNamespace, const boost::shared_ptr<SECURITY_DESCRIPTOR> desc, const wchar_t * const boundaryName, const wchar_t * const name) {
  SECURITY_ATTRIBUTES attr;
  attr.nLength = sizeof(SECURITY_ATTRIBUTES);
  attr.bInheritHandle = FALSE;
  attr.lpSecurityDescriptor = desc.get();
  std::vector<SHARED_SID> sidList;
  sidList.push_back(GetWellKnownSid(WinBuiltinAnyPackageSid));
  HANDLE boundaryImpl = ::CreateBoundaryDescriptorW(boundaryName, 0);
  if (boundaryImpl == NULL) {
    return false;
  }
  BOOST_FOREACH(const SHARED_SID sid, sidList) {
    if (::AddSIDToBoundaryDescriptor(&boundaryImpl, sid.get()) == FALSE) {
      ::DeleteBoundaryDescriptor(boundaryImpl);
      return false;
    }
  }
  const SHARED_HANDLE boundary(boundaryImpl, &MyDeleteBoundaryDescriptor);
  privateNamespace.reset(::CreatePrivateNamespaceW(&attr, boundary.get(), name), &MyClosePrivateNamespace);
  if (!privateNamespace) {
    return false;
  }
  return true;
}

SECURITY_ATTRIBUTESとBoundaryDescriptorを構築、それをもとにCreatePrivateNamespaceWを呼び出しています。
nameが実際に共有メモリーなどを作成する際につける名前空間の名前で、boundaryNameは別プロセスから同じPrivateNamespaceを作る際に使用する識別子になります。
boundaryNameさえあっていれば同じ名前空間になるので、OpenPrivateNamespaceでnameを変えて実行すれば別名だが同じものとして動作するハンドルを取得できます。
また、AddSIDToBoundaryDescriptorでアクセスコントロールを行っており、WinBuiltinAnyPackageSidを指定することでAppContainerからのアクセスを許可しています。

次に呼び出し側

  SHARED_HANDLE privateNamespace;
  if (!CreatePrivateNamespace(privateNamespace, desc, L"boundaryName", L"privateNamespaceName")) {
    std::wcout << L"Error: PrivateNamespaceの生成に失敗しました" << std::endl;
    std::wcout << ::GetLastError() << std::endl;
    std::wcin.ignore();
    return 1;
  }
  const wchar_t * const sharedMemoryName = L"privateNamespaceName\\AppConteinerTestMemory";
  HANDLE handle;
  if (!CreateSharedMemory(handle, sharedMemoryName, sizeof(SharedStruct), desc)) {
    std::wcout << L"Error: 共有メモリーの生成に失敗しました" << std::endl;
    std::wcout << ::GetLastError() << std::endl;
    std::wcin.ignore();
    return 1;
  }

特に説明することはありません、「#{PrivateNamespaceの名前空間名}\#{オブジェクトの名前}」で共有メモリーを作成しているだけです。

その他AppContainerでのexeの起動などは以前解説したことがあるので省き、次はExampleAppのPrivateNamespaceのオープンから。

bool OpenPrivateNamespace(SHARED_HANDLE &privateNamespace, const wchar_t * const boundaryName, const wchar_t * const name) {
  std::vector<SHARED_SID> sidList;
  sidList.push_back(GetWellKnownSid(WinBuiltinAnyPackageSid));
  HANDLE boundaryImpl = ::CreateBoundaryDescriptorW(boundaryName, 0);
  if (boundaryImpl == NULL) {
    return false;
  }
  BOOST_FOREACH(const SHARED_SID sid, sidList) {
    if (::AddSIDToBoundaryDescriptor(&boundaryImpl, sid.get()) == FALSE) {
      ::DeleteBoundaryDescriptor(boundaryImpl);
      return false;
    }
  }
  const SHARED_HANDLE boundary(boundaryImpl, &MyDeleteBoundaryDescriptor);
  privateNamespace.reset(::OpenPrivateNamespaceW(boundary.get(), name), &MyClosePrivateNamespace);
  if (!privateNamespace) {
    return false;
  }
  return true;
}

OpenPrivateNamespaceWで先ほど別プロセスで作成したPrivateNamespaceをオープンしています。
この時boundaryの内容が作成側と異なると失敗するので注意が必要です。
また、前述のようにnameに関しては作成時と同一にする必要はありません。

次はこちらも呼び出し側

  SHARED_HANDLE privateNamespace;
  if (!OpenPrivateNamespace(privateNamespace, L"boundaryName", L"privateNamespaceName")) {
    std::wcout << L"Error: 名前空間のオープンに失敗しました" << std::endl;
    std::wcout << ::GetLastError() << std::endl;
    std::wcin.ignore();
    return 1;
  }
  void * ptr;
  const wchar_t * const sharedMemoryName = L"privateNamespaceName\\AppConteinerTestMemory";
  if (!CreateSharedMemory(ptr, sharedMemoryName, sizeof(SharedStruct))) {
    std::wcout << L"Error: 共有メモリーの生成に失敗しました" << std::endl;
    std::wcout << ::GetLastError() << std::endl;
    std::wcin.ignore();
    return 1;
  }

こちらも特に説明することはなく、オープンしたPrivateNamespaceで共有メモリーを作成しているだけです。

その他実際にプロセス間通信を行っているところなどはPrivateNamespaceとは関係がなく、ググればすぐにわかるので割愛します。

終わりに

これでAppContainerを含む複数のプロセスで1対多や多対多のプロセス間通信が行えるようになりました。
また今回は共有メモリーで行いましたが、Mutexやパイプなどその他のオブジェクトに関しても同様の手順で使用することができます。
あまり需要はないと思いますが、参考にしてくれる人がいれば幸いです。

ではまた。