こういうコードを書いたとき、38ってWindows SDKのどこかで#defineされていないの?と疑問に思いました。

#include <iostream>
#include <windows.h>
 
int main()
{
  // {00000000-0000-0000-0000-000000000000}
  GUID guid = GUID_NULL;
 
  WCHAR s[39]; // ← これ!!!39ってみんな書くでしょ
 
  StringFromGUID2(guid, s, ARRAYSIZE(s));
  std::wcout << s << std::endl;
}

というわけで、grepしたところ以下のものが見つかりました。

  • axcore.idl:#define CHARS_IN_GUID 39
  • cfgmgr32.h:#define MAX_GUID_STRING_LEN 39
  • strmif.h:#define CHARS_IN_GUID 39
  • Msi.h:#define MAX_GUID_CHARS 38

38なのは、最後のヌル文字を含まない場合ですね。

いずれも、<windows.h>などからインクルードされるものではありません。なので、いつでも使おうと無条件に言える感じがしませんが、気が向いたら使ってみてはどうでしょうか、と締めてみます。

この記事のカテゴリ

  • ⇒ GUIDの文字列表現の文字数のための定数
  • ⇒ GUIDの文字列表現の文字数のための定数

RegisterActiveObject関数は内部でRunning Object Table (ROT)を使っていると、MSDNライブラリにも書かれてあります: Registration Functions (Automation)

ROTは、モニカーをキーとしてオブジェクトを登録する、つまり連想配列のようなものです。ならば、クラスIDをキーに登録するRegisterActiveObjectはどういうモニカーでROTに登録するのか、ということを知りたくなりました。

調べた結果、クラスIDの文字列のアイテムモニカーであることが分かりました。すなわち、モニカーの文字列表現は"!{XXXXXXXX-……}"という形式になります。

今回のサンプルプログラムは、RegisterActiveObjectで登録したオブジェクトをIRunningObjectTable::GetObjectで取り出したり、IRunningObjectTable::Registerで登録したオブジェクトをGetActiveObjectで取り出したりするというものです。

#define UNICODE
#define _UNICODE
#define WIN32_LEAN_AND_MEAN
#define _ATL_NO_AUTOMATIC_NAMESPACE
 
#include <iostream>
#include <cstdlib>
#include <windows.h>
#include <atlbase.h>
#include <atlcom.h>
#include <atlutil.h>
 
// {EECAE0DA-5F2B-48E2-9F22-09A788DA83FE}
static const GUID CLSID_Hoge =
{
  0xeecae0da, 0x5f2b, 0x48e2,
  { 0x9f, 0x22, 0x9, 0xa7, 0x88, 0xda, 0x83, 0xfe }
};
 
struct Hoge : IUnknown
{
  IFACEMETHOD(QueryInterface)(REFIID riid, void** ppv) override
  {
    if (riid == IID_IUnknown)
    {
      *ppv = (IUnknown*)this;
      return S_OK;
    }
    return E_NOINTERFACE;
  }
  IFACEMETHOD_(DWORD, AddRef)() override { return 1; }
  IFACEMETHOD_(DWORD, Release)() override { return 1; }
};
 
void ExitIfFailed(_In_ PCWSTR functionName, HRESULT hr)
{
  if (SUCCEEDED(hr))
  {
    return;
  }
  std::wclog << functionName << '\n';
  std::wclog << std::showbase << std::hex << hr << '\n';
  std::wclog << ATL::AtlGetErrorDescription(hr).GetString() << std::endl;
  std::quick_exit(static_cast<int>(hr));
}
 
int main()
{
  std::wclog.imbue(std::locale(""));
  auto hrInit = CoInitializeEx(
    nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);
  ExitIfFailed(L"CoInitializeEx", hrInit);
 
  ATL::CComPtr<IRunningObjectTable> rot;
  auto hrRot = GetRunningObjectTable(0, &rot);
  ExitIfFailed(L"GetRunningObjectTable", hrRot);
 
  ATL::CComPtr<IMoniker> mk;
  auto hrMk = CreateItemMoniker(
    L"!", L"{EECAE0DA-5F2B-48E2-9F22-09A788DA83FE}", &mk);
  ExitIfFailed(L"CreateItemMoniker", hrMk);
 
  std::cout << std::boolalpha;
  {
    Hoge obj;
    DWORD reg;
    auto hrReg = RegisterActiveObject(
      &obj, CLSID_Hoge, ACTIVEOBJECT_STRONG, &reg);
    ExitIfFailed(L"RegisterActiveObject", hrReg);
 
    ATL::CComPtr<IUnknown> unk;
    auto hrGet = rot->GetObject(mk, &unk);
    ExitIfFailed(L"IRunningObjectTable::GetObject", hrGet);
 
    std::cout << unk.IsEqualObject(&obj) << std::endl;
 
    RevokeActiveObject(reg, nullptr);
  }
  {
    Hoge obj2;
    DWORD reg;
    auto hrReg = rot->Register(
      ROTFLAGS_REGISTRATIONKEEPSALIVE, &obj2, mk, &reg);
    ExitIfFailed(L"RegisterActiveObject", hrReg);
 
    ATL::CComPtr<IUnknown> unk;
    auto hrGet = GetActiveObject(CLSID_Hoge, nullptr, &unk);
    ExitIfFailed(L"GetActiveObject", hrGet);
 
    std::cout << unk.IsEqualObject(&obj2) << std::endl;
 
    rot->Revoke(reg);
  }
  std::quick_exit(0);
}

