ffmpeg「開発」から三ネタ(msinttypes と c99wrap、makedef)

何でこんな話を、のゴールの話は「至りませんでした」ネタなので、書くかどうかわかんないけど、その過程で出たネタ。


msinttypes の話は 前にもしてる からいいよね。以上。


今「C での開発」で全世界的に「大問題」になってるのが、もちろん「C99 カッケー」派とそれをまだ扱えないコンパイラしか使えない開発者群との「闘い」なわけね。無論これは「俗に言う」 K&R C と「俗にかつてそう呼んでいた」 ANSI C (今 C89 と呼ばれているもの)との間でも起こった。無論その時代も変換ツールが出回っていた。ただその当時のそれとは今回の進化が明らかに違っているのは、「誰もがその進化を歓迎したわけではない」ということ。

C99 「だけ」を見れば全然悪かないよ。圧倒的に保守性の高いプログラムを記述出来る。ただ Microsoft が最後まで強行に反対し続けた通り、「C++ との関係性を無視した拡張が行われてしまった」ことが、登場時点から今の今まで、ずーっとしこりのまま残っておるわけね。そして結果として最も C99 対応が遅れたのが、メジャーなものとしてはやはり Microsoft の MSVC。

そうはいっても「今更 Visual Studio 2008 (VC++ 9.0) もあるめーよ」は、そう、あなたは正しい。ただ一点を除いては。無論「Microsoft Visual C++ Compiler for Python 2.7」のことを言っている。(2.7 と言っているけれど Python 3.2 までは同じコンパイラでビルドされている。) すなわち「C99」で書かれたが最後、「Python 2.7 では絶望的になる」…。まぁあと二年の命でしょどーせ、て見方もあるけどサ。

そんな貴方に「c99wrap」。

というと思ったでしょう? ところがそうは問屋が卸さない。

まずこれ、libav の中のサブプロジェクトね。ffmpeg の、と言っても良いか。で、ノリとしては「とりあえずは ffmepg で古いコンパイラ対応捨てちゃったから、直裁的な方法だけどインチキ対応ででもリカバリしとくべぇ」ってもの。つまりは「ffmpeg のソースさえビルド出来ればよろしい」という程度のもの。つまり網羅性については今ひとつよくわからんのね。それだけでなくこやつ、単独で利用するのが非常に使いにくい。本当はセットになってる「c99conv」を使いたいのだけれど、これは現実問題「出来ない」。原則「c99conv」は「c99wrap」から呼び出されて使われることだけを踏まえてる。

まぁそれだけなら良かったんだけどさ、実際 ffmpeg のソースでさえ、「たった一つの C ソースだけが」ビルド出来ない(3.2の場合)。libavcodec/ffv1enc.c がそれ。これだけは唯一「手作業で C89 変換」しなければならない。具体的にはここ:

 1 static const AVOption options[] = {
 2     { "slicecrc", "Protect slices with CRCs", OFFSET(ec), AV_OPT_TYPE_BOOL, { .i64 = -1 }, -1, 1, VE },
 3     { "coder", "Coder type", OFFSET(ac), AV_OPT_TYPE_INT,
 4             { .i64 = 0 }, -2, 2, VE, "coder" },
 5         { "rice", "Golomb rice", 0, AV_OPT_TYPE_CONST,
 6             { .i64 = AC_GOLOMB_RICE }, INT_MIN, INT_MAX, VE, "coder" },
 7         { "range_def", "Range with default table", 0, AV_OPT_TYPE_CONST,
 8             { .i64 = AC_RANGE_DEFAULT_TAB_FORCE }, INT_MIN, INT_MAX, VE, "coder" },
 9         { "range_tab", "Range with custom table", 0, AV_OPT_TYPE_CONST,
10             { .i64 = AC_RANGE_CUSTOM_TAB }, INT_MIN, INT_MAX, VE, "coder" },
11         { "ac", "Range with custom table (the ac option exists for compatibility and is deprecated)", 0, AV_OPT_TYPE_CONST,
12             { .i64 = 1 }, INT_MIN, INT_MAX, VE, "coder" },
13     { "context", "Context model", OFFSET(context_model), AV_OPT_TYPE_INT,
14             { .i64 = 0 }, 0, 1, VE },
15 
16     { NULL }
17 };

