[ICU][C++]MSVCでICUを静的リンク

 ICU(International Components for Unicode)はC言語やJavaでUnicodeを扱うためのライブラリです。ICU4C(C言語用ICU)のバイナリが配布されていて、これを使うと手軽にICUを使うことができるんですが、今回はVisual StudioからICUを静的リンクして使おう、っていう試みです。

ICU

 ICUをビルドするにあたってソースコードが必要になりますが、ソースコードはここからダウンロード出来ます。「ICU4C Source Code Download」ってところにあるzipファイルを落としましょう。

 なお、今回利用したVisual StudioはVS2010で、ICUのバージョンは50.1.2です。

 Visual Studioの場合、解答したフォルダの「icu/source/allinone」フォルダ内に「allinone.sln」が入っていますので、これを開いてビルドするだけでひと通りの環境は揃います。が、これでできるのはDLLファイルで、今回はここで生成されるDLLをlibファイルにしようってことです。

 Visual Studio以外の環境では、configure時に「--enable-static」を指定するだけで静的リンク可能なライブラリファイルが出来るのですが、ページを見てもVisual Studioでのビルド方法は書いていません。結果から言うとそんなに難しくはなかったのですが、色々勘違いしてて嵌ったので以下に方法をまとめておきます。

Visual Studioでビルド

 libファイルを作るには、プロジェクトのプロパティの「構成の種類」が「ダイナミックライブラリ(.dll)」になっているものを「スタティックライブラリ(.lib)」に変えます。具体的には

  • common
  • ctestfw
  • i18n
  • io
  • layout
  • layoutex
  • testplug
  • toolutil

上記のプロジェクトです。

 次に、プリプロセッサを追加します。「U_STATIC_IMPLEMENTATION」を指定するとスタティックライブラリになるらしいので、「makedata」を除く全てプロジェクトの構成に対して「U_STATIC_IMPLEMENTATION」を指定してください。DebugとReleaseの両方です。

 ここでビルドをかけると、大量のリンクエラーとともにビルドが終了します。エラーの原因はLNK2005、「icu_50::StringPiece::npos」なる整数値が複数定義されることによるみたいです。

icu_50::StringPiece::nposの定義を直す

 さて、ここで問題の「stringpiece.h」を見てみましょう。stringpiece.hは「common」プロジェクトの「string」フィルタ内にあります。186行目辺りでnposが定義されています。見た感じ特に悪さはしていない、というかC++では「static const int」なメンバはちゃんと内部結合になるのでヘッダに書いても定数として扱われるはずなんですが…

 …とここでおもむろに「stringpiece.cpp」の最後の方を覗いてみましょう。こんなコードがあるはずです。

#if (!defined(_MSC_VER) || (_MSC_VER >= 1500)) && !defined(CYGWINMSVC)
const int32_t StringPiece::npos;
#endif

 「_MSC_VER」マクロはVisual Studioのバージョンが定義されたマクロで、1500はVisual Studio 2008です。stringpiece.cppコメントを見たところ、Visual Studio 2008のバグが関係しているようで、ヘッダで定義した定数メンバをここでもう一度宣言しないとビルドが通らないらしいです。このバグは2010では直ってるみたいで、「>=」の部分を「==」にすれば解決のようです。

#if (!defined(_MSC_VER) || (_MSC_VER == 1500)) && !defined(CYGWINMSVC)
const int32_t StringPiece::npos;
#endif

 ここでもう一度ビルドを通すと…今度は「_icudt50_dat」が見つからないとのことで怒られます。