クラスモニカーを使っているのかと思っていましたが、その予想は外れてしまいました。

この記事のカテゴリ

  • ⇒ RegisterActiveObjectとROT
  • ⇒ RegisterActiveObjectとROT
  • ⇒ RegisterActiveObjectとROT

デバイスインスタンスパス(デバイスインスタンスID)からIPアドレスやMACアドレスなどの情報を取り出したり、あるいはその逆でデバイスインスタンスパスを得る話です。WMIを使って良ければ、Win32_NetworkAdapterのPnPDeviceIdプロパティで解決ですが、諸般の事情でこれを使いたくない場合の方法です。

Setup APIやCfgMgr APIなどで取り扱われるデバイスインスタンスパスはWindowsに接続されているデバイスを識別する文字列です。一方、ネットワーク関係のAPIでは、各ネットワークインタフェースの識別方法にGUID、LUID、インタフェースインデックスなど複数あります。これらを結び付ける方法が知りたいわけです。

これには、Network Configuration InterfacesのAPIを使います。紹介記事がBindview とINetCfg API | Japan WDK Support Blogにあります。このAPIを使うと、デバイスインスタンスパスとインタフェースGUIDの組み合わせが判明します。

以下、サンプルプログラムです。

#include <iostream>
#include <comdef.h>
#include <windows.h>
#include <netcfgx.h>
#include <devguid.h>
 
_COM_SMARTPTR_TYPEDEF(INetCfg, __uuidof(INetCfg));
_COM_SMARTPTR_TYPEDEF(INetCfgClass, __uuidof(INetCfgClass));
_COM_SMARTPTR_TYPEDEF(IEnumNetCfgComponent, __uuidof(IEnumNetCfgComponent));
_COM_SMARTPTR_TYPEDEF(INetCfgComponent, __uuidof(INetCfgComponent));
 
int main()
{
  std::locale l(std::locale::classic(), "", std::locale::ctype);
  std::wcout.imbue(l);
  std::wclog.imbue(l);
 
  HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
  if (FAILED(hr))
  {
    return static_cast<int>(hr);
  }
 
  {
    INetCfgPtr nc(CLSID_CNetCfg, nullptr, CLSCTX_INPROC_SERVER);
    nc->Initialize(nullptr);
    INetCfgClassPtr ncc;
    nc->QueryNetCfgClass(&GUID_DEVCLASS_NET, IID_PPV_ARGS(&ncc));
    IEnumNetCfgComponentPtr e;
    ncc->EnumComponents(&e);
    ULONG fetched;
    INetCfgComponentPtr component;
    while (e->Next(1, &component, &fetched) == S_OK)
    {
      DWORD characteristics;
      if (SUCCEEDED(component->GetCharacteristics(&characteristics)))
      {
        if ((characteristics & NCF_HIDDEN) != 0)
          continue;
      }
      PWSTR displayName;
      if (SUCCEEDED(component->GetDisplayName(&displayName)))
      {
        std::wcout << displayName << std::endl;
        CoTaskMemFree(displayName);
      }
      PWSTR devNodeId;
      if (SUCCEEDED(component->GetPnpDevNodeId(&devNodeId)))
      {
        std::wcout << '\t' << devNodeId << std::endl;
        CoTaskMemFree(devNodeId);
      }
      GUID guid;
      if (SUCCEEDED(component->GetInstanceGuid(&guid)))
      {
        WCHAR buf[39];
        StringFromGUID2(guid, buf, ARRAYSIZE(buf));
        std::wcout << '\t' << buf << std::endl;
      }
      std::wcout << std::hex << "\tcharacteristics: "
        << characteristics << std::endl;
    }
    nc->Uninitialize();
  }
  CoUninitialize();
}

このプログラムで出力しているGUIDは、Win32_NetworkAdapter.GUIDと一致します。

また、IP_ADAPTER_ADDRESSES::Luidとは、以下の関数で相互に変換できます。

この2関数はWindows Vistaで追加された関数です。もし、それ以前を対象とする場合はNhpAllocateAndGetInterfaceInfoFromStack関数を使いましょう。こちらでは、インタフェースインデックス (IP_ADAPTER_ADDRESSES::IfIndex)と結び付けられます。

この記事のカテゴリ

  • ⇒ デバイスインスタンスパスからネットワークインタフェースの情報を引き出す(あるいはその逆)
  • ⇒ デバイスインスタンスパスからネットワークインタフェースの情報を引き出す(あるいはその逆)

以前の記事、UCRTをアプリと同じフォルダに置く (VS2015 RTM)の更新版です。Visual Studio 2015 Update 1での変更を反映させました。Update 2も対象にしています。

おさらい

Visual C++ 2015でCRTの構成が大きく変わりました。C標準ライブラリ関数の多くがucrtbase.dll(リリースビルド), ucrtbased.dll(デバッグビルド)に移され、Windows SDKの配下になりました。これがユニバーサルCRT (Universal CRT; UCRT)です。

ucrtbase.dllはOSの一部という扱いになりました。Windows 10では最初から入っています。Vista~8.1に対してもWindows Updateで配信されており、最新はKB311841です: Windows での汎用の C ランタイムの更新プログラム。なお、VS 2015 RTM時点ではKB2999226でした。

あまり変わらない場合

以下の場合、Visual C++ 2013までとほぼ変わりありません。

  • 再頒布パッケージvcredist_x86.exe, vcredist_x64.exeを使う場合: UCRTも同時にインストールされます。
  • コンパイラオプション/MT/MTdで静的リンクする場合: UCRT部分も静的にリンクされます。

