ソースコードmesg-jpn.h を読める形式にする
msg-utf8ブランチで実装してみました。 http://git.sourceforge.jp/view?p=ffftp/ffftp.git;a=shortlog;h=refs/heads/msg-utf8
メインの変更は http://git.sourceforge.jp/view?p=ffftp/ffftp.git;a=commitdiff;h=166d7d7d10119b178e33e04b4bed01976887cd57 です。
mesg-jpn.h で使っている u8(x) は msgutil.h によって msgutil.c の MessageUtil_GetUTF8StaticBinaryBlock関数に展開されます。
ご検討お願いします。
(a)は設定次第では自動化も可能です。 (b)は確かに修正したいと考えていました。 (c)はmbtoutf8を書き換えて"mesg-jpn.h"をUTF-8で保存すれば対応可能です。
u8マクロは今後の移植を考えると、C++11ではu8が予約語になるので、それらしい別の名前(例えば"_Tu8(x)"など)に変えた方が良いと思います。
この仕様だと恐らく実行時にUTF-16 LEやShift_JISからUTF-8に変換することになりますが、これには考慮するべき点がいくつかあります。
これは変換後に、確保したメモリをVirtualProtectExで変更すれば実現可能です。
MSGJPN001などの定数をconst char*型グローバル変数に置き換え、変換後の文字列を指すようにし、さらにそのグローバル変数をVirtualProtectExで変更不可にすれば実現可能です。
コードレビューは後で行います。
コードを見てみましたが、参照するたびに検索し、しかも検索が単純な線形探索でしたので、testブランチでは一旦無効化しました。文字列の検索には二分探索などを用いると良いでしょう。 こちらでも改善策を考えてみます。 http://git.sourceforge.jp/view?p=ffftp/ffftp.git;a=commit;h=1a0506a7fce0ca8d2d735bc6d5e1f79b73487b51
#define U8MSG(text, u8) (u8) #define MSGJPN001 U8MSG("テスト", "\xE3\x83\x86\xE3\x82\xB9\xE3\x83\x88")
この方式はどうでしょうか。
>s_kawamotoさん レビューありがとうございました。 ひとつずつコメントします。
u8マクロは今後の移植を考えると、C++11ではu8が予約語になるので、それらしい別の名前(例えば"_Tu8(x)"など)に変えた方が良いと思います。
C++11において'u8'は予約ではありません。現状でも L"文字列" はWide文字列を表しますが、Lが予約語で無いのと同じ理屈です。u8という変数も有効です。 (参考: http://d.hatena.ne.jp/melpon/20110826/1314343549 ) わかりやすさからいうとu8(x)はわりと適切ではないかと考えています。
* 一般的に文字列定数はconst char型かつ書き換え禁止のメモリページ上にある。 これは変換後に、確保したメモリをVirtualProtectExで変更すれば実現可能です。
VirtualProtectEx は知りませんでした。これを使うと文字列定数をエミュレートできますね。素晴らしいです。 今の実装は関数の戻り値をconst char* constにしてお茶を濁していましたが、char*にキャストした上で実際書き換えられてしまうと困るなぁと思っていたところでした。
単純な実装だと実行時に文字列を検索するか逐一変換しなければならない
はい。逐一検索を行っています。 ただし、今回の実装は「文字列検索」でなく、「文字列の先頭アドレス」検索を行っています。 「u8(x)の引数には文字列定数しか与えない」という制約を想定しての実装です。 文字列全体を比較するのではなく、単純な数値比較になるので、逐一検索であっても十分に高速な処理となっています。
#define U8MSG(text, u8) (u8) #define MSGJPN001 U8MSG("テスト", "\xE3\x83\x86\xE3\x82\xB9\xE3\x83\x88")
この方式はどうでしょうか。
こうなると結局第一引数と第二引数の二重管理になってしまうので、個人的にはできれば避けたいところです。次のとおり、メンテナンスコストに比べて得られるパフォーマンスメリットは少ないと踏んでいます。
コードを見てみましたが、参照するたびに検索し、しかも検索が単純な線形探索でしたので、testブランチでは一旦無効化しました。文字列の検索には二分探索などを用いると良いでしょう。 こちらでも改善策を考えてみます。
現状、文字列定数は高々350程度であり、また通常実行時に参照される文字列定数は2桁のオーダーに収まりそうです。さらに、『検索のキーは「文字列の先頭アドレス(数値)」であるために検索処理は高速である』『内部文字列フォーマットがUTF-8であるため、取得した文字列についてはほぼすべてWindows APIに渡すタイミングでUTF8→16変換を行っており、この変換にくらべて各々の逐一検索のための数値比較が十分小さい処理時間と考えられる』さらに言うと、他の処理に隠れてしまうほどの処理時間増加であり、無視できると考えています。
二分探索については、実現にするためのコードの複雑さとソート時間を嫌ったのですが、処理速度が文字列定数の増加に影響されないという意味でおっしゃるとおりメリットがありそうです。試してみます。ありがとうございます。
まとめ。 主張としては「参照のたびに検索したとしても十分高速であり、アプリ実行速度に影響を及ぼさない」ということなのですが、処理速度について現状では主観になってしまうので計測するのがよさそうですね。ベンチマークプログラムを書いてみます。
解決不可能な欠点が見つかりましたので、こちらで考えた案で実装しようと思います。
u8マクロの欠点は以下の通りです。
代替案は以下の通りです。
とりあえずtestブランチで前述の代替案で実装しました。 http://git.sourceforge.jp/view?p=ffftp/ffftp.git;a=commit;h=2ff870d5b379970d1b68caa18758695caa46424f
中途半端だったので、二分探索とVirtualProtectでのメモリ保護を実装してmsg-utf8ブランチを更新しました。
軽く速度テストもしました。100万回呼び出して200msec弱なので速度的には問題ありませんでした。
const char* get_msg(int i) { const char* p = NULL; switch (i) { case 1: p = MSGJPN001; break; ... } } ... int _tmain(int argc, _TCHAR* argv[]) { __int64 sum = 0; LONG start = 0, end = 0; int i; srand(1); start = GetTickCount(); for (i = 1; i < 100 * 10000; i++) { unsigned int r = rand(); const char* p = get_msg(r % 338 + 1); sum += strlen(p); } end = GetTickCount(); printf("sum: '%I64d', time: %d msec\n", sum, (end - start)); }
u8マクロの欠点は以下の通りです。 * 定数ではないためchar[]型変数の初期値に使えない。 * sizeofで文字列の長さを取得できない。
確かにこれはそうですね。FFFTP内ではこの使い方をしてないようなので気がつきませんでした。
定数ではないためswitch case文に使えない。
文字列リテラルは元々caseのラベルとしては使えないようです。
とりあえずtestブランチで前述の代替案で実装しました。
さすがです。速いですね。 元の状態に比べてずいぶん良くなったと思います。このチケットで挙げた問題点はほとんど解決されているのでこれでもよさそうです。
1.98eで実装しました。
メッセージ定義ヘッダ mesg-jpn.h をUTF-8にする提案です。
ソースコード中の、howtobuild.htmによると、メッセージリソースは "mesg-jpn.old.h" に定義してmbtoutf.batでmesg-jpn.hに変換するようになっています。
Visual C++ではソースコード中の文字列リテラルは実行時にUTF-16LEまたはShfft_JISでしか取得できないことと、 FFFTPの内部文字コードがUTF-8であるための措置と思います。
現行の手順の問題として以下のような点が挙げられます。
これらを解決する手段として、mesg-jpn.hをマスターとして、実行時にUTF-8バイナリに変換するようにする方法を提案します。
利用側のコードは変えず、mesg-jpn.hの文字列を UTF-8エンコードとし、このような書き方にします。
u8(x)部分を、実行時にUTF-8で取得できるような関数に展開します。
※u8マクロの名前は C++11の u8"UTF-8文字列" の表記を意識しています。