以下の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++ばっかり使っているからです。

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

この記事のカテゴリ

私が知る、リソーススクリプト用のヘッダーファイルのまとめです。

afxres.h
MFCのヘッダーファイルです。winres.hをインクルードしています。リソースID用の定数を多数定義しています。その中にはAFX_IDW_TOOLBARなどAFXで始まるものもあれば、ID_FILE_NEWのように汎用的に使えそうなものもあります。
atlres.h
WTLのヘッダーファイルです。Visual Studioの付属品ではありません。winresrc.hをインクルードしています。afxres.hのように、リソースID用の定数を多数定義しています。ATL/WTL用にATL_IDW_TOOLBARなどATLで始まるものもあります。そのほか、VS_VERSION_INFOやIDC_STATIC、ID_FILE_NEWなど、winres.hやafxres.hと同等のものも定義されています。
winres.h
前回の記事に書いたとおり、以前はMFC扱いでしたが、今はWindows SDKに収録されています。winresrc.hをインクルードしています。定数VS_VERSION_INFOと定数IDC_STATICの定義があります。
winresrc.h, windows.h
Windows SDKあるいはPlatform SDKに昔からあります。どちらをインクルードしても同じです。リソースコンパイル時のwindows.hは、winresrc.hをインクルードするようになっているためです。

以下、細々とした話です。

  • Visual Studioのリソースエディタでダイアログにスタティックコントロールを設置すると、リソースIDがIDC_STATICになります。そのため、Visual Studioでデスクトップアプリを作るなら、windows.hやwinresrc.hでは足りません。それ以外を使用するのが良いでしょう。
  • 最近のVisual Studioでは、デフォルトのオプションだとMFCが入りません(C++自体もそうですけど)。MFCが入っていないということはafxres.hもありません。アプリ自体でMFCを使っていない場合、afxres.h以外を使うのがおすすめです。
  • winres.hはWindows SDKにはありますが、現在のところMinGW-w64にはないようです: /mingw-w64-headers (Tag v5.0.1)。

このラインナップ、私が認識しているものを並べたものです。なので、Visual StudioやWindows SDKに入っていないatlres.hを含めています。

この記事のカテゴリ

リソース関係のヘッダーファイルwinres.hの話です。これはMFCのヘッダーファイルだったはずですが、少し前からWindows SDKに収録されるようになりました。

(さらに…)

この記事のカテゴリ

以下のコードのように、実際には何も行わず、ただミューテックスの要件を満たすだけの実装は簡単に作れるでしょう。Null Objectパターンの一種と言えますよね。

class null_mutex
{
  void lock() {}
  void try_lock() {}
  void unlock() {}
};

Boostには、そんなクラスがなんだかあちこちにあるようです。

Boost.Threadのものが最も汎用的です。BoostのUpgradeLockableコンセプトまで実装しています。それに次ぐのがBoost.Interprocessのものです。残りはLockableコンセプト(lock, try_lock, unlock)のみ実装のようです。

私自身は、boost::signals2::dummy_mutexなら使ったことがあります。シングルスレッドでboost::signals2::signalを使うという状況でした。

この記事のカテゴリ

  • ⇒ Boostのなんにもしないミューテ(ッ)クス

Visual C++ 2015および2017 RCでは、<codecvt>の各クラステンプレートにchar16_tやchar32_tを組み合わせるとリンクエラーになるという問題があります。今回、これに対する条件付きのWorkaroundについて書きます。

まずは、エラーになるコードの例を提示します。

#include <codecvt>
#include <string>
#include <cassert>
#include <locale>
 
int main()
{
  std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter_utf32;
  std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter_utf16;
 
  std::string u8str = u8"\U0001F359";
 
  std::u32string u32str = converter_utf32.from_bytes(u8str);
  std::u16string u16str = converter_utf16.from_bytes(u8str);
}

これをVisual C++ 2015でコンパイル・リンクするとこんなエラーメッセージが出てきます。