なお、日本語版の再頒布パッケージは、マイクロソフトのダウンロードセンターからダウンロードできます。

ちょっと変わる場合

問題は、VC++ランタイムDLLを同梱し、アプリと同じフォルダにランタイムDLLを置く場合です。

  • ユニバーサルCRT(KB311841やKB2999226)がインストール済みであることをあてにしてよければ、概ね今までどおりです。すなわち、VC++ 2015でビルドしたアプリと同じフォルダにVC++ランタイムDLLを置くだけで良いです。
  • それをあてにできない場合、VC++ 2015のランタイムDLLに加え、ユニバーサルCRT(ucrtbase.dllとそれが依存するファイル)も同じフォルダに置くことになります。

ようするにランタイムDLLのファイルがたくさん増えたというだけで、難しいことではないです。

なお、マージモジュールを使う場合も同じく影響があるはずです。なぜなら、VC++ 2015のマージモジュールには、UCRTが含まれていないそうです。しかし、私がマージモジュールを使ったことがないので、お話しできません。

DLLの場所

というわけで、VC++ 2015とUCRTのランタイムDLLをアプリに同梱するにあたり、必要なDLLファイルの場所を紹介していきます。なお、この先現れるC:\Program Files (x86)は、環境に応じて適当に読み替えてください。

リリースビルドの場合、

  • Visual C++ 2015ランタイム(リリースビルド用)
  • ユニバーサルCRT(リリースビルド用)

で紹介するファイルが必要です。デバッグビルドの場合

  • Visual C++ 2015ランタイム(デバッグビルド用)
  • ユニバーサルCRT(デバッグビルド用)

で紹介するファイルが必要です。

いつもどおりであれば、デバッグビルド用のランタイムライブラリは再頒布可能ではないはずです。すなわち、自分でデバッグするためなどの用途でしか使えないと思います。注意してください。

Visual C++ 2015ランタイム(リリースビルド用)

C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redistの中にファイルがあります。VC++ 2013までと同じなので、以前のバージョンを使ったことがあるかたなら、すぐ分かるでしょう。

このフォルダの中のx86ならびにx64フォルダの中、以下のファイルが必要になります。

  • Microsoft.VC140.CRT\vcruntime140.dll: ほぼすべての場合
  • Microsoft.VC140.CRT\msvcp140.dll: C++標準ライブラリを使用している場合
  • Microsoft.VC140.CRT\vccorlib140.dll: C++/CXプログラムの場合
  • Microsoft.VC140.CRT\concrt140.dll: 同時実行ランタイム (ConcRT)を使用している場合
  • Microsoft.VC140.CXXAMP\vcamp140.dll: AMPを使用している場合
  • Microsoft.VC140.OPENMP\vcomp140.dll: OpenMPを使用している場合

Visual C++ 2015ランタイム(デバッグビルド用)

C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\debug_nonredistにリリースビルド用の場合と同じような階層構造でファイルが置いてあります。

x86ならびにx64の中に、以下のようなファイルがあります。

  • Microsoft.VC140.DebugCRT\vcruntime140d.dll
  • Microsoft.VC140.DebugCRT\msvcp140d.dll
  • Microsoft.VC140.DebugCRT\vccorlib140d.dll
  • Microsoft.VC140.DebugCRT\concrt140d.dll
  • Microsoft.VC140.DebugCXXAMP\vcamp140d.dll
  • Microsoft.VC140.DebugOpenMP\vcomp140d.dll

ユニバーサルCRT(リリースビルド用)

x86, x64それぞれ、以下の場所にあるファイルすべてです。ucrtbase.dllとそれに必要なDLLがすべて置いてあります。

  • C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\x64
  • C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\x86

念のため、前回同様にファイルの一覧も載せておきます。

  • api-ms-win-core-console-l1-1-0.dll
  • api-ms-win-core-datetime-l1-1-0.dll
  • api-ms-win-core-debug-l1-1-0.dll
  • api-ms-win-core-errorhandling-l1-1-0.dll
  • api-ms-win-core-file-l1-1-0.dll
  • api-ms-win-core-file-l1-2-0.dll
  • api-ms-win-core-file-l2-1-0.dll
  • api-ms-win-core-handle-l1-1-0.dll
  • api-ms-win-core-heap-l1-1-0.dll
  • api-ms-win-core-interlocked-l1-1-0.dll
  • api-ms-win-core-libraryloader-l1-1-0.dll
  • api-ms-win-core-localization-l1-2-0.dll
  • api-ms-win-core-memory-l1-1-0.dll
  • api-ms-win-core-namedpipe-l1-1-0.dll
  • api-ms-win-core-processenvironment-l1-1-0.dll
  • api-ms-win-core-processthreads-l1-1-0.dll
  • api-ms-win-core-processthreads-l1-1-1.dll
  • api-ms-win-core-profile-l1-1-0.dll
  • api-ms-win-core-rtlsupport-l1-1-0.dll
  • api-ms-win-core-string-l1-1-0.dll
  • api-ms-win-core-synch-l1-1-0.dll
  • api-ms-win-core-synch-l1-2-0.dll
  • api-ms-win-core-sysinfo-l1-1-0.dll
  • api-ms-win-core-timezone-l1-1-0.dll
  • api-ms-win-core-util-l1-1-0.dll
  • api-ms-win-crt-conio-l1-1-0.dll
  • api-ms-win-crt-convert-l1-1-0.dll
  • api-ms-win-crt-environment-l1-1-0.dll
  • api-ms-win-crt-filesystem-l1-1-0.dll
  • api-ms-win-crt-heap-l1-1-0.dll
  • api-ms-win-crt-locale-l1-1-0.dll
  • api-ms-win-crt-math-l1-1-0.dll
  • api-ms-win-crt-multibyte-l1-1-0.dll
  • api-ms-win-crt-private-l1-1-0.dll
  • api-ms-win-crt-process-l1-1-0.dll
  • api-ms-win-crt-runtime-l1-1-0.dll
  • api-ms-win-crt-stdio-l1-1-0.dll
  • api-ms-win-crt-string-l1-1-0.dll
  • api-ms-win-crt-time-l1-1-0.dll
  • api-ms-win-crt-utility-l1-1-0.dll
  • ucrtbase.dll

