前回(ドライブ文字を使わないパス指定)の少し変わったパス文字列、<filesystem>のpathだとどうなるのか、気になって試してみました。

#include <filesystem>
#include <iostream>
 
int main()
{
  using namespace std::experimental::filesystem;
 
  path x(LR"(\\?\Volume{e96621af-7084-11e3-80b0-XXXXXXXXXXXX}\Windows\system.ini)");
  std::wcout << x.root_name() << std::endl;
}

root_nameは、ドライブ文字を含むパスであれば、ドライブ文字の部分を返すメンバー関数です。

これをVisual C++ 2017 (15.3)でコンパイルします。実行結果はこうなりました。

\\?

これはつまり、UNCのため、バックスラッシュ2つで始まる部分はroot name扱いということなのだと思います。Volume{……}までroot name扱いにはなりませんでした。

この記事のカテゴリ

  • ⇒ 変わったパスとVC++のpathクラス
  • ⇒ 変わったパスとVC++のpathクラス

Windowsの絶対パスと言えば、C:\Windowsのようなドライブ文字で始まる表記を思い浮かべることと思います。しかし、ドライブ(ボリューム)を指定する表記はほかにもあります。

ボリュームGUID
各ボリュームに対するGUIDです。mountvolコマンドで確認できます。
ディスク番号・パーティション番号による表記
正式な名称は分かりませんが、Harddisk0Partition1のような文字列です。それぞれの番号は、diskpartコマンドやWMIなどで確認できるはずです。CDドライブなどでは、CdRom0などとなるようです。

いずれも、SysinternalsのWinObjで確認すると、C:などのドライブ文字と同じ\GLOBAL??名前空間に存在しています。つまり、C:などのドライブ文字と取っ替えて使えるはずと予想するわけです。実際、次のプログラム、3種類のどのパスの表記でも同じように動きます。

#define UNICODE
#define _UNICODE
 
#include <iostream>
#include <fstream>
#include <string>
#include <windows.h>
 
int main()
{
#if 1
  WCHAR path[] = LR"(\\?\Volume{e96621af-7084-11e3-80b0-XXXXXXXXXXXX}\Windows\system.ini)";
#elif 1
  WCHAR path[] = LR"(\\?\Harddisk0Partition2\Windows\system.ini)";
#else
  WCHAR path[] = LR"(C:\Windows\system.ini)";
#endif
  std::ifstream ifs(path);
  if (ifs)
  {
    std::cout << ifs.rdbuf() << std::endl;
  }
}

すぐに思い浮かぶ用途は、ドライブ文字を割り当てていないボリューム(ドライブ)へのアクセスですね。というか、ドライブ文字を割り当てていないボリュームで、GUID表記がなされている事例を見かけて、この存在を知りました。

この記事のカテゴリ

  • ⇒ ドライブ文字を使わないパス指定
  • ⇒ ドライブ文字を使わないパス指定

C++ filesystemには、パーミッションを取得・設定する機能があります。そんなわけで、たとえばこれをその辺のUnix系システムでg++を使ってコンパイル・実行すれば、755という出力が得られるはずです。

#include <iostream>
#include <experimental/filesystem>
 
int main()
{
  using namespace std::experimental::filesystem;
 
  auto perm = status("/bin/sh").permissions();
  std::cout << std::oct << static_cast<int>(perm) << std::endl;
}

さて、これWindowsだとどうなるのだろう?と思うわけです。試してみました。


これをVisual C++ 2015でコンパイル・実行すると、777という出力になりました。

#include <iostream>
#include <experimental/filesystem>
 
int main()
{
  using namespace std::experimental::filesystem;
 
  auto perm = status(R"(C:\Windows\win.ini)").permissions();
  std::cout << std::oct << static_cast<int>(perm) << std::endl;
}

無条件に777となるのでしょうか?というわけで、Visual C++の実装を眺めてみます。探したところ、VC++ 2015やVC++ 2017ではsrc/src/filesys.hにありました。

  // FILE STATUS FUNCTIONS
