カクタスソフトウェア
カクタスソフトウェア
サウンド MIDI マルチメディア アプリケーション

構造体

マネージ環境で用いられる構造体はアンマネージ環境のものと互換性がありません。相互運用では構造体の定義にStructLayoutアトリビュートを付加することでアンマネージ互換の構造体を定義します。

アンマネージ互換の構造体は構造体定義の頭に以下の定義を付けます。

[StructLayout(必要なディレクティブをカンマで区切りながら羅列)]

ディレクティブには以下の種類があります。

LayoutKind.Sequential

アンマネージ互換の構造体を定義します。メンバーは上から順に配置されます。以下にPOINT構造体の例を示します。

typedef struct tagPOINT
{
    LONG  x;
    LONG  y;
} POINT;
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
    public int  x;
    public int  y;
}

LayoutKind.Explicit

アンマネージ互換の構造体を定義します。メンバーは[FieldOffset(N)]で指定された位置に配置されます。この方法を用いると同じ位置に複数のメンバーを配置できるため、C/C++のunionの定義に用いられます。以下にMMTIME構造体の例を示します。

typedef struct mmtime_tag
{
    UINT            wType;      /* indicates the contents of the union */
    union
    {
        DWORD       ms;         /* milliseconds */
        DWORD       sample;     /* samples */
        DWORD       cb;         /* byte count */
        DWORD       ticks;      /* ticks in MIDI stream */

        /* SMPTE */
        struct
        {
            BYTE    hour;       /* hours */
            BYTE    min;        /* minutes */
            BYTE    sec;        /* seconds */
            BYTE    frame;      /* frames  */
            BYTE    fps;        /* frames per second */
            BYTE    dummy;      /* pad */
            BYTE    pad[2];
        } smpte;

        /* MIDI */
        struct
        {
            DWORD songptrpos;   /* song pointer position */
        } midi;
    } u;
} MMTIME;
[StructLayout(LayoutKind.Explicit)]
public struct MmTime
{
    [FieldOffset(0)]
    public uint     wType;          // indicates the contents of the union

    [FieldOffset(4)]
    public uint     ms;             // milliseconds
    [FieldOffset(4)]
    public uint     sample;         // samples
    [FieldOffset(4)]
    public uint     cb;             // byte count
    [FieldOffset(4)]
    public uint     ticks;          // ticks in MIDI stream

    // SMPTE
    [FieldOffset(4)]
    public byte     hour;           // hours
    [FieldOffset(5)]
    public byte     min;            // minutes
    [FieldOffset(6)]
    public byte     sec;            // seconds
    [FieldOffset(7)]
    public byte     frame;          // frames
    [FieldOffset(8)]
    public byte     fps;            // frames per second
    [FieldOffset(9)]
    public byte     dummy;          // pad
    [FieldOffset(10)]
    public byte     pad0;
    [FieldOffset(11)]
    public byte     pad1;

    // MIDI
    [FieldOffset(4)]
    public uint     songptrpos;     // song pointer position
}

Pack=N

アライメントをNにします。デフォルトでは8の様に大きな値になっていることがあります。実装しようとする構造体のメンバーに隙間ができないように適切な値を設定します。

CharSet=CharSet.Ansi

構造体で用いる文字をAnsiとします。

CharSet=CharSet.Unicode

構造体で用いる文字をUnicodeとします。

これらのディレクティブを用いた例を示します。

[StructLayout(LayoutKind.Sequential, Pack=2, CharSet=CharSet.Ansi)]
public struct MmIoInfo
{
// 本体は省略
}

構造体は値型なので、関数の引数としてそのまま用いた場合、マーシャリングされた構造体全体がスタックにコピーされ、関数が呼び出されます。引数に構造体へのポインターを必要とする場合は、変数をrefあるいはoutで修飾する必要があります。

クラスの利用

以下の関数は引数にMMCKINFO構造体への参照を取ります。そして検索条件によってlpckParentパラメータをNULLにするよう指定されています。

typedef struct _MMCKINFO
{
    // 本体は省略
} MMCKINFO, FAR *LPMMCKINFO;

MMRESULT mmioDescend(
    HMMIO       hmmio,
    LPMMCKINFO  lpck,
    LPMMCKINFO  lpckParent,
    UINT        wFlags);

これをC#へ移植すると以下の定義となります。

[StructLayout(LayoutKind.Sequential, Pack=4, CharSet=CharSet.Ansi)]
public struct MmCkInfo
{
    // 本体は省略
}

int mmioDescend(
    IntPtr              hmmio,
    [In] ref MmCkInfo   lpck,
    [In] ref MmCkInfo   lpckParent,
    int                 wFlags);

しかしMmCkInfoは値型変数であるため、その参照であるlpckParentもIntPtr.Zeroにすることは許されません。一つの解決策は関数のオーバーライドを作ることです。ここでIntPtr.Zeroを設定したい引数をIntPtrと定義したバージョンを作ります。lpckParentが有効な値のとき上の定義が、lpckParentがIntPtr.Zeroのときは下の定義が採用されます。

int mmioDescend(
    IntPtr              hmmio,
    [In] ref MmCkInfo   lpck,
    IntPtr              lpckParent,
    int                 wFlags);

しかし変数が多数ある場合や、組み合わせが複雑な場合、この方法では対応できません。そこで構造体をクラスとして実装する方法を紹介します。以下に例を示します。

[StructLayout(LayoutKind.Sequential, Pack=4, CharSet=CharSet.Ansi)]
public class MmCkInfo
{
    // 本体は省略
}

int mmioDescend(
    IntPtr                                              hmmio,
    [In, MarshalAs(UnmanagedType.LPStruct)] MmCkInfo    lpck,
    [In, MarshalAs(UnmanagedType.LPStruct)] MmCkInfo    lpckParent,
    int                                                 wFlags);

MmCkInfoの定義はstructを単にclassに置き換えたものです。lpckParent変数は有効なクラスへの参照を代入することはもちろん、値をIntPtr.Zeroにすることができます。IntPtr.Zeroにした場合、C/C++側にはNULLポインターとして伝えられます。マーシャラーへはクラスを構造体への参照として扱うよう[MarshalAs(UnmanagedType.LPStruct)]という指示を行います。

少しややこしい話をするので復習です。変数を直接スタックに書き込んで関数をコールする場合、変数は値型として扱われたことになります。一方、変数が別の場所にあってそこへの参照をスタックに書き込んだ場合、変数は参照型と呼ばれます。

クラスは参照型の変数で、その内容はクラスへの参照です。しかし、その値をスタックに書き込んで関数をコールしたとき、その変数はクラスの値型と呼ぶことができます。C/C++側で構造体への参照は、C#側ではクラスの値型となり、C#側のクラスへの参照は、C/C++側では構造体への参照への参照ということになります。

このクラスは構造体の代替としての機能しか持ちません。プロパティやメソッドを定義しても、アンマネージ側に反映されることはありません。

ドキュメントの先頭へ

カクタスソフトウェア 技術協力 資料室 資料室の広場 SourceForge.jp お問い合わせ