さてはて、ucrtbase.dllとVC++ランタイムの依存関係を見ても、ここにあるファイルすべてが必要なわけではなさそうに見えます。Windows XPに持っていって動かしてみましたが、やはりそうです。少し気になります。

ユニバーサルCRT(デバッグビルド用)

上記ユニバーサルCRT(リリースビルド用)で紹介した場所にあるファイルのうち、ucrbase.dll以外と、以下の場所にあるucrtbased.dllが必要となります。

  • C:\Program Files (x86)\Windows Kits\10\bin\x86\ucrt
  • C:\Program Files (x86)\Windows Kits\10\bin\x64\ucrt

むすび

Visual Studio 2015 RTMでは、パッケージングでミスしてC:\Program Files (x86)\Windows Kits配下のファイルが使えないという話でしたが、Update 1で直ったようです: Introducing the Universal CRT | Visual C++ Team Blogの“Distributing Software that uses the Universal CRT”の6.。そのため、今回の記事を書きました。

上記では、x86とx64のみを紹介しましたが、ほとんどにはarmも、一部にはarm64もあります。ただ、デスクトップアプリを作れるわけではないので、今のところ出番は無いと思います。

2016年4月26日追記: UCRTの更新KB311841とVC++ 2015 Update 2の再頒布パッケージの記載を追加しました。Nさん(コメント)より情報提供いただきました。ありがとうございます。

この記事のカテゴリ

  • ⇒ UCRTをアプリと同じフォルダに置く (VS2015 Update 1, 2)

Windowsでパケットキャプチャをするなら、WiresharkとWinPcapの組み合わせが最も一般的だと思います。しかし、Windowsにもパケットキャプチャの機能が標準搭載されています。それはnetsh trace startコマンドです。

Windows 7以降のnetsh traceコマンドでパケットをキャプチャする方法 – Eiji James Yoshidaの記録

ならば、自分のアプリからも使えないものかと思い、netsh trace startがやっていることを調べてみました。同じようにAPIを呼び出せば、同じようにパケットキャプチャできるはずという、至極単純な発想です。


というわけで作ったプログラムがこちらです。

gist: “netsh trace start caputre=yes traceFile=D:\packet.etl”の再現

Windows 8.1とServer 2012 R2、ともにx64で動作を見ました。これを実行すると、netsh trace start capture=yes traceFile=D:\capture.etlを実行したかのように、パケットキャプチャが始まります。停止するには、netsh trace stopを実行します。

出力ファイルの拡張子が.ETLなので、ETW APIを使っていることは、即座に分かりました。しかし、それだけでは足りず、最終的に以下の処理が必要でした。

  1. ndiscapサービス(デバイスドライバ)を起動する。
  2. ndiscap関係のレジストリを書き換える。
  3. INetCfgでms_ndiscapを有効化する
  4. ETWのStartTrace, EnableTraceEx2関数を呼び出し、Microsoft-Windows-Networking-CorrelationとMicrosoft-Windows-NDIS-PacketCaptureのトレースを開始する。
  5. netsh関係のレジストリを書き換える。

  6. さて、これはまだnetshと同様にファイルに書き出すだけです。次は、これをファイルに書き出さず、パケットの中身を取れるようにしたいと考えるのが自然な発想でしょう。

    実は、ETWのStartTraceでリアルタイムモードを指定してもパケットを取れることは確認しました。というわけで、次は、WinPcap.sys抜きでWinPcap.dllを動かせないか、試しているところです。

    この記事のカテゴリ

    • ⇒ netsh traceと同じようにパケットキャプチャーするプログラムを作った
    • ⇒ netsh traceと同じようにパケットキャプチャーするプログラムを作った

マクロだと思っていませんか?実は、結構前からインライン関数なんです。

MSDNライブラリの該当ページのタイトルはHRESULT_FROM_WIN32 macro (COM)で、マクロのままです。しかし、その中には「こう定義されている」と、C/C++の場合はインライン関数として定義される箇所がばっちり抜粋されています。

#define __HRESULT_FROM_WIN32(x) ((HRESULT)(x) <= 0 ? ((HRESULT)(x)) : ((HRESULT) (((x) & 0x0000FFFF) | (FACILITY_WIN32 << 16) | 0x80000000)))
 
#if !defined(_HRESULT_DEFINED) && !defined(__midl)
#define _HRESULT_DEFINED
typedef __success(return >= 0) long HRESULT;
#endif
 
