C#でVFWのAVIFileを使ったログ

WinAPIにはVFW(VideoForWindows)としてマルチメディア関連のAPIがありますが、その中にAVIを操作するものがあります。今ではその役割はDirectShowに取って代わられたようですが、DirectXは触ったことがないのでVFWを使ってみることにしました。
要は複数のBitmapからAVIファイルを作りたかったんです。で、C言語のコードをP/Invokeで移植しようとしたらいろいろ詰まったのでそのメモをつらつらと。

ハンドルをどうするか

WinAPIではちょくちょくポインタが登場します。構造体やクラスのポインタなら感覚で書けますが、例えばハンドルは「何かへのポインタ」と定義されているので下手に中身を定義できません。なのでIntPtrという汎用ポインタが.NETには用意されています。
さて、今回使うPAVIFILE・PAVISTREAMはなんでしょうか。ヘッダファイルをたどっていくとこう書かれています。

DECLARE_INTERFACE_(IAVIStream, IUnknown)
{
    /* 中略 */
}
typedef       IAVIStream FAR* PAVISTREAM;

え、COMだったの・・・?
なーんだそれならタイプライブラリ変換すれば簡単じゃんね―ははは、と思ったらタイプライブラリがない。ぐぬぬ
さっき出てきたIntPtrを使う手もありますが、型の区別がつかなくなってしまうので避けたいところです。そこでこんな方法。

  1. ヘッダファイルからIAVIStreamのGUIDを探し出す(IID_IAVIStreamに定義されていた)
  2. 文字列形式に書き換えて、C#のコードに書き写す
  3. メソッドはどうせ使わないからからっぽってことにしておく
    [ComImport]
    [Guid("00020021-0000-0000-C000-000000000046")]
    interface IAviStream { }

これで動いちゃった。メソッドがどうこうというより、IIDがあっていればマーシャリングはできるようです。そのためのIIDか。

LONG型は.NETの何?

まずよく見かける型はこう対応しています。

C .NET C#
DWORD System.UInt32 uint
WORD System.UInt16 ushort

さて、LONGは?

C .NET C#
LONG System.Int32 int

C#ではlongは64ビット整数型ですが、C言語ではlongも32ビットだったという・・・すっかり忘れてたよ。

HRESULT

HRESULTってなんの型に対応するんだろう、と言う前に、そういえばHRESULTがどうのこうのって例外がCOMを使ったときに出ていました。ということはうまいこと例外に変換してくれる機能があるはずっ・・・

PreserveSig フィールドに true を設定すると、HRESULT 値または retval 値でアンマネージ シグネチャが直接変換されます。false を設定すると、HRESULT 値または retval 値が自動的に例外に変換されます。

http://msdn.microsoft.com/ja-jp/library/system.runtime.interopservices.dllimportattribute.preservesig(v=VS.100).aspx

あったー!

LPVOID

STDAPI AVIStreamSetFormat(
    PAVISTREAM pavi,
    LONG lPos,
    LPVOID lpFormat,
    LONG cbFormat
);

3番目の引数がLPVOIDになっていますが、ここは任意のデータへのポインタを指定します。任意のデータか、じゃあobjectでいいよね。

        [DllImport("avifil32.dll", PreserveSig = false)]
        public static extern void AVIStreamSetFormat(IAviStream pavi, int lPos, object lpFormat, int cbFormat);

ところがこれ動きません。PInvokeStackImbalance(スタック壊れちゃった><)という例外が発生してしまいます。え、objectなら中身なんにせよポインタじゃないの・・・?
調べてみたもののよくわからず、とりあえず「object型の引数に適当なクラスのインスタンス渡したつもりでも、そのポインタは渡っていない」らしいです。で、今風な解決策はオーバーロード作りまくることらしいです。可能性のある型がわかっていればこれでもいいですが、ちょっと意味合いが変わってしまいそうで今回は使いたくないですね。
で、別の方法。

        [DllImport("avifil32.dll", PreserveSig = false)]
        public static extern void AVIStreamSetFormat(IAviStream pavi, int lPos, [MarshalAs(UnmanagedType.AsAny)] object lpFormat, int cbFormat);

MarshalAsでAsAnyと指定してやると、objectの中身をマーシャル時に確認して、それにあったマーシャリングをしてくれるとのこと。つまりオーバーロードを勝手に用意してくれるような感じ。やったね!
と、ここまで書いてobjectに値型も代入できることを思い出しました。うーんやっぱりvoid*にぴったりな方法はないのか・・・

ひとこと

P/Invokeって奥が深いというか難しいというか下手したら泥沼のような・・・
やっぱりCOMも使えるようにならないとなーと思いました。いつかやりましょう、いつか。
参考サイト