_icudt50_datの未定義を直す

 直したいのですが、Google先生はこの単語をインデックスしてないようで、検索出来ませんでした。ということで地道にぐぐりん。

 どうやら原因は出力ファイル名にあるようです。スタティックライブラリとして出力するプロジェクトの出力ファイル名(ターゲット名)を以下のように変更します。

  • common → icuuc
  • i18n → icuin
  • io → icuio
  • layout → icule
  • layoutex → iculx

 リリースビルドの時は上記のように変更してください。デバッグビルドの場合は、出力ファイル名の末尾に「d」をつけます。「icuuc」なら「icuucd」になります。

 こんなん気づくかよ!って突っ込みたくなりますがとりあえずこの設定でリビルド。するとエラーは1箇所のみで他のプロジェクトはすべてビルド完了するはずです。

 makedataプロジェクトでエラーが起きてますがこれはあんまり関係ないみたいです。

icudt.lib

 ただビルドを通しただけだと、「icudt.lib」が生成されないようです。しょうがないので手動で生成しましょう。icudt.libは「source/data/out/tmp」の中にある「icudt50l_dat.obj」から生成します。Visual Studio 2010コマンドプロンプトを起動して、tmpフォルダに移り、以下のコマンドを実行しましょう(普通のコマンドプロンプトではダメです)

lib /out:icudt.lib icudt50l_dat.obj

 終わったら出来上がった「icudt.lib」をlibフォルダに入れておしまいです。ためしに以下の様なコードをビルドしてみましょう。

#include <iostream>
#include <fstream>
#include <unicode/unistr.h>

#pragma comment(lib, "icudt.lib")
#pragma comment(lib, "icuin.lib")
#pragma comment(lib, "icuio.lib")
#pragma comment(lib, "icule.lib")
#pragma comment(lib, "iculx.lib")
#pragma comment(lib, "icuuc.lib")

int main()
{
    std::fstream r("utf8.txt");
    std::string str;
    r >> str;

    icu::UnicodeString ustr(str.c_str(), "utf8");

    char buf[256];
    ustr.extract(0, ustr.length(), buf, "shift_jis"); 
    std::cout << buf << std::endl;

    return 0;
}

 上記のコードはUTF-8のファイルを開いてShiftJISに変換して表示するコードです。ちゃんと動いてるみたいです。

バカデカい実行ファイルをどうするか

 静的リンクにすると、文字コードの変換テーブルを全部実行ファイルに埋め込むことになるので、実行形式ファイルのサイズが20MB程度まで膨れ上がります。

 これはしょうがないといえばしょうがないんですが、必要のない文字コード変換テーブルを外すことである程度は小さくなるようです。

ICU Data Library Customizer

 上記のサイトで必要な文字コードを選べますので、日本語だけでいいならSJISとかEUCに絞ってテーブルを作るとずいぶん小さくなります。

 選んだら「Get Data Library」をクリックしてダウンロードしましょう。今回はICU 50.1.2を使ったのでicudt50lみたいなファイル名になっていますが、ここでダウンロードするファイルはICUのバージョンに依存しますので、49系の人はhttp://apps.icu-project.org/datacustom/ICUData49.htmlからダウンロードしましょう。各バージョンごとに適切なファイルがありますので、必ず適したバージョンのデータファイルをダウンロードしましょう。

 ダウンロードしたら「source/data/in」にある「*.dat」ファイルを落としてきたdatに置き換えてソリューションをまるごとリビルドしましょう。これで 実行形式ファイルもまぁまぁ小さくなったかと思います。

1 件のコメント :

  1. 現在のバージョン(ICU 58.1)とVisual C++ 2015でやってみました。StringPiece::nposの問題も発生せずビルドできました。

    また、sicudt58.libの生成も成功しました。makedataプロジェクトに対して、プロジェクトのプロパティ→構成プロパティ→NMake→全般→ビルドコマンドラインオプションに"ICU_PACKAGE_MODE=-m static"を追加してビルドしたところ、source\data\out\build\icudt58l\sicudt58.libが生成されるようになりました。

    ただし、makedata.makに拡張子.dll決め打ちでファイルコピーをする処理が書かれており、VC++プロジェクトmakedataのビルドそのものは失敗という扱いになってしまいました。

    返信削除