#ifndef __midl
FORCEINLINE HRESULT HRESULT_FROM_WIN32(unsigned long x) { return (HRESULT)(x) <= 0 ? (HRESULT)(x) : (HRESULT) (((x) & 0x0000FFFF) | (FACILITY_WIN32 << 16) | 0x80000000);}
#else
#define HRESULT_FROM_WIN32(x) __HRESULT_FROM_WIN32(x)
#endif

インライン関数として定義されることで、HRESULT_FROM_WIN32(GetLastError())というコードを書いても心配ありません。GetLastError()は1回しか評価されません。

逆に、マクロでないと困るときには、__HRESULT_FROM_WIN32を使います。こちらはマクロです。たとえば、switch caseのように、定数式が要求される場面です。

#include <iostream>
#include <windows.h>
#include <shlwapi.h>
#include <comdef.h>
 
int main()
{
  IStreamPtr s;
  HRESULT hr = SHCreateStreamOnFileEx(
    LR"(C:\)",
    STGM_READ,
    0,
    FALSE,
    nullptr,
    &s);
  switch (hr)
  {
  case S_OK:
    std::cout << "S_OK" << std::endl;
    break;
  case __HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND):
    std::cout << "ERROR_PATH_NOT_FOUND" << std::endl;
    break;
  default:
    std::cout << std::hex << hr << std::endl;
  }
}

軽く調べたところ、Visual Studio 2008付属のWindows SDK 6.0Aでは、このインライン関数による定義でした。一方、Visual Studio 2005付属のPlatfrom SDKでは、まだマクロによる定義がデフォルトでした。ただし、INLINE_HRESULT_FROM_WIN32を定義すると、インライン関数で定義されるようになっていました。

この記事のカテゴリ

  • ⇒ HRESULT_FROM_WIN32は関数である
  • ⇒ HRESULT_FROM_WIN32は関数である

Boost.Variantにpolymorphic_getという関数が追加されていることに気が付きました。リリースノートを見ると1.56.0で追加されたようです(1.56.0 日本語1.56.0 英語)。

boost::getとよく似たこの関数は、しかしboost::getと異なり、基底クラスの型を指定しても値を取り出せるというものです。

#include <iostream>
#include <boost/variant/variant.hpp>
#include <boost/variant/polymorphic_get.hpp>
 
struct Base
{
  Base(std::string s) : name(std::move(s)) {}
  std::string name;
};
struct ImplA : Base
{
  ImplA(std::string s) : Base(std::move(s)) {}
};
 
int main()
{
  boost::variant<int, ImplA> x = ImplA("mikan");
  std::cout << boost::polymorphic_get<ImplA>(x).name << std::endl;
  std::cout << boost::polymorphic_get<Base>(x).name << std::endl;
}

このプログラムを実行すると、2回目のBaseを指定したboost::polymorphic_get関数の呼び出しも成功します。そのため、次のような出力になります。

mikan
mikan

boost::variantを参照で受け取るものと、ポインタで受け取るものが用意されている点は、boost::getと同じです。すなわち、指定した型で値を取り出せない場合、参照だと例外が投げられ、ポインタだとnullptrが返されます。


これを使って、多態的なコードが書けます。

#include <iostream>
#include <ctime>
#include <boost/variant/variant.hpp>
#include <boost/variant/polymorphic_get.hpp>
 
struct Base
{
  virtual void Do() = 0;
};
struct ImplA : Base
{
  void Do() override { std::cout << "ImplA" << std::endl; }
};
struct ImplB : Base
{
  void Do() override { std::cout << "ImplB" << std::endl; }
};
 
int main()
{
  boost::variant<ImplA, ImplB> obj;
  if (std::time(nullptr) % 2 == 1) // ランダムに振り分ける意図
  {
    obj = ImplA();
  }
  else
  {
    obj = ImplB();
  }
  boost::polymorphic_get<Base>(obj).Do();
}

main関数内の変数objは、boost::variantによりImplAまたはImplBの型のオブジェクトを保持します。ImplAとImplB、ともにBase型から派生させています。するとどうでしょう、boost::polymorphic_get(obj)と書くと、「objの中身が何型か分からないけど、とにかくBase型(への参照)としてアクセスする」ということが実現できます。

ポインタを使った、このようなコードなら馴染みある形ではないでしょうか。それと似たようなことがBoost.Variantで書けたということになります。

int main()
{
  std::unique_ptr<Base> obj;
  if (std::time(nullptr) % 2 == 1) // ランダムに振り分ける意図
  {
    obj = std::make_unique<ImplA>();
  }
  else
  {
    obj = std::make_unique<ImplB>();
  }
  obj->Do();
}

先のようなBoost.Variantを使うやり方は、ポインタを使う場合と比べ、次のような点が異なります。

  • 動的メモリ確保が不要。ただし、すべての型のレイアウトが分かっていないといけない(pimplイディオムのように、実装詳細として隠せない)。
  • boost::variantのテンプレート実引数で、使用が想定される型をすべて書かないといけない。コンパイル時に決める必要がある。

私は、動的メモリ確保が不要な点で、これは便利だと思いました。


もちろん、boost::polymorphic_get関数がないとこういう多態的なコードが書けない、というわけではありません。そもそも、私は初めこういうコードを書こうとしていました。

int main()
{
  boost::variant<ImplA, ImplB> obj;
  Base* p = nullptr;
  if (std::time(nullptr) % 2 == 1)
  {
    obj = ImplA();
    p = boost::get<ImplA>(&obj);
  }
  else
  {
    obj = ImplB();
    p = boost::get<ImplB>(&obj);
  }
  p->Do();
}

ふとBoost.Variantのリファレンスを見たら、boost::polymorphic_getを見つけたという次第です。