u.obj : error LNK2019: 未解決の外部シンボル "public: static class std::locale::id std::codecvt::id" (?id@?$codecvt@_SDU_Mbstatet@@@std@@2V0locale@2@A) が関数 "public: __thiscall std::locale::locale >(class std::locale const &,class std::codecvt_utf8_utf16 const *)" (??$?0V?$codecvt_utf8_utf16@_S$0BAPPPP@$0A@@std@@@locale@std@@QAE@ABV01@PBV?$codecvt_utf8_utf16@_S$0BAPPPP@$0A@@1@@Z) で参照されました。
u.obj : error LNK2019: 未解決の外部シンボル "public: static class std::locale::id std::codecvt::id" (?id@?$codecvt@_UDU_Mbstatet@@@std@@2V0locale@2@A) が関数 "public: __thiscall std::locale::locale >(class std::locale const &,class std::codecvt_utf8 const *)" (??$?0V?$codecvt_utf8@_U$0BAPPPP@$0A@@std@@@locale@std@@QAE@ABV01@PBV?$codecvt_utf8@_U$0BAPPPP@$0A@@1@@Z) で参照されました。
u.exe : fatal error LNK1120: 2 件の未解決の外部参照

さて、エラーメッセージを読むと、1件目はstd::locale::id型のstaticメンバー変数std::codecvt<char16_t,char,struct _Mbstatet>::idが見つからないという内容です。2件目も同じ内容でchar32_tになっているだけです。ということは、これを自分で定義したら良いのではないでしょうか?

というわけで、こんなコードを書いたらうまくいきました。コンパイル・リンクできてちゃんと実行できました。

#include <codecvt>
#include <string>
#include <cassert>
#include <locale>
 
// この2行を追加
std::locale::id std::codecvt<char32_t, char, std::mbstate_t>::id;
std::locale::id std::codecvt<char16_t, char, std::mbstate_t>::id;
 
int main()
{
  std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter_utf32;
  std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter_utf16;
 
  std::string u8str = u8"\U0001F359";
 
  std::u32string u32str = converter_utf32.from_bytes(u8str);
  std::u16string u16str = converter_utf16.from_bytes(u8str);
}

ただし、この方法を使うには条件があります。それは、VC++ランタイムを静的リンクする場合(/MT/MTdコンパイルオプション)だけ使えるというものです。/MD/MDdだと、問題となるstaticメンバー変数idが__declspec(dllimport)付きの宣言となってしまうため、この方法が使えません。別のリンクエラーになります。

ところで、以下のcpprefjpの各ページのサンプルコードもこの問題に該当します。それぞれのサンプルコードの作成・動作確認にあたり、私はこの方法で乗り切りました。

まとめです。

  • codecvt_utf16, codecvt_utf8, codecvt_utf8_utf16のいずれかに、char16_tかchar32_tを使うとリンクエラーが出る。
  • そうなったら、codecvtクラステンプレートののstaticメンバー変数idを自分で定義すれば、リンクが通る。

バグ報告は出ているそう(Visual C++における文字コード変換 – C++と色々)なので、将来的にはVisual C++が直ることを期待します。あと/MD, /MDdで使えるWorkaround欲しいです。

この記事のカテゴリ

  • ⇒ VC++ 2015のcodecvtでリンクエラーになる問題の回避策

Visual C++ 2015と2017 RCで、以下のプログラムが期待どおりに動きませんでした。

#include <iostream>
#include <sstream>
#include <iomanip>
#include <ctime>
 
int main()
{
  std::istringstream s("20170110");
 
  std::tm x = {};
  s >> std::get_time(&x, "%Y%m%d");
 
  char buf[255];
  strftime(buf, sizeof(buf), "%Y-%m-%d", &x);
  std::cout << buf << std::endl;
}

%Yで2017だけパースされることを期待しているのですが、Visual C++の実装では、20170110まで読み込んでしまうようになっていました。

libstdc++やlibc++では、ちゃんと2017だけ読み込む実装だったので、Visual C++のほうに問題がありそうです。

2017年1月15日追記: 同じ問題が報告されているのを見つけました。std::get_time fails when using formats without separators – Developer Community

この記事のカテゴリ

  • ⇒ VC++でstd::get_timeがなんかダメ
  • ⇒ VC++でstd::get_timeがなんかダメ

« 前ページへ次ページへ »