_FS_DLL _File_type __CLRCALL_PURE_OR_CDECL _Stat(const TCHAR *_Fname, _Perms *_Pmode)
{  // get file status
WIN32_FILE_ATTRIBUTE_DATA _Data;
 
if (TFUN(GetFileAttributesEx)(_Fname, GetFileExInfoStandard, &_Data))
  {  // get file type and return permissions
  if (_Pmode != 0)
    *_Pmode = _Data.dwFileAttributes & FILE_ATTRIBUTE_READONLY
      ? READONLY_PERMS : perms::all;
  return (_Map_mode(_Data.dwFileAttributes));
  }
else

読み取り専用属性が付いていたら555、そうでなければ777という実装でした。

ちなみに、Boost.Fileystemのほうはどうなっているかというと、実行ファイル・バッチファイルの場合のみ実行ビットを立てる実装となっているようです: filesystem/operations.cpp at boost-1.64.0 · boostorg/filesystem(1805行目からのシンボリックリンクの判定も、VC++と異なります)。

Windowsのファイルシステムのアクセス制御はUnix系のパーミッションとは異なる仕組みである以上、なんらか適当な数値をでっち上げるしかありません。そんなわけなので、どうなっているのか調べてみました。

この記事のカテゴリ

  • ⇒ VC++ filesystemのパーミッション取得の実装
  • ⇒ VC++ filesystemのパーミッション取得の実装

CreateProcess時、PROC_THREAD_ATTRIBUTE_MITIGATION_POLICYでプロセスに対する一部の挙動を変更することが可能です。その中にWindows 10のいつからか、DLLの検索パスについての定数が増えています。前々から気になっていたので、試してみました。

その定数は、UpdateProcThreadAttributeに記載のある以下です。

  • PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_DEFER
  • PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON
  • PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_OFF

まず、題材となるプログラムを作ります。これをコンパイルして、app.exeを作ります。iphlpapi.dllを何も考えずにLoadLibrayしているのは、もちろんわざとです。効果を確認するため、セキュリティ上問題のある書き方にしています。

// cl /MT /EHsc app.cpp /link /subsystem:console,5.01
 
#define UNICODE
#define _UNICODE
 
#include <iostream>
#include <windows.h>
#include <iphlpapi.h>
 
int main()
{
  auto hmod = LoadLibrary(L"iphlpapi.dll");
  if (auto pfn = reinterpret_cast<decltype(GetTcpTable2)*>(
    GetProcAddress(hmod, "GetTcpTable2")))
  {
    ULONG size = 0;
    pfn(nullptr, &size, 0);
    std::cout << size << std::endl;
  }
}

では、偽のiphlpapi.dllを作りましょう。次のソースコードをコンパイルします。

// cl iphlpapi.cpp user32.lib /LD
 
#define UNICODE
#define _UNICODE
#include <windows.h>
 
#pragma comment(linker, "/export:GetTcpTable2=_GetTcpTable2@12")
 
extern "C" __declspec(dllexport)
ULONG WINAPI GetTcpTable2(void*, ULONG*, BOOL)
{
  MessageBox(nullptr, L"やっほー", L"evil bit=1", MB_OK);
  return ERROR_INVALID_PARAMETER;
}

さっきのapp.exeと同じフォルダに、偽iphlpapi.dllを置き、app.exeを実行します。すると、メッセージボックスが表示されます。

さて、例のフラグ…_ALWAYS_ONを試します。CreateProcessを呼び出すlauncher.exeを作ります。

// cl /MT launcher.cpp
 
#define UNICODE
#define _UNICODE
 
#include <iostream>
#include <memory>
#include <cstdint>
#include <cstdlib>
#include <windows.h>
 
int main()
{
  WCHAR cmdLine[] = L"app.exe";
 
  SIZE_T bufSize = 0;
  if (!InitializeProcThreadAttributeList(nullptr, 1, 0, &bufSize)
    && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
  {
    std::quick_exit(1);
  }
  auto buf = std::make_unique<BYTE[]>(bufSize);
  auto attrList = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(buf.get());
  if (!InitializeProcThreadAttributeList(attrList, 1, 0, &bufSize))
  {
    std::quick_exit(1);
  }
  std::uint64_t value = PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON;
  if (!UpdateProcThreadAttribute(
    attrList, 0, PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY,
    &value, sizeof value, nullptr, nullptr))
  {
    std::quick_exit(1);
  }
  STARTUPINFOEX si{};
  si.StartupInfo.cb = sizeof si;
  si.lpAttributeList = attrList;
  PROCESS_INFORMATION pi{};
  if (CreateProcess(nullptr, cmdLine, nullptr, nullptr, FALSE,
    EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr, &si.StartupInfo, &pi))
  {
    WaitForSingleObject(pi.hProcess, INFINITE);
  }
  // DeleteProcThreadAttributeListなどは省略
}

これをコンパイルして実行すると、見事メッセージボックスは表示されませんでした。

なお、定数…_DEFERを指定して、app.exe内でPreferSystem32Images = 1を指定してSetProcessMitigationPolicyを呼び出せば同様にsystem32優先になると予想していたのですが、これはうまくいきませんでした。偽iphlpapi.dllが読み込まれる結果となりました。やり方にミスがあったのでしょうか。

この方法、ご覧のとおりCreateProcessを呼び出す側での対処です。アプリ側に手を入れずに効果を発揮させることが可能で、しかもアプリ(app.exe)側のプロセス内で無効化できないはずという点で強力です。逆に、JVNTA#91240916: Windows アプリケーションによる DLL 読み込みやコマンド実行に関する問題UNLHA32.DLLで作成された自己解凍書庫における任意のDLL読み込みに関する脆弱性)への対処に使うのは大変手間がかかるでしょう。

というわけで、DLL検索パスの指定方法がまた1つ増えました。

この記事のカテゴリ

  • ⇒ System32優先でDLLを探す指定を試してみた
  • ⇒ System32優先でDLLを探す指定を試してみた

以前より、Visual C++のファイルストリーム(basic_fstream, basic_ifstream, basic_ofstream)には、独自拡張が存在しました。ファイルパスをワイド文字列で指定できる実引数のコンストラクタとopenメンバー関数の多重定義です。理由は、WindowsのAPIがUTF-16 (wchar_t)でファイルパスを扱うためです。

さて、C++17でこれが標準に取り込まれることに気付きました。こうなったのは、std::filesystem::pathの影響です。代表として、C++17のbasic_fstreamのコンストラクタを見てみます。

basic_fstream(const char*, ios_base::openmode);
basic_fstream(const string&, ios_base::openmode);
// C++17で追加。ただし、value_typeがchar以外の場合のみ。
basic_fstream(const filesystem::path::value_type*, ios_base::openmode);
// C++17で追加。
basic_fstream(const filesystem::path&, ios_base::openmode);

多重定義が2つ追加されています。同様のものが、openメンバー関数にも、basic_ifstreamやbasic_ofstreamにも、basic_filebuf::openにも追加されています。

前回(filesystem::pathの文字の型)書いたように、filesystem::path::value_typeというのはpathクラス内で用いている文字型です。すなわち、Windows環境ではwchar_tになると考えています。

するとどうでしょう。今まで独自拡張として定義されてきた多重定義が、そのままconst filesystem::path::value_type*版に当てはまっています。

というわけで、このままいけば「ワイド文字列(const wchar_t*)でファイルパスをファイルストリームの実引数に渡す」ということがVC++の独自拡張ではなくなると考えています。

#include <fstream>
#include <iostream>
#include <string>
 
int main()
{
  // あるいはL"C:\\Windows\\win.ini"でも可
  std::ifstream is(LR"(C:\Windows\win.ini)");
  std::string s;
  std::getline(is, s);
  std::cout << s << std::endl;
}

こういうわけなので、この手のコードがUnix系でも使えるようになるという話ではありません。逆に言えば、MinGW (g++, clang++)やC++ Builderなど、Windowsターゲットなら期待できるのではないでしょうかという具合です。

C++17 filesystemが実装された暁には、std::filesystem::pathクラスオブジェクトでファイルパスを扱うことをおすすめします。が、今回はpathクラスを直接使わない場合の話でした。

この記事のカテゴリ

  • ⇒ ワイド文字列でファイル名を指定する機能がC++標準に入るようだ
  • ⇒ ワイド文字列でファイル名を指定する機能がC++標準に入るようだ

以下のpathクラスについてです。これらが内部で使用する型は環境によって異なるという話です。

  • C++17で標準ライブラリに入るstd::filesystem::path
  • Boost.Filesystem v3のboost::filesystem::path

これらpathクラスは、内部では文字列でデータを保有しています。この文字列は、実行環境のファイルシステムでネイティブに使用されるエンコーディングとなります。そのため、実行環境によって、使用する文字の型も異なります。Boostの場合、以下のようになっています。

  • Windows環境では、wchar_t
  • POSIX環境とその他の環境では、char

C++17では、もちろんそのような直接的な規定はありません。ただ、同様にWindowsでwchar_t、POSIX環境でcharとなるということが例として記述されています (N4659 30.10.8 Class [fs.class.path]の最後のexample)。

これに関連する型とメンバー関数として、以下のものがあります。

value_type
上記の文字型です。
string_type
basic_string<value_type>型です。
メンバー関数 const string_type& native() const noexcept;
pathオブジェクトが保有している文字列への参照を返します。
メンバー関数 const value_type* c_str() const noexcept;
pathオブジェクトが保有している文字列へのconstポインタを返します。native().c_str()と同じです。
メンバー関数 operator string_type() const;
暗黙型変換です。pathオブジェクトが保有している文字列のコピーを返します。Boost版にはありません。
メンバー関数 string_type::size_type size() const noexcept;
pathオブジェクトが保有している文字列の長さを返します。native().size()と同じです。C++17標準ライブラリ版にはありません。

たとえば、WindowsでCreateFileW関数の実引数にpathオブジェクトの値を渡そうという場合、新しい文字列オブジェクトを作って返すwstring()メンバー関数よりも、文字列オブジェクトを作らないc_str()メンバー関数のほうが効率的です。

なんでWindowsだけwchar_tなのかと言えば、OSのAPIがwchar_t(ただしUTF-16)だからですね。Boost Filesystem Version 3 Designで述べられています。そこでUTF-8固定などにならないのがBoostや標準ライブラリらしさと言えるかもしれません。

というわけで、pathクラスは、Windowsだけwchar_t文字列であるということを書きたかったのでした。

この記事のカテゴリ

  • ⇒ filesystem::pathの文字の型

WinHTTPにHTTP/2の対応が入ったようです。そこで、簡単にですが確かめてみました。

WinHTTPは、Windowsの汎用的なHTTPとWebSocketのAPIです。そんなわけで、自身でときどき使ったり、使っているアプリをたまに見かけたりします。

フラグをMSDNライブラリで見つけた

この前、WinHttpSetOption関数で指定するOption Flagsを見ていたところ、WINHTTP_PROTOCOL_FLAG_HTTP2というものが増えていることに気付きました。

WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL

Sets a DWORD bitmask of acceptable advanced HTTP versions. Supported on Windows 10, version 1607 and newer. Possible values are:

  • WINHTTP_PROTOCOL_FLAG_HTTP2 (0x1). Supported on Windows 10, version 1607 and newer

Legacy versions of HTTP (1.1 and prior) cannot be disabled using this option. The default is 0x0.

WINHTTP_PROTOCOL_FLAG_HTTP2の意味が書かれていませんが、どう考えてもHTTP/2を使うというフラグに違いありません。

使ってみる

というわけでさっそくコードを書いてみます。

#include <iostream>
#include <memory>
#include <string>
#include <cstdlib>
#include <windows.h>
#include <winhttp.h>
 
struct winhttp_deleter
{
  using pointer = HINTERNET;
  void operator()(_In_ HINTERNET h) const noexcept
  {
    WinHttpCloseHandle(h);
  }
};
 
using unique_hinternet = std::unique_ptr<HINTERNET, winhttp_deleter>;
 
int main()
{
  unique_hinternet session(WinHttpOpen(
    nullptr,
    WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY,
    nullptr,
    nullptr,
    0));
  if (session == nullptr)
  {
    std::quick_exit(1);
  }
  // ~~ ここから ~~
  DWORD protocolOption = WINHTTP_PROTOCOL_FLAG_HTTP2;
  if (!WinHttpSetOption(
    session.get(),
    WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL,
    &protocolOption,
    sizeof protocolOption))
  {
    std::wclog << "HTTP/2 is not supported." << std::endl;
  }
  // ~~ ここまで ~~
  unique_hinternet connect(WinHttpConnect(
    session.get(),
    L"www.yahoo.co.jp",
    INTERNET_DEFAULT_PORT,
    0));
  if (connect == nullptr)
  {
    std::quick_exit(1);
  }
  unique_hinternet request(WinHttpOpenRequest(
    connect.get(),
    L"HEAD",
    L"/",
    nullptr,
    WINHTTP_NO_REFERER,
    WINHTTP_DEFAULT_ACCEPT_TYPES,
    WINHTTP_FLAG_SECURE));
  if (connect == nullptr)
  {
    std::quick_exit(1);
  }
  if (!WinHttpSendRequest(
    request.get(),
    WINHTTP_NO_ADDITIONAL_HEADERS,
    0,
    WINHTTP_NO_REQUEST_DATA,
    0,
    0,
    0))
  {
    std::quick_exit(1);
  }
  if (!WinHttpReceiveResponse(request.get(), nullptr))
  {
    std::quick_exit(1);
  }
 
  WCHAR buffer[256];
  DWORD size = sizeof buffer;
  if (WinHttpQueryHeaders(
    request.get(),
    WINHTTP_QUERY_VERSION,
    WINHTTP_HEADER_NAME_BY_INDEX,
    buffer,
    &size,
    WINHTTP_NO_HEADER_INDEX))
  {
    std::wcout << buffer << std::endl;
  }
  std::quick_exit(0);
}

HTTP/2になっていることを確かめる(その1)

さて、どうやってHTTP/2であることを確認しよう?と少し悩みました。ウェブサイトでHTTP/2を使うとなれば、TLS (HTTPS)併用が事実上必須です。TLSで暗号化されていては、内容の確認は難しいです。

考えた結果「WiresharkでTLSの様子を見て、ALPNでh2が入っていたら良し」ということにしました。これがその結果です。

  • まず、ClientHelloにALPNのh2とhttp/1.1が入っています。

    WinHTTPによるTLSのCLIENT HELLO。ALPNでh2とhttp/1.1が指定されている。

  • そして、Server HelloにALPNでh2が指定されています。

    先の通信に対するSERVER HELLO。ALPNでh2が指定されている。

良さそうですね。h2になっているので、HTTP/2でしょう。

HTTP/2になっていることを確かめる(その2)

よくよく考えたら私はwww.activebasic.comのアクセスを見られます。というわけで、www.activebasic.comにアクセスさせて、アクセスログでHTTP/2であることを確認できることに気付きました。

分かりやすいように、User Agentを指定します。

--- a/http.cpp
+++ b/http.cpp
@@ -21,3 +21,3 @@
   unique_hinternet session(WinHttpOpen(
-    nullptr,
+    L"WinHTTP-Test-App/0.0",
     WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY,
@@ -43,3 +43,3 @@
     session.get(),
-    L"www.yahoo.co.jp",
+    L"www.activebasic.com",
     INTERNET_DEFAULT_PORT,

実際のアクセスログの該当行はこんなです。LTSVです。protocol:HTTP/2.0かつagent:WinHTTP-Test-App/0.0となっています。

domain:www.activebasic.com      host:153.203.0.68       server:160.16.87.113    user:-       time:30/Apr/2017:01:09:27 +0900 method:HEAD     path:/index.html        protocol:HTTP/2.0    status:200      size:7312       referer:-       agent:WinHTTP-Test-App/0.0   response_time:1207      cookie:-        set_cookie:-

WinHTTPが報告するバージョンは1.1

上記プログラムではWINHTTP_QUERY_VERSIONを使って、通信に使ったHTTPのバージョンを取得し、それをwcoutに出力するようにしています。その出力は、上記2つの確認時、いずれも以下のようになりました。

HTTP/1.1

びっくりです。実際には、HTTP/2で通信しているにもかかわらず、WINHTTP_QUERY_VERSIONでは、HTTP/1.1という文字列を返してきました。なお、WINHTTP_QUERY_RAW_HEADERS_CRLFでも同様にHTTP/1.1となっていました。


以上、Windows 10 1607で、WinHTTPにHTTP/2への対応が入っていることを見つけた話でした。

なお、ずっと前から関数WinHttpSetOption用の定数WINHTTP_OPTION_HTTP_VERSIONと構造体HTTP_VERSION_INFOがあるのに、新たな定数WINHTTP_OPTION_ENABLE_HTTP_PROTOCOLを追加してくるあたり、苦労が窺えます。

この記事のカテゴリ

  • ⇒ WinHTTPがHTTP/2に対応した (Windows 10 1607)
  • ⇒ WinHTTPがHTTP/2に対応した (Windows 10 1607)

表(?)の掲示板https://www.activebasic.com/forum/では、phpBBが使われています。そのphpBBを最新版3.2に更新しました。そのときやったことのまとめです。

phpBB 3.2ではスタイルprosilverが標準となっていますが、ここでは以前より使い続けているsubsilver2を引き続き使い続けています。以下の手順もsubsilver2に絞ったものとなっています。

  1. 公式サイトのphpBB • Download phpBB 3.2からphpBB-3.2.0.zipをダウンロード。
  2. 公式サイトのphpBB • Japanese – Contribution Detailsから1番新しいjapanese_1_0_5.zipをダウンロード。この中の画像ファイルを使用する。
  3. subsilver2スタイルの導入。phpBB • subsilver2 – Contribution Detailsから最新の3.1.10をダウンロードする。
  4. GitHub – msr-i386/phpBB3_language_jaのClone or downloadのDownload ZIPのリンクから、ZIPファイルをダウンロードする。
  5. 上記3ファイルを同じディレクトリに解凍する。
  6. ディレクトリstyles/prosilverを削除する。
  7. enの内容を元にphpBB3/styles/subsilver2/theme/ja/stylesheet.cssを作る。
  8. ここまでの手順で用意したファイルを使い、あとは通常どおりphpBB公式ウェブサイトにあるUser GudeのUpgrading from 3.1 to 3.2の手順に従って処理する。

ファイルを解凍する以降の手順について、実際には以下のコマンドを実行しました。ZIPファイルに対応しているbsdtarを使っています。

tar xf phpBB-3.2.0.zip
tar xf subsilver2_3.1.10.zip -C phpBB3/styles
tar xf phpBB3_language_ja-msrmod.zip --strip-components 2 -C phpBB3 --include=phpBB3_language_ja-msrmod/root/language
tar xf japanese_1_0_5.zip --strip-components 4 -C phpBB3/styles/subsilver2/theme
rm -r phpBB3/styles/prosilver
cp phpBB3/styles/subsilver2/theme/en/stylesheet.css phpBB3/styles/subsilver2/theme/ja

なお、実際には、phpBB3/styles/subsilver2/theme/jaに日本語フォントに関するfont-familyの指定を追加しています。それについては、直接https://www.activebasic.com/forum/styles/subsilver2/theme/ja/stylesheet.cssを見てください。

これを書くにあたって、prosivlerのほうも同様の手順でできそうかどうか試してみました。結果、画像ファイルの配置がsubsilver2と同じかどうか自信が持てなかったのでやめました。

phpBB 3.2はPHP 7系(7.0, 7.1)対応を謳っています。そこで、phpBB 3.2に上げることに決めました。というわけで、www.activebasic.comおよびdev.activebasic.comのサーバーは現在PHP 7.1が動いています。

2017年8月23日追記:japanese_1_0_5.zipをダウンロードする手順が抜けていたので、追加しました。なお、現在、phpBB 3.2.1やSubsilver 3.1.11が登場しています。今後はそちらを使うようにしましょう。

この記事のカテゴリ

  • ⇒ phpBB 3.2を日本語化して使う

私がWindows APIプログラミングを始めた頃から、MSDNライブラリのCreateWindow関数にはこんなことが書いてありました。従って、NT系ならここはNULLで良いのだと、最近まで信じていました。

hInstance
Windows 95/98:ウィンドウに関連付けられたモジュールのインスタンスハンドルを指定します。

Windows NT/2000:このパラメータは無視されます。


ところが、最近こんなのを見つけたんです: What is the HINSTANCE passed to CreateWindow and RegisterClass used for? – The Old New Thing

ウィンドウクラスはHINSTANCEとクラス名で識別されるのだということが書いてあります。あれあれ?この説明のとおりなら、HINSTANCE必要では?と思ったところ、コメント欄に決定打がありました。

Raymond Chen – MSFT says:
April 18, 2005 at 12:52 pm

CornedBee: That’s a doc bug; I’ve submitted a correction. All versions of Windows have always used the HINSTANCE to identify the class.

そして、最新情報に更新され続ける、英語のほうのMSDNライブラリを開けばこうです。

hInstance [in, optional]

Type: HINSTANCE

A handle to the instance of the module to be associated with the window.

いつの間にか書いてあることが変わっているではありませんか!

そんなわけで、CreateWindow関数(もちろんCreateWindowEx関数も)のHINSTANCEの実引数は昔も今もちゃんインスタンスハンドルを渡さないといけないということでした。

この記事のカテゴリ

  • ⇒ CreateWindow関数にはHINSTANCEが必要

手書きのリソーススクリプトなら先頭で、Visual C++プロジェクトでなら「読み取り専用ヘッダーファイル」で指定するヘッダーファイルの話の続きです。前回(リソース関係のヘッダーファイルまとめ)、いろんなリソーススクリプト用のヘッダーファイルを紹介しました。今回は、私の選択基準を書きます。

MFCを使用するWTLを使用するno<afxres.h>yesVC++ 2012以上を使用するno<atlres.h>yes<winres.h>yes<winresrc.h>no

  • アプリでMFCを使っているなら、MFCの<afxres.h>を使います。
  • アプリでWTLを使っているなら、WTLに入っている<atlres.h>を使います。
  • Visual C++ 2012およびそれ以降を使っているなら、<winres.h>を使います。
  • それ以外の場合、<winresrc.h>を使います。たとえば、Visual C++ 2010までだったり、MinGWなどだったりする場合です。

もっとも、これに従うと、ほとんどは<atlres>か<winres.h>という結論になります。私は新規で作るときにMFCを使うことはありませんし、最近のバージョンのVisual C++ばっかり使っているからです。

念のため追記しておきますが、これが絶対の基準であるとは思っていません。だから、「私の選択基準」という表現にしました。

この記事のカテゴリ

次ページへ »