2016年2月29日追記: この方法は、基底型(上記ではBase型)へのポインタや参照を取り出し、virutal関数による多態性を利用するコードに混ぜて使えるのが特徴です。その必要が無い(Boost.Variantで統一できる)なら、virtual関数を作らず、apply_visitor関数とテンプレート(またはジェネリックラムダ)を使う方法もあります。

Windows Vistaで導入されたMandatory Integrity Control (MIC)を活用すれば、管理者ユーザーでも削除できないフォルダやファイルを作れます。実際に試してみました。

昇格した管理者ユーザーアカウントのプロセスはILが高なので、それよりさらに上のレベルであるシステムを設定してみたというだけのことです。

// il-system.cpp
 
#define UNICODE
#define _UNICODE
 
#include <iostream>
#include <cassert>
 
#include <windows.h>
#include <aclapi.h>
 
template<typename... Args>
SE_SID MakeSid(
  BYTE revision,
  SID_IDENTIFIER_AUTHORITY identifierAuthority,
  Args&&... args)
{
  SE_SID sid{ revision, sizeof...(args), identifierAuthority };
  DWORD subAuthority[] = { static_cast<DWORD>(args)... };
  std::copy(
    std::begin(subAuthority), std::end(subAuthority),
    sid.Sid.SubAuthority);
  return sid;
}
 
int main()
{
  auto ILSystemSid = MakeSid(S
    ID_REVISION,
    SECURITY_MANDATORY_LABEL_AUTHORITY,
    SECURITY_MANDATORY_SYSTEM_RID);
 
  constexpr auto ILSystemSidSize = sizeof (SID);
  assert(IsValidSid(&ILSystemSid));
  assert(ILSystemSidSize == GetLengthSid(&ILSystemSid));
 
  constexpr DWORD dwSaclSize =
    sizeof (ACL)
    + sizeof (SYSTEM_MANDATORY_LABEL_ACE)
    - sizeof (SYSTEM_MANDATORY_LABEL_ACE::SidStart)
    + ILSystemSidSize;
#if 1
  union
  {
    char saclBuffer[dwSaclSize];
    struct
    {
      ACL sacl;
      SYSTEM_MANDATORY_LABEL_ACE mandatoryLavelAce;
    };
  };
 
  sacl.AclRevision = ACL_REVISION;
  sacl.AclSize = sizeof saclBuffer;
  sacl.AceCount = 1;
  mandatoryLavelAce.Header.AceType = SYSTEM_MANDATORY_LABEL_ACE_TYPE;
  mandatoryLavelAce.Header.AceFlags =
    CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE;
  mandatoryLavelAce.Header.AceSize =
    (sizeof mandatoryLavelAce)
    - sizeof (SYSTEM_MANDATORY_LABEL_ACE::SidStart)
    + ILSystemSidSize;
  mandatoryLavelAce.Mask = SYSTEM_MANDATORY_LABEL_NO_WRITE_UP;
  memcpy(&mandatoryLavelAce.SidStart, &ILSystemSid, ILSystemSidSize);
  assert(IsValidAcl(&sacl));
#else
  InitializeAcl(&sacl, dwSaclSize, ACL_REVISION);
  if (!AddMandatoryAce(
    &sacl, ACL_REVISION,
    CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE,
    SYSTEM_MANDATORY_LABEL_NO_WRITE_UP,
    &ILSystemSid))
  {
    auto e = GetLastError();
    std::cout << "AddMandatoryAce: " << e << std::endl;
    return 0;
  }
#endif
 
  auto result = SetNamedSecurityInfo(
    LR"(D:\TEMP\t)",
    SE_FILE_OBJECT,
    LABEL_SECURITY_INFORMATION,
    nullptr,
    nullptr,
    nullptr,
    &sacl);
  if (result != ERROR_SUCCESS)
  {
    std::cout << "SetNamedSecurityInfo: " << result << std::endl;
  }
}

sizeof (SYSTEM_MANDATORY_LABEL_ACE::SidStart)は、C++11で追加された構文です。少し前のVisual C++だと対応していないので、そういうときは適宜修正してください。あるいは、最新のコンパイラを使ってください。あと、無名unionの中に無名structを置くことは、Visual C++の独自拡張のような気がします。ほかのコンパイラを使っているかた、ごめんなさい。

#if 1#else側は、どちらも同じであることを意図しています。どちらも実際に動かして、期待どおりに動作することを確認しました。

試し方はこうです。

  1. 上のプログラムをコンパイルしたものをil-system.exeとします。
  2. D:\TEMP\tというフォルダを作り(ソースコード内決め打ち!)、その中にファイルを作っておきます。
  3. ILがシステムな状態でil-system.exeを実行します。お手軽なのは、PsExecを使い、psexec -s il-system.exeで実行することだと思います。

すると、D:\TEMP\tは「管理者として実行」などで昇格したプロセスごときでは書き込み操作が拒否されるようになります。例えば、ファイルを追加したり、既に作成済みのファイルに追記したり、あるいは削除したりなどといった操作ができなくなります。

なお、削除するにはpsexec -s cmd /c rd /s D:\TEMP\tなどとすれば大丈夫です。

最後に、MSDNライブラリの関係する項目へのリンクを貼っておきます。


というわけで、管理者ユーザーはILが高なので、ILがシステムなオブジェクトなら操作できないことを試してみました、という話でした。

