2016年6月3日金曜日

Visual Studio 2015で gflagsを x64/Unicode文字セットのアプリケーションから利用する

google先生の作った gflagsというコマンドライン引数を扱うライブラリを タイトルの環境で使えるようにしたのだけど、とてもつかれた・・・ また同じ思いはしたくないのでメモ。ちなみにまだ簡単なサンプルでしか動作確認していないので注意。

[CMakeのインストール]
・cmakeがインストールされてなければ、CMake公式サイトから win32/x86のバイナリをダウンロードしてインストール

[gflagsのビルド]
1. githubから ソースを入手
2. VS2015の各種ツールのパスの通ったコンソールから、gflagsの CMakeLists.txt があるフォルダに移って、"cmake ."を実行
3. gflags.sln が生成されたはずなので、これをVS2015で開いてビルドすると、ひとまずx86でマルチバイト文字セットのライブラリができる

[gflagsをx64でビルド]
1. gflags.slnを開いた状態で、"ビルド->構成マネージャー"メニューを実行し、右上のアクティブソリューションプラットフォームの欄で新規作成を選び x64プラットフォームを作成する。
2. gflags_staticとgflags_nothred_staticプロジェクトのプロパティを開き、ライブラリアン->その他のオプションの項目で MachineX86オプションが明示的に指定されているのでこれらを除去。
3. これでx64のライブラリがビルドできるはず。

[gflagsをUnicode文字セットのプロジェクトからリンクする]
1. gflagsをUnicode文字セットでビルドするのはソースをガシガシ書き換える必要がありそうなので諦めて、gflags自体はマルチバイト文字セットのままにする。
2. Unicode文字セットのプロジェクトでは普通に gflagsをリンクし、google::ParseCommandLineFlags()で渡す引数をマルチバイトに変換して渡す。
3. これで利用できるはずだが、VS2015ではロケールの扱い周りでバグがあるらしく(?)、std::cout、std::wcoutにどちらかしか日本語出力ができなかったり、mbstowcs系の関数がうまく動かなかったりでハマった。 ひとまず Win32 APIの MultiByteToWideChar()を使ったり, coutへの出力は諦めて対応。コードはざっと以下のような感じ。
#include "../../../gflags/include/gflags/gflags.h"
#pragma comment(lib, "../../../gflags/x64/release/gflags_static.lib")
#pragma comment(lib, "shlwapi.lib") // __imp_PathMatchSpecAのリンクに必要

#include <iostream>
#include <vector>
#include <windows.h>

DEFINE_bool(big_menu, true, "Include 'advanced' options in the menu listing");
DEFINE_string(language, "japanese", "default language");

bool MBS2WCS(const std::string& s, std::wstring& sResult)
{
    int nBufSize = MultiByteToWideChar(CP_ACP, 0, s.c_str(), -1, NULL, 0);
    std::vector<wchar_t> vBuf(nBufSize);
    if (MultiByteToWideChar(CP_ACP, 0, s.c_str(), -1, &vBuf[0], nBufSize) == 0) {
        _ASSERTE(false);
        return false;
    }
    sResult = std::wstring(&vBuf[0]);
    return true;
}
bool WCS2MBS(const std::wstring& s, std::string& sResult)
{
    int nBufSize = WideCharToMultiByte(CP_ACP, 0, s.c_str(), -1, NULL, 0, NULL, NULL);
    std::vector<char> vBuf(nBufSize);
    if (WideCharToMultiByte(CP_ACP, 0, s.c_str(), -1, &vBuf[0], nBufSize, NULL, NULL) == 0) {
        _ASSERTE(false);
        return false;
    }
    sResult = std::string(&vBuf[0]);
    return true;
}

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
    setlocale(LC_ALL, "");
    std::locale loc("");
    std::wcout.imbue(loc);

    std::vector<char*> argvA(argc);
    std::vector<std::vector<char>> buf(argc);
    for (int i = 0; i < argc; ++i) {
        std::string s;
        WCS2MBS(argv[i], s);
        buf[i].resize(s.size() + 1);
        strcpy_s(&buf[i][0], buf[i].size(), s.c_str());
        argvA[i] = &buf[i][0];
    }

    gflags::SetUsageMessage("gflagsライブラリのテスト");
    char** pA = &argvA[0];
    google::ParseCommandLineFlags(&argc, &pA, true);

    std::wcout << FLAGS_big_menu << std::endl;

    // coutに日本語が出力できないのでwcoutに出力
    std::wstring s;
    MBS2WCS(FLAGS_language, s);
    std::wcout << s << std::endl;

    return 0;
}

実行するとこんな感じ。
>gflag_test.exe -language 日本語
1
日本語

>gflag_test.exe -language English -nobig_menu
0
English

0 件のコメント:

コメントを投稿