なんでここだけピンポイントで手作業なのか、は実は c99wrap 自身には「根本的な意味では」罪はなかったりするのがまた痛ましい。すなわち「c99wrap が呼び出す cl.exe がハングアップしてしまう」から。非常に偶然何かの琴線に触れてしまうようで。(c99wrap かまさなくともハングアップする。)

「バグの一個もないコンパイラ」なんか存在しない。CL.EXE でも随分経験した。GCC にだってある。というわけで、諦めて手で直すしかない、ここは。どうしたもんかなぁと思ったが、(多分)こうするしかない:

 1 static const AVOption options[] = {
 2     { "slicecrc", "Protect slices with CRCs", OFFSET(ec), AV_OPT_TYPE_BOOL, { -1LL }, -1, 1, VE, NULL },
 3     { "coder", "Coder type", OFFSET(ac), AV_OPT_TYPE_INT,
 4             { 0LL }, -2, 2, VE, "coder" },
 5         { "rice", "Golomb rice", 0, AV_OPT_TYPE_CONST,
 6             { (__int64)AC_GOLOMB_RICE }, INT_MIN, INT_MAX, VE, "coder" },
 7         { "range_def", "Range with default table", 0, AV_OPT_TYPE_CONST,
 8             { (__int64)AC_RANGE_DEFAULT_TAB_FORCE }, INT_MIN, INT_MAX, VE, "coder" },
 9         { "range_tab", "Range with custom table", 0, AV_OPT_TYPE_CONST,
10             { (__int64)AC_RANGE_CUSTOM_TAB }, INT_MIN, INT_MAX, VE, "coder" },
11         { "ac", "Range with custom table (the ac option exists for compatibility and is deprecated)", 0, AV_OPT_TYPE_CONST,
12             { 1LL }, INT_MIN, INT_MAX, VE, "coder" },
13     { "context", "Context model", OFFSET(context_model), AV_OPT_TYPE_INT,
14             { 0LL }, 0, 1, VE, NULL },
15     { NULL }
16 };

要するに「全フィールド漏らさず」かつ「union 初期化は int64 であることを明示」。

「多分」と歯切れが悪いのは、結局 VC++ 9.0 での「ビルド」は成功したけれど、それが正しく動作することは遂になかったから。まぁ「あとでネタにするかもしれないししないかもしれない」と言ってるのはこの話ね。

ただ現実問題としては、ffmpeg ほどの大量のファイルをコンパイルして、たまたま不幸なケースに触れてしまったことだけで評価を確定するのは、さすがに酷だろう。そういうことではなくて、あたしゃこの「作りが雑過ぎてイヤじゃ」。見ただけで保守したくない、こんなん。

実際さ、「ハングアップしてしまうのが問題なのであるならば、変換が必要や否や、の判定を変えてしまえば対応出来るかもなぁ」と手を入れようかとも思ったわけよ。けど萎えた、萎えました。やだよこんなの。

まぁちょっとしたものだったら、ハッピーになれることもあるやもしれぬぞ、とは思う。「朗報です!」と強く言えるほどではないと思うけれど、「足しにはなるかもしれんぞ」くらいには言えると思う。一応使い方は「ほぼ包まれるコンパイラ(など)と同じ」:

1 c99wrap -ms cl -nologo aaa.c

てな具合。-ms は MSVC 互換モード、て意味。(C コンパイラだけでなくリソースコンパイラなんかも対象に出来ると思う、多分。) あと「どんな変換が行われたのかわからないと原因解析でけん」て場合は、「c89変換されたソースを残す」ことも出来る:

