TLBのないコンポーネントvsCOM相互運用

「ねーねーWindowsMediaFormatSDKをC#で使いたいんだけどー」
既に立派なライブラリ*1があるのでこれを使えばいいんだと思います。はい。
ですがせっかくなので練習も兼ねて自分でラップしてみることにしました。
通常タイプライブラリが用意されていればTlbImpを使ったりVSから参照追加できたりと簡単なのですが、このSDKはタイプライブラリがない?のでC++のヘッダファイルを元に自分で書くわけです。

相互運用ことはじめ

インターフェイスに付けるお約束属性。

//属性の型が以前に COM で定義されたことを示します。
[ComImport]
//自動 GUID が望ましくない場合に、明示的な System.Guid を指定します。
[Guid("96406BD8-2B2B-11d3-B36B-00C04F6108FF")]

HRESULTの扱い

おそらくComImportのおかげですが、HRESULTを返す関数はvoidとして書くことで例外に変換してくれます。最後の引数がoutならこんな書き方も。

HRESULT GetOutputFormatCount(DWORD dwOutputNumber, DWORD *pcFormats)
/* ↓ */
void GetOutputFormatCount(uint dwOutputNumber, out uint pcFormats);
/* ↓ */
uint GetOutputFormatCount(uint dwOutputNumber);

COMじゃないけどHRESULTを返すAPIにはPreserveSigをfalseにすることで同様の変換をしてくれます。

[DllImport("WMVCore.dll", PreserveSig = false)]
public extern static IWMReader WMCreateReader(
    IntPtr pUnkReserved,
    WMT_RIGHTS dwRights);
// HRESULT WMCreateReader(... , IWMReader **ppReader)相当

インターフェイスがIUnknownなのかIDispatchなのか

ってのは勝手に判断してくれる or IUnknownが既定値かと思ったら、デュアルが既定値のようです。

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IWMStatusCallback
{
}

付け忘れたら何も言わずに落ちた。

void*の渡し方

なにかしらのポインタ・・・objectのことか!と思って書いたら、呼び出されるときにアクセス違反で殴られました。
かといってIntPtrは扱いにくいのでこんな方法でお茶を濁してみる。

void OnStatus(
    [MarshalAs(UnmanagedType.IUnknown)] object pvContext);

refやoutでもないのに何が違うのかは謎だけど、C#から使う分にはこれでもいけるはず。

インターフェイスの継承(実装ではない)

struct IWMStatusCallback : public IUnknown
{
public:
    HRESULT OnStatus(...) = 0;
}
struct IWMReaderCallback : public IWMStatusCallback
{
public:
    HRESULT OnSample(...) = 0;
}

こんなのをC#で書くと

interface IWMStatusCallback
{
    void OnStatus(...);
}
interface IWMReaderCallback : public IWMStatusCallback
{
    void OnSample(...);
}

こうなりそうなんですが、またもアクセス違反。
で、こう書けばいいらしいんだけど

// 1.継承は残す
interface IWMReaderCallback : public IWMStatusCallback
{
    new void OnStatus(...);
    void OnSample(...);
}
// 2.継承とはなんだったのか
interface IWMReaderCallback
{
    void OnStatus(...);
    void OnSample(...);
}

なんともよくわからない。一応インターフェイスを明示せずに実装していればC#上でも両インターフェイスの実装として扱われるはず。
MSDNに気になる記事はあったものの*2いまいち理解できず。

デバッグ実行なのにデバッガが例外を捕捉できない

コンポーネント側で発生した各種例外はそのままでは捕捉できないらしい。
プロジェクトのプロパティを開いて、デバッグ→アンマネージコードのデバッグを有効にするにチェックすると、VC++のような例外画面が現れるようになる。ただし.NETの例外ポップアップが使えなくなるようなので、落ちなくなったらチェック外した方がよさそう。

to be continued...?