さて、C#でOgg Vorbisを扱う場合、方法は二つあります:
- libogg及びlibvorbisの全てのソースコード(C言語で書かれている)を、C#に移植する。
- オリジナルのソースコードにはそれ程手を加えずDLLをビルドし、プラットフォーム呼び出しを使う。
liboggもlibvorbisも、少なくともXiph.orgではC#版が公開されていないので、C#に移植する場合はバージョンが更新されるごとに書き直さなくてはなりません。迷わず後者を選びます(Windows環境でない場合は、移植が必要になるかも知れません。少なくとも以下の方法は使えません)。
まず、DLLビルド用のプロジェクトを作ります。以下ではVisual Studio 2005 Professionalを使っていますが、それ以外の人は異なる部分を適宜読み替えて下さい。
マイドキュメント以下のVisual Studio 2005\Ogg Vorbisディレクトリにliboggやlibvorbisを展開したので、Ogg Vorbisというソリューション名にしました。また、liboggとlibvorbisを一つのDLLにまとめるので、Integratedというプロジェクト名に。ここは好きに変えて、読み替えて下さい。
この後、アプリケーションの設定を行います。
アプリケーションの種類をDLLにするのは必須ですが、空のプロジェクトにするかどうかはお好みで。
次に、このプロジェクトに追加するファイルをコピーします。インクルードパスなどを設定すれば必要は無いですが、書き換える部分もあるので元のソースを取っておきたい場合はコピーして下さい。
- libogg-x.x.x\include\ogg ディレクトリを $(ProjectDir)include に
- libogg-x.x.x\src ディレクトリの中身を $(ProjectDir)source\ogg に
- libvorbis-x.x.x\include\vorbis ディレクトリを $(ProjectDir)Include に
- libvorbis-x.x.x\lib ディレクトリの中身を $(ProjectDir)source\vorbis に
$(ProjectDir)は、「プロジェクトファイルがあるディレクトリ + パス区切り文字」に展開して下さい。上記のようにコピーすれば、次のような構造になるはずです:
マイドキュメント\Visual Studio 2005\Projects\Ogg Vorbis\Integrated include ogg vorbis source ogg vorbis
コピーが済んだら、プロジェクトにソースファイルを追加します。ヘッダファイルは追加しないままで構いません。拡張子cのファイルだけ追加したいので、「ソース ファイル」カテゴリのコンテキストメニュー - 「追加」 - 「既存の項目」でワイルドカード「*.c」を使うと楽です。また、追加するソースはsource\ogg\*.cとsource\vorbis\*.cの全てですが、今回はエンコードまでしないため、source\vorbis\vorbisenc.cは除外します(これを追加するとDLLのサイズが異様に大きくなります。大量のグローバル変数のせい?)。また、source\vorbis\barkmel.c、source\vorbis\psytune.c、source\vorbis\tone.cは不必要(というより、main関数を含んでいるので入れては駄目)なのでこれらも除外します。
今度は、プロジェクトの設定を行います。プロジェクトのコンテキストメニューから「プロパティ」を選んで設定ダイアログを開きます。最初は共通の設定を行うので、ダイアログ左上の構成から「すべての構成」を選びます。共通で設定すべき構成プロパティの項目とその設定値は次の通りです。
- 全般 - 文字セット : 設定しない
- 全般 - 出力ディレクトリ : $(ConfigurationName)
- C/C++ - 全般 - 追加のインクルードディレクトリ : .\include
- C/C++ - 詳細 - 呼び出し規約 : __stdcall (/Gz)
- C/C++ - 詳細 - コンパイル言語の選択 : C コードとしてコンパイル (/TC)
- リンカ - 全般 - 出力ファイル : $(OutDir)\OggVorbis.dll
追加のインクルードディレクトリと呼び出し規約の変更は確実に行って下さい。特に呼び出し規約。関数のスタッククリアをどこでやるかを決めるもので、既定値の__cdeclでは呼び出し側で、__stdcallでは呼び出された関数内部で行います。既定値の__cdeclでも出来ないことはないですが、DLLのエクスポート関数は普通__stdcallですし、.NET Frameworkでは__stdcallの関数しか(多分)作れないので、__stdcallを使います。それ以外の項目の変更は念の為です。
共通の設定が終わったら、最適化に関する設定を行います。外から見れば得られる結果は変わりませんが、内部的には速くなるので、最大限に最適化する方が良いでしょう。構成は「Release」にして、次の項目を変更します。
- C/C++ - 最適化 - 最適化 : 最大限の最適化 (/Ox)
また、SSE2が使えるCPU(Pentium4)では、次の項目も設定しておくと速くなるかも知れません。
- C/C++ - コード生成 - 拡張命令セットを有効にする : ストリーム SIMD 拡張機能 2 (/arch:SSE2)
但し、これはコンパイルする環境ではなくDLLを呼び出す環境で(このDLLを使う.NET Frameworkアプリケーションが実行される環境で)SSE2が使えないといけません。SSE2が使えるかどうかの判定(cpuid)は使わないみたいです(毎回やってれば遅くなるし、最初に一度だけするようにするとサイズが倍加するからでしょう)。/arch:SSEも同様。使う場合には、プロセッサ別にバイナリを用意するか、端からPentium4以外のCPUを見捨てるかを選択する必要があります。一度に全てをデコードする必要がある場合はともかく、ストリーミングバッファに書き込む場合、バッファ更新のインターバルよりも十分短い時間でデコードできることが多いので、使わない方が楽でいいと思います。
ここまで出来たら、とりあえずビルドしてみましょう。精度を失う暗黙の型変換や、sprintf、fopenなどの古い関数をまだ使っている、といった警告が嫌と言うほど出ますが、Xiph.orgを信用して無視します。どうしても五月蠅いという場合、コンパイラのコマンドラインオプションに/wdXXXXを追加することで全て非表示に出来ます(その警告を出さないだけで勿論潜在的には残ったままです)。無視しても問題ない警告を消すには次のコマンドラインオプションを追加します:
- /wd4244 /wd4267 /wd4305 /wd4996
これら以外の警告やエラーは改善すべきです。
- error C2440: '関数' : 'int (__stdcall *)(const void *,const void *)' から 'int (__cdecl *)(const void *,const void *)' に変換できません。
- warning C4024: 'qsort' : の型が 4 の仮引数および実引数と異なります。
こういうエラーが幾つか出ると思います。理由は明らかに呼び出し規約の既定値を__cdeclから__stdcallに変えたせいですね。勿論ここで呼び出し規約の既定値を__cdeclに戻すわけにも行きません。暗黙的なキャストが駄目だからと言って、次のようなキャストをするのは問題外です:
qsort(sortpointer,n,sizeof(*sortpointer), (int (__cdecl *)(const void *,const void *))icomp);
確かにコンパイルエラーは出ませんが、スタックポインタの値がおかしくなる為、潜在的で修復の難しいバグを抱え込むことになります。正しい解決方法は、qsortに渡される比較関数のみを__cdeclに変えることです。エラー一覧でqsortに__stdcallの比較関数を渡してしまっている箇所を探し、比較関数の名前で右クリック - 「定義へ移動」で定義場所を探し、__cdeclキーワードを追加します。具体的には、
static int icomp(const void *a,const void *b); // floor1.c static int comp(const void *a,const void *b); // lsp.c static int apsort(const void *a, const void *b); // psy.c static int sort32a(const void *a,const void *b); // sharedbook.c
の4つを
static int __cdecl icomp(const void *a,const void *b); static int __cdecl comp(const void *a,const void *b); static int __cdecl apsort(const void *a, const void *b); static int __cdecl sort32a(const void *a,const void *b);
と変えればOKです。実際のソースコードではプロトタイプ宣言ではなく、関数の実体が書かれているのでそのままコピーはしないで下さい(セミコロンの位置には実際は左中括弧)。
ここまで来れば、コンパイルに通るようになるはずです。これでよし……と言いたいところですが、マネージドコードから使うにはこれでは不十分です。というのは、エラー処理が出来ないからです。sourceディレクトリにSetError.cというファイルを作り、プロジェクトに追加して次の内容をコピーして下さい:
// SetError.c #include <stdlib.h> #include <errno.h> void SetError( int errorOccurred ) { if ( errorOccurred ) _set_errno( EINVAL ); else _set_errno( 0 ); return; }
引数errorOccurredが真であればグローバル変数errnoをEINVAL(=22)に、偽であれば0に設定するものです。後で使います。
これでソースコードの修正は完了です。しかし、この状態ではどの関数もエクスポートされていません。「dumpbin /exports OggVorbis.dll」とやれば、Summary以外に何も出力されないことが確認できるでしょう。エクスポートとは、DLLの関数を外に見えるようにすることを指します。エクスポートされた関数は、関数名、エントリポイント(関数のアドレス)、序数が公開され、外部から呼び出すことが出来るようになります。関数をエクスポートするには幾つか方法があるのですが、今回はモジュール定義ファイルを使用します。sourceディレクトリにOggVorbis.defというファイルを作り、(一応)プロジェクトに追加して次の内容をコピーして下さい:
;OggVorbis.def LIBRARY EXPORTS ov_open_callbacks ov_clear ov_info ov_read ov_pcm_total ov_pcm_seek ov_pcm_tell SetError
詳しいことは説明しませんが、EXPORTSに続けてエクスポートしたい関数の名前を書いていけばエクスポートできます。モジュール定義ファイルの詳細はモジュール定義 (.def) ファイルを参照して下さい。通常のデコードをする場合は上記以外の関数をエクスポートする必要はないでしょうが、もし必要そうなものがあればVorbisfile API Referenceなどを見ながら追加してください。このドキュメントはlibvorbis-x.x.x\docにも入っています。
最後に、このモジュール定義ファイルをリンカの入力に追加します。プロジェクトのプロパティダイアログを開き、「すべての構成」で次の項目を変更します:
- リンカ - 入力 - モジュール定義ファイル : .\source\OggVorbis.def
これでプラットフォーム呼び出しに必要なDLLの完成です。お疲れさまでした。……勿論、モジュール定義ファイルを追加したらリビルドしておいて下さいね。
……しかし、一行でも空白行作ったらそれ以降p要素やbr要素を滅茶苦茶に入れてくれるMovable Typeさんってどうにかならないんでしょうか。
Warning ! Have you heard about Google sniping ? there is a buzz about it right now . Basically its an alternative to blogging NOBODYSDEAD.COM check it out and tell me what you think please