1 c99wrap -ms -keep cl -nologo aaa.c

次は「makedef」の話。これも ffmpeg (libav) のソース内でしか見つけられないので、独自のものかもしれない。

これは「場合によっては嬉しいのかもなぁ」とちょっとだけ思ったので、一応紹介してみたい。

直前に DLL からインポートライブラリを作る ネタを書いたが、makedef も同系統のネタ。ただしこの makedef については「どこぞの誰かが作った DLL から抽出する」話ではなくて、「オレ様が作っておるアプリケーションが何をば公開すんべかぁ」を「楽に」制御するためのもの。

DLL を作るにあたって、「公開関数」とするかどうかを決定する方法は2つある。ひとつはソースコード中に Microsoft 拡張の「__declspec(dllexport)」で修飾すること。この話は以前 Cython ネタに絡めてしたことがあるかと思う。GeographicLib を例に。もう一つが DLL からインポートライブラリを作る ネタでも書いた「Module Definition File (.def)」ファイルを作ること。

ただ後者は原則としては「手作り」すべきものである。そりゃぁそうなのだ、「取捨選択する」ための定義ファイルなんだから、「勝手にぜーんぶ」公開されたら実際実用上の問題が頻発する。つまり「ABI が枯れない」ということが起こる。(プライベート関数を追加しただけでインターフェイスがブチ壊れる、てこと。)

それではこの makedef は「やったね、自動化してくれるぜぃ」ってツール、なのか? 否。そうじゃなくて、「大量の関数の取捨選択を、正規表現によるフィルタで半自動」化するもの。DLL からインポートライブラリを作る と同じく dumpbin の出力を利用するのだが、DLL から作るのと違って「コンパイルしたての .obj ファイル」を入力とする。

実際のところはなーんの説明もないので意図を理解するまで時間がかかっちゃったんであって、上で説明したことは「ソースも読みつつ実際に動かしてみて」ようやくわかったこと。このツールは「バージョンファイル」と呼ばれる「フィルタ」と obj ファイルを受け取って def ファイルを作る。「バージョンファイル」と呼んでいるものの構造はこんな感じ:

 1 LIBAVFORMAT_MAJOR {
 2     global:
 3         av*;
 4         #FIXME those are for ffserver
 5         ff_inet_aton;
 6         ff_socket_nonblock;
 7         ff_rtsp_parse_line;
 8         ff_rtp_get_local_rtp_port;
 9         ff_rtp_get_local_rtcp_port;
10         ffio_open_dyn_packet_buf;
11         ffio_set_buf_size;
12         ffurl_close;
13         ffurl_open;
14         ffurl_write;
15         #those are deprecated, remove on next bump
16         url_feof;
17     local:
18         *;
19 };

global に列挙されていないものは EXPORTS として抽出されない、ということらしいよ。(すまん、自分で活用しようとは全然思わないんで、想像で言ってる。)

開発スタイルによっては「あーこれいいかも」なのかもなぁ、と思った。も一度言うけれど「ABI がむやみに動かない」ことが必要なことなんです。結構これに無頓着なプロジェクトが多いんだけど、ffmpeg (libav) はそうではない、少なくとも「公開関数の選択、という点に限れば」ということね。(もちろん関数のシグニチャを変えればブチ壊れるよ、当たり前。)


以上の3つのネタは、「ffmpeg でとあることをしようとして失敗しました」というネタから無理やり引っこ抜いたネタでした。「失敗談」は、ちょっとオモロイのかオモロないのかなんとも言えないもんなんで、書くかどうかはわかんない。「VC++ 9.0 で」は「失敗のうちのたった一つ」であって、実際はその先すらも果てた、というネタ。どうだろね、人によっては役に立ちうるのかもしれんし、混乱させるだけかもしれんし。まだなんとも言えん。