echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

C#でCOMを使ってみた。番外編

とりあえず、今日から夏休みです。
取得するのに、相当紆余曲折がありましたが…
(というか、話せば話すほど社長は僕は水と油だということを感じる。色々良くないことも多くなってきたし、試用期間までが潮時、って言うか、それすらも持たないだろうな…)

…てな話はともかく、今回は番外編というか、本当に備忘録のような内容です。


C#C++の型

ネイティブC++の関数を呼び出すとき、ちゃんとC#側で対応する引数の型を指定しないといけないけど、主な型の対応はこんな感じ。(とりあえず、関係しそうなものだけ)

C++C#
WORDshort
DWORDuint
LPSTRstring
LPWSTR
LPCSTR
LPCWSTR
void*IntPtr
HANDLE
LPARAM
WPARAM
HRESULTint
MMRESULT


ネイティブC++関数呼び出し時の引数指定

MSDNなどに記載してあるネイティブC++関数呼び出し時の汎用的なルール。
※あくまで「汎用的」であって、これに当てはまらないケースも当然ある。

C++での引数指定C#での引数指定
純粋な「型名」のみ(例:DWORD、WORD)対応する型の「値渡し」(ref、outキーワードなし)
接頭語「LP」+「型名」(例:LPWORD、LPDWORD)対応する型の「参照渡し」(ref、outキーワードあり)
ポインタ渡し(例:LPWORD*、LPDWORD*)IntPtr型渡し
※コールバック関数を指定する場合は「C#でコールバック関数を使う」を参照のこと。


配列のポインタ

関数に構造体配列のポインタを渡す場合、例えばC++だと

STRUCTURE[] structure = new STRUCTURE[2];
func(&structure);

みたいな書き方でOKだが、C#で同じことをやろうとして、例えば

STRUCTURE[] structure = new STRUCTURE[2];
int str_size = Marshal.SizeOf(structure[0]) + Marshal.SizeOf(structure[1]);
IntPtr ptr_structure = Marshal.AllocCoTaskMem(str_size);
Marshal.StructureToPtr(structure, ptr_structure, false);
func(ptr_structure);

とやっても、コンパイルエラーになってしまう。

で、これをうまくやる方法を調べたのだが、結局は原始的な方法かも知れないが、1要素目のptr_structureの値と各要素のサイズを計算して

STRUCTURE[] structure = new STRUCTURE[2];
int str_size = Marshal.SizeOf(structure[0]) + Marshal.SizeOf(structure[1]);
IntPtr ptr_structure = Marshal.AllocCoTaskMem(str_size);
Marshal.StructureToPtr(structure[0], ptr_structure, false);
Marshal.StructureToPtr(structure[1], (IntPtr)((int)ptr_structure + Marshal.SizeOf(structure[0])), false);

みたいにするしか無いみたい。(もちろん、要素数が多いならループなどを使用するべき。)


構造体の「union」をC#で実装する方法

例えば、DirectSoundなどで使用する、WAVEFORMATEXTENSIBLE構造体は、

typedef struct {
  WAVEFORMATEX  Format;
  union {
    WORD  wValidBitsPerSample;
    WORD  wSamplesPerBlock;
    WORD  wReserved;
  } Samples;
  DWORD   dwChannelMask; 
  GUID    SubFormat;
} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE;

となっている。

この構造体の「union」に該当するキーワードはC#にはないが、「StructLayout」と「FieldOffset」の指定をうまく使うことで、構造体のunionと同じことを行うことが出来る。

// FieldOffsetを自分で指定するので、StructLayoutはLayoutKind.Explicitにしないといけない。
[StructLayout(LayoutKind.Explicit)]
private struct WAVEFORMAT_EXTENSIBLE
{
    // wValidBitsPerSample,wSamplesPerBlock,wReservedのFieldOffsetを同じにして「union」と同じことを実現している。
    [FieldOffset(0)]
    public WAVEFORMATEX Format;
    [FieldOffset(20)]
    public ushort wValidBitsPerSample;
    [FieldOffset(20)]
    public ushort wSamplesPerBlock;
    [FieldOffset(20)]
    public ushort wReserved;
    [FieldOffset(22)]
    public uint dwChannelMask;
    [FieldOffset(26)]
    public Guid SubFormat;
}


…すいません。ちょっと手抜き感が否めないですが、今回はこれでご勘弁を。