WindowsサービスなんかはみんなILがシステムです。ユーザーに触れられたくないファイルに、こういう設定をしたら良いのではないだろうかと思いました。もっとも、psexecのように、管理者(Administartors)は、そのWindowsサービスを追加・削除する権限を持っています。そのため、完全に防げるものではありません。

この記事のカテゴリ

  • ⇒ 管理者でも削除できないフォルダ・ファイルを作る
  • ⇒ 管理者でも削除できないフォルダ・ファイルを作る

LogonUserExExW関数を使ってみました。この関数のすごいところは、好きなグループを追加してアクセストークンを作成できるという点です。ただし、そういう使い方をするには特権SE_TCB_PRIVILEGEが必要です。

今までも同様のことはLsaLogonUserで可能だったはずですが、LogonUserExExWのほうが使い方が簡単です。LSAハンドルを開くなどの事前準備が不用だからです。

というわけで、これを使ってプロセスを起動するプログラムを書いてみました。本来ならUsersグループのLocalServiceアカウントに、Administratorsグループを追加しています。

// cl logon.cpp advapi32.lib userenv.lib
 
#define UNICODE
#define _UNICODE
#define WIN32_LEAN_AND_MEAN
#define _ATL_NO_AUTOMATIC_NAMESPACE
 
#include <iostream>
#include <locale>
#include <memory>
#include <windows.h>
#include <userenv.h>
#include <atlbase.h>
#include <atlutil.h>
 
extern "C"
BOOL WINAPI LogonUserExExW(
  _In_ LPCWSTR lpszUsername,
  _In_opt_ LPCWSTR lpszDomain,
  _In_opt_ LPCWSTR lpszPassword,
  _In_ DWORD dwLogonType,
  _In_ DWORD dwLogonProvider,
  _In_opt_ PTOKEN_GROUPS pTokenGroups,
  _Outptr_opt_ PHANDLE phToken,
  _Outptr_opt_ PSID *ppLogonSid,
  _Outptr_opt_result_bytebuffer_all_(*pdwProfileLength)
    PVOID *ppProfileBuffer,
  _Out_opt_ LPDWORD pdwProfileLength,
  _Out_opt_ PQUOTA_LIMITS pQuotaLimits);
 
struct env_deleter
{
  void operator()(void* env) const throw()
  {
    DestroyEnvironmentBlock(env);
  }
};
 
std::unique_ptr<void, env_deleter> create_environment_block(
  _In_ HANDLE hToken, BOOL inherit)
{
  void* tmp;
  if (CreateEnvironmentBlock(&tmp, hToken, inherit))
  {
    return std::unique_ptr<void, env_deleter>(tmp);
  }
  else
  {
    return nullptr;
  }
}
 
HRESULT OutputErrorMessgae(_In_ PCWSTR functionName, HRESULT hr)
{
  std::wclog << functionName << '\n';
  std::wclog << std::showbase << std::hex << hr << '\n';
  std::wclog << ATL::AtlGetErrorDescription(hr).GetString() << std::endl;
  return hr;
}
 
HRESULT OutputLastError(_In_ PCWSTR functionName)
{
  auto hr = ATL::AtlHresultFromLastError();
  return OutputErrorMessgae(functionName, hr);
}
 
template<typename... Args>
SE_SID MakeSid(
  BYTE revision,
  SID_IDENTIFIER_AUTHORITY identifierAuthority,
  Args&&... args)
{
  SE_SID sid{ revision, sizeof...(args), identifierAuthority };
  DWORD subAuthority[] = { static_cast<DWORD>(args)... };
  std::copy(
    std::begin(subAuthority), std::end(subAuthority),
    sid.Sid.SubAuthority);
  return sid;
}
 
int main()
{
  std::wclog.imbue(std::locale(std::locale::classic(), "", std::locale::ctype));
 
  ATL::CHandle hTokenBase;
  if (!LogonUser(
    L"LocalService",
    L"NT AUTHORITY",
    nullptr,
    LOGON32_LOGON_SERVICE,
    LOGON32_PROVIDER_DEFAULT,
    &hTokenBase.m_h))
  {
    return OutputLastError(L"LogonUserExExW (hTokenBase)");
  }
 
  DWORD tokenGroupsBufferSize;
  auto result = GetTokenInformation(
    hTokenBase,
    TokenGroups,
    nullptr,
    0,
    &tokenGroupsBufferSize);
  auto e = GetLastError();
  if (result == FALSE && e != ERROR_INSUFFICIENT_BUFFER)
  {
    return OutputErrorMessgae(
      L"GetTokenInformation (buffer size)",
      HRESULT_FROM_WIN32(e));
  }
 
  auto tokenGroupsBuffer = std::make_unique<BYTE[]>(tokenGroupsBufferSize);
  if (!GetTokenInformation(
    hTokenBase,
    TokenGroups,
    tokenGroupsBuffer.get(),
    tokenGroupsBufferSize,
    &tokenGroupsBufferSize))
  {
    return OutputLastError(L"GetTokenInformation");
  }
 
  auto tgBase = reinterpret_cast<const TOKEN_GROUPS*>(
    tokenGroupsBuffer.get());
 
  auto sidAdministrators = MakeSid(
    SID_REVISION, SECURITY_NT_AUTHORITY,
    SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
 
  std::size_t bufferSize = sizeof(TOKEN_GROUPS)
    + (tgBase->GroupCount - 1 + 1) * sizeof tgBase->Groups[0];
  auto buffer = std::make_unique<BYTE[]>(bufferSize);
  memcpy(buffer.get(), tgBase, bufferSize);
  auto tg = reinterpret_cast<TOKEN_GROUPS*>(buffer.get());
  tg->GroupCount++;
  tg->Groups[tgBase->GroupCount].Sid = &sidAdministrators;
  tg->Groups[tgBase->GroupCount].Attributes =
    SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED;
 
  ATL::CHandle hToken;
  PSID logonSid;
  void* profileBuffer;
  DWORD profileLength;
  QUOTA_LIMITS quotaLimits;
  if (!LogonUserExExW(
    L"LocalService",
    L"NT AUTHORITY",
    nullptr,
    LOGON32_LOGON_SERVICE,
    LOGON32_PROVIDER_DEFAULT,
    tg,
    &hToken.m_h,
    &logonSid, &profileBuffer, &profileLength, &quotaLimits))
  {
    return OutputLastError(L"LogonUserExExW");
  }
 
  STARTUPINFO si{ sizeof si };
  PROCESS_INFORMATION pi{};
#if 1
  if (!CreateProcessWithTokenW(
    hToken, LOGON_WITH_PROFILE,
    LR"(T:\hello.exe)", nullptr,
    0,
    nullptr, nullptr, &si, &pi))
  {
    return OutputLastError(L"CreateProcessWithTokenW");
  }
#else
  auto env = create_environment_block(hToken, FALSE);
  if (!CreateProcessAsUser(
    hToken,
    LR"(T:\hello.exe)", nullptr,
    nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT,
    env.get(), nullptr, &si, &pi))
  {
    return OutputLastError(L"CreateProcessAsUser");
  }
#endif
  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread);
}

