Incidencia #27169

ソースコードmesg-jpn.h を読める形式にする

Abrir Fecha: 2012-01-19 08:39 Última actualización: 2012-02-23 17:24

Informador:
Propietario:
Tipo:
Estado:
Cerrado
Componente:
(Ninguno)
Hito:
(Ninguno)
Prioridad:
5 - Medium
Gravedad:
5 - Medium
Resolución:
Fixed
Fichero:
Ninguno

Details

メッセージ定義ヘッダ 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であるための措置と思います。

現行の手順の問題として以下のような点が挙げられます。

  • (a)mesg-jpn.hとmesg.old.hの二重管理になりメンテナンス性に難がある。
  • (b)Visual Studio上から\xNN...エンコード前の文字列を参照できない
  • (c)sourceforge.jpのソースコードビューアで内容やdiffを確認できない

これらを解決する手段として、mesg-jpn.hをマスターとして、実行時にUTF-8バイナリに変換するようにする方法を提案します。

利用側のコードは変えず、mesg-jpn.hの文字列を UTF-8エンコードとし、このような書き方にします。

#define MSGJPN001		u8("接続を中止しました.")

u8(x)部分を、実行時にUTF-8で取得できるような関数に展開します。

※u8マクロの名前は C++11の u8"UTF-8文字列" の表記を意識しています。

Ticket History (3/13 Histories)

2012-01-19 08:39 Updated by: umorigu
  • New Ticket "ソースコードmesg-jpn.h を読める形式にする" created
2012-01-19 08:54 Updated by: umorigu
  • Resolución Update from Ninguno to Fixed
  • Details Updated
  • Summary Updated
Comentario

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関数に展開されます。

ご検討お願いします。

2012-01-19 09:10 Updated by: umorigu
  • Tipo Update from Bugs to Patches
2012-01-19 11:21 Updated by: s_kawamoto
Comentario

(a)は設定次第では自動化も可能です。 (b)は確かに修正したいと考えていました。 (c)はmbtoutf8を書き換えて"mesg-jpn.h"をUTF-8で保存すれば対応可能です。

u8マクロは今後の移植を考えると、C++11ではu8が予約語になるので、それらしい別の名前(例えば"_Tu8(x)"など)に変えた方が良いと思います。

この仕様だと恐らく実行時にUTF-16 LEやShift_JISからUTF-8に変換することになりますが、これには考慮するべき点がいくつかあります。

  • 一般的に文字列定数はconst char型かつ書き換え禁止のメモリページ上にある。

これは変換後に、確保したメモリをVirtualProtectExで変更すれば実現可能です。

  • 単純な実装だと実行時に文字列を検索するか逐一変換しなければならない。

MSGJPN001などの定数をconst char*型グローバル変数に置き換え、変換後の文字列を指すようにし、さらにそのグローバル変数をVirtualProtectExで変更不可にすれば実現可能です。

コードレビューは後で行います。

2012-01-19 19:39 Updated by: s_kawamoto
Comentario

コードを見てみましたが、参照するたびに検索し、しかも検索が単純な線形探索でしたので、testブランチでは一旦無効化しました。文字列の検索には二分探索などを用いると良いでしょう。 こちらでも改善策を考えてみます。 http://git.sourceforge.jp/view?p=ffftp/ffftp.git;a=commit;h=1a0506a7fce0ca8d2d735bc6d5e1f79b73487b51

2012-01-19 19:47 Updated by: s_kawamoto
Comentario
#define U8MSG(text, u8) (u8)
#define MSGJPN001 U8MSG("テスト", "\xE3\x83\x86\xE3\x82\xB9\xE3\x83\x88")

この方式はどうでしょうか。

2012-01-20 01:25 Updated by: umorigu
Comentario

>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変換を行っており、この変換にくらべて各々の逐一検索のための数値比較が十分小さい処理時間と考えられる』さらに言うと、他の処理に隠れてしまうほどの処理時間増加であり、無視できると考えています。

二分探索については、実現にするためのコードの複雑さとソート時間を嫌ったのですが、処理速度が文字列定数の増加に影響されないという意味でおっしゃるとおりメリットがありそうです。試してみます。ありがとうございます。


まとめ。 主張としては「参照のたびに検索したとしても十分高速であり、アプリ実行速度に影響を及ぼさない」ということなのですが、処理速度について現状では主観になってしまうので計測するのがよさそうですね。ベンチマークプログラムを書いてみます。

2012-01-20 19:53 Updated by: s_kawamoto
  • Summary Updated
Comentario

解決不可能な欠点が見つかりましたので、こちらで考えた案で実装しようと思います。

u8マクロの欠点は以下の通りです。

  • 定数ではないためchar[]型変数の初期値に使えない。
  • 定数ではないためswitch case文に使えない。
  • sizeofで文字列の長さを取得できない。

代替案は以下の通りです。

  • 前述のU8MSGマクロのような形式を使用する。
  • コーディング時はU8MSGマクロのtext部を編集する。
  • ビルド直前に自動的にU8MSGマクロのu8部を自動生成するツールを起動する。
  • SourceForge.JP上から読めるように"mesg-jpn.h"をUTF-8で保存する。
  • "mesg-jpn.old.h"は使用されないので削除する。
2012-01-20 19:53 Updated by: s_kawamoto
  • Summary Updated
2012-01-20 20:23 Updated by: s_kawamoto
  • Summary Updated
2012-01-21 01:14 Updated by: s_kawamoto
Comentario

とりあえずtestブランチで前述の代替案で実装しました。 http://git.sourceforge.jp/view?p=ffftp/ffftp.git;a=commit;h=2ff870d5b379970d1b68caa18758695caa46424f

2012-01-21 08:30 Updated by: umorigu
  • Propietario Update from umorigu to s_kawamoto
Comentario

中途半端だったので、二分探索と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ブランチで前述の代替案で実装しました。

さすがです。速いですね。 元の状態に比べてずいぶん良くなったと思います。このチケットで挙げた問題点はほとんど解決されているのでこれでもよさそうです。

2012-02-23 17:24 Updated by: s_kawamoto
  • Ticket Close date is changed to 2012-02-23 17:24
  • Estado Update from Open to Cerrado
Comentario

1.98eで実装しました。

Attachment File List

No attachments

Editar

You are not logged in. I you are not logged in, your comment will be treated as an anonymous post. » Entrar