CreateProcessWithTokenWとCreateProcessAsUser、どちらでもうまくいきました。次に、そこで指定しているhello.exeのソースコードを載せます。デスクトップにアクセスするための権限がないため、ファイルに出力するようにしています。

#include <fstream>
#include <windows.h>
 
int WINAPI WinMain(HINSTANCE, HINSTANCE, char*, int)
{
  std::ofstream s(R"(T:\hello.txt)");
  s << "hello world" << std::endl;
 
  Sleep(10000); // プロセスの存在を確認するため
}

これを以下のように起動します。LocalSystemアカウントで実行するため、PsExecを使います。

T:\>psexec -sid T:\logon.exe

これで起動したhello.exeをProcess Explorerで見てみます。

LocalServiceアカウントにAdministratorsグループを与えてプロセスを起動したところをProcess Explorerで表示させた様子

このように、BUILTIN\Administratorsが追加されています。それに伴い、特権も増えているようです。

書いてみて気付いた注意事項です。

  • MSDNライブラリには「GetProcAddressして使いなさい」と書いてありますが、その必要はありません。インポートライブラリには情報が入っているので、自分でプロトタイプ宣言すれば使えます。これに限らず、最近のWindows SDKはそんな感じです。
  • 最初にLogonUserを呼び出している理由は、ログオンSIDを入手するためというのが最も大きいです。LogonUserExExWにTOKEN_GROUPSを渡す場合、ログオンSIDが含まれていないと、失敗になるようです。通常はOpenProcessTokenで自分自身のアクセストークンを取り出せば良いのでしょうが、LocalSystemアカウントのアクセストークンはログオンSIDが含まれないので、今回はLogonUserを使うことにしました。

もちろん使いどころは限られますが、(便利という意味で)面白いAPI関数です。

この記事のカテゴリ

  • ⇒ LogonUserExExWを使ってみた
  • ⇒ LogonUserExExWを使ってみた

以前、こんなコードを書きました(アクセス許可の指定でリモートアクセスを禁じる)。SID型は可変長のため、十分な大きさのchar配列との共用体を作っているというものです。

union
{
  char buffer[SECURITY_MAX_SID_SIZE];
  SID sid;
} networkLogon = {};
DWORD sidNetworkSize = sizeof networkLogon;
if (!CreateWellKnownSid(
  WinNetworkSid, nullptr, &networkLogon.sid, &sidNetworkSize))
{
  ATL::AtlThrowLastWin32();
}

Windows 10のSDKから、うってつけのものが追加されました。winnt.hのSE_SIDです。

typedef union _SE_SID {
    SID Sid;
    BYTE  Buffer[SECURITY_MAX_SID_SIZE];
} SE_SID, *PSE_SID;

自分が書いたものと同じような共用体です。これは感激です。もう自分でこんな共用体を作る必要がなくなりました。

これを使ってコードを書き直すとこうなります。

SE_SID networkLogon = {};
DWORD sidNetworkSize = sizeof networkLogon;
if (!CreateWellKnownSid(
  WinNetworkSid, nullptr, &networkLogon.Sid, &sidNetworkSize))
{
  ATL::AtlThrowLastWin32();
}

ほかの例です。Administratorsグループ (S-1-5-32-544)のSIDを作るコードです。

SE_SID sid = { SID_REVISION, 2, SECURITY_NT_AUTHORITY };
sid.Sid.SubAuthority[0] = SECURITY_BUILTIN_DOMAIN_RID;
sid.Sid.SubAuthority[1] = DOMAIN_ALIAS_RID_ADMINS;

こんな風にローカル変数でSIDオブジェクトを作ること、私はよくやります。AllocateAndInitializeSid関数を使う例をよく見かけますが、それだとFreeSid関数で解放する必要があって、面倒だなあと思うのです。

この記事のカテゴリ

  • ⇒ Windows 10 SDKで加わったSID型への補助

次ページへ »