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

MMRESULT

MMRESULTについて

MMRESULTは、マルチメディア関連のAPIで用いられる変数の型で、関数が戻すリターンコードを示します。その型は、「mmsystem.h」の中で以下のように定義されています。

typedef UINT    MMRESULT;   /* error return code, 0 means no error */

ゼロは特別な値で、エラーが無いことを示します。

#define MMSYSERR_NOERROR      0                    /* no error */

MMRESULTは、いくつかのグループに分類されます。エラーコードはグループを作っており、その先頭の値が次のように決められています。一般的なエラーは、「MMSYSERR_BASE」を先頭に配置され、例えば、MIDI関連のエラーは、「MIDIERR_BASE」を先頭に配置される、という要領です。

#define MMSYSERR_BASE          0
#define WAVERR_BASE            32
#define MIDIERR_BASE           64
#define TIMERR_BASE            96
#define JOYERR_BASE            160
#define MCIERR_BASE            256
#define MIXERR_BASE            1024

ここでサンプルとしてWAV関連の定義を示します。「WAVERR_BASE」から始まる値になっていることを確認してください。

#define WAVERR_BADFORMAT      (WAVERR_BASE + 0)    /* unsupported wave format */
#define WAVERR_STILLPLAYING   (WAVERR_BASE + 1)    /* still something playing */
#define WAVERR_UNPREPARED     (WAVERR_BASE + 2)    /* header not prepared */
#define WAVERR_SYNC           (WAVERR_BASE + 3)    /* device is synchronous */
#define WAVERR_LASTERROR      (WAVERR_BASE + 3)    /* last error in range */

MMRESULTを戻す関数は、各***_BASEで定義されている名前から分かるように、以下のグループが定義されています。

特にウェーブやMIDIなど、サウンドを扱う上で重要な関数群が含まれています。

アンマネージ環境

アンマネージ環境(C/C++)でMMRESULTを利用するには、「stdafx.h」などに「mmsystem.h」をインクルードすれば良く、特に問題となる点はありません。

マネージ環境

一方、マネージ環境(C#)からウェーブやMIDI関連のWin32APIを使うためには、APIの引数や戻り値などを定義した宣言をソースに含めなければなりません。当然、MMRESULTも定義することになります。

本来であれば、マイクロソフトがマネージ環境でウェーブやMIDI関連のWin32APIを使えるよう、定義ファイルやラッパークラスを提供することが望ましいのですが、そのようなものは今の所、存在しません。マネージ環境からWin32APIを呼び出す手続きについては、「プラットフォーム呼び出し」、「マーシャリング」などのセクションで解説しているので参照してください。

C#には「typedef」に相当する機能が無いため、MMRESULTをuint型として定義することができません。その代案として「enum」を用いる方法があります。以下にサンプルコードを示します。

public enum MMRESULT : uint
{
    MMSYSERR_NOERROR        = 0,                    /* no error */

    MMSYSERR_BASE           = 0,
    WAVERR_BASE             = 32,
    MIDIERR_BASE            = 64,

    MMSYSERR_ERROR          = (MMSYSERR_BASE + 1),  /* unspecified error */
    MMSYSERR_BADDEVICEID    = (MMSYSERR_BASE + 2),  /* device ID out of range */
    // 省略

    WAVERR_BADFORMAT        = (WAVERR_BASE + 0),    /* unsupported wave format */
    WAVERR_STILLPLAYING     = (WAVERR_BASE + 1),    /* still something playing */
    // 省略

    MIDIERR_UNPREPARED      = (MIDIERR_BASE + 0),   /* header not prepared */
    MIDIERR_STILLPLAYING    = (MIDIERR_BASE + 1),   /* still something playing */
    // 省略
}

しかしこの定義では、「MMRESULT.MMSYSERR_NOERROR」のように利用するときに長い名前になってしまいます。

ここでエラーコードをグループごとに定義すると、「MMRESULT.NOERROR」や「WAVERR.STILLPLAYING」のようになり使いやすくなります。

public enum MMSYSERR : uint
{
    NOERROR         = 0,            /* no error */

    BASE            = 0,
    ERROR           = (BASE + 1),   /* unspecified error */
    BADDEVICEID     = (BASE + 2),   /* device ID out of range */
    // 省略
}

public enum WAVERR : uint
{
    BASE            = 32,
    BADFORMAT       = (BASE + 0),   /* unsupported wave format */
    STILLPLAYING    = (BASE + 1),   /* still something playing */
    // 省略
}

public enum MIDIERR : uint
{
    BASE            = 64,
    UNPREPARED      = (BASE + 0),   /* header not prepared */
    STILLPLAYING    = (BASE + 1),   /* still something playing */
    // 省略
}

しかしこのままではMMSYSERRのグループがWAVERRやMIDIERRから使えません。MMSYSERRの定義をWAVERRやMIDIERRの中に含める方法もありますが、コーディングが冗長になりスマートな方法とは言えません。

.NET新しい環境へ

C/C++の歴史は長く、関数が戻すエラーコードの定義も様々に発展しました。その結果、全体の構造は複雑で、とても美しいとは言えないものになっています。エラーコードは関数グループ毎に定義され、いくつもの種類が存在します。ここで紹介しているMMRESULTもその中の一つです。これはマルチメディア関連のAPIにだけ適応されるローカルな定義です。

.NETの環境では、関数はエラーコードを戻すより、例外を発生させる手法の方が好まれます。これは例外を使った方が、プログラムがすっきりと書けるという点や、たくさんのエラーコードを管理するより、例外クラスの中で詳細を記述した方が柔軟に対応できるからです。いずれにしろ.NETでは、Win32APIやCOMの複雑なエラーコードと決別しようとしているように見えます。

しかしC#を使ってWin32APIをコールしようとすると、エラーコードとの決別の決心も揺らぎます。上の実装例を見ても分かるように、C#の環境にMMRESULTを定義せざるを得ません。ここでエラーコードに対して、次の2つの考え方があると思われます。

がんばって移植する

これは、mmsystem.hで記述されたエラーコードをC#環境で使えるよう、コーディングを行うというものです。mmsystem.hは、4000行あまりのファイルなので、がんばれば移植も可能でしょう。

移植は最小限に抑える

仮に移植が可能だとしても、.NETに過去の「しがらみ」を持ち込まないという立場もあるでしょう。この場合、MMRESULTのような変数型は定義しない、という方向になります。

クラスによる定義

ここでエラーコードをクラスを使って定義する方法を紹介します。このエラーコードは、intで定義され、特定の変数型を持ちません。そしてエラーコード群は、「MmSysErr」などのクラスによってラップされます。利用するときは、「MmSysErr.NoError」のような表記となります。

public class MMSysErr
{
    internal const int  Base = 0;
    public const int    NoError = 0;                // no error
    public const int    Error = (Base + 1);         // unspecified error
    public const int    BadDeviceID = (Base + 2);   // device ID out of range
    // 省略
}

public class WavErr
{
    internal const int  Base = 32;
    public const int    BadFormat = (Base + 0);     // unsupported wave format
    public const int    StillPlaying = (Base + 1);  // still something playing
    // 省略
}

public class MidiErr
{
    internal const int  Base = 64;
    public const int    Unprepared = (Base + 0);    // header not prepared
    public const int    StillPlaying = (Base + 1);  // still something playing
    // 省略
}

ウェーブ関連、MIDI関連のAPIは、MMRESULTを戻すのではなく、intを戻すと定義します。例えば「midiOutLongMsg」関数は、以下のように宣言します。

[DllImport("winmm.dll")]
public static extern int midiOutLongMsg(IntPtr hmo, ref MidiHdr pmh, int cbmh);

表記の違い

Pascal 形式

Win32APIでは、構造体の名前や変数の型は、すべて大文字で表現します。しかし.NETではすべて大文字から成る表記法は用いられません。その代わり単語の頭だけを大文字にし、その他の文字を小文字で表現する「Pascal 形式」が用いられます。一例を示します。

 Win32 .NET 
MMSYSERR MMSysErr
 BADDEVICEID  BadDeviceID
 STILLPLAYING  StillPlaying

ここでは.NETに移植するにあたって、ルールに従って変数名を書き換えています。

ただしIO(InputOutput)のように1文字が単語の省略形として意味を持つときは、結果としてすべて大文字になる場合があります。

unsingnedの制限

Win32APIでは、負にならない変数をunsigned属性で表現する習慣があります。以下にWAVEFORMATEX構造体の例を示します。すべての変数が、WORDあるいはDWORDで定義されています。

typedef struct tWAVEFORMATEX
{
    WORD        wFormatTag;         /* format type */
    WORD        nChannels;          /* number of channels (i.e. mono, stereo...) */
    DWORD       nSamplesPerSec;     /* sample rate */
    DWORD       nAvgBytesPerSec;    /* for buffer estimation */
    WORD        nBlockAlign;        /* block size of data */
    WORD        wBitsPerSample;     /* number of bits per sample of mono data */
    WORD        cbSize;             /* the count in bytes of the size of */
                                    /* extra information (after cbSize) */
} WAVEFORMATEX, *PWAVEFORMATEX, NEAR *NPWAVEFORMATEX, FAR *LPWAVEFORMATEX;

一方、マネージ環境の例を見てみます。以下の定義は、Managed DirectSoundで定義されているウェーブフォーマット構造体の定義です。

namespace Microsoft.DirectX.DirectSound
{
    public struct WaveFormat
    {
        public WaveFormat();
        public int              AverageBytesPerSecond { get; set; }
        public short            BitsPerSample { get; set; }
        public short            BlockAlign { get; set; }
        public short            Channels { get; set; }
        public WaveFormatTag    FormatTag { get; set; }
        public int              SamplesPerSecond { get; set; }
        public override string  ToString();
    }
}

これを見るとすべての変数はsigned属性で定義されています。チャンネル数やサンプリング周波数など、shortやintのレンジがあればオーバーフローすることがないので、正しく表現できることが分かります。

.NETでは、特にunsigned属性でなければならない理由がない限り、signed属性を用います。

Win32APIの定義を行うとき、元の定義に忠実にunsigned属性で定義するか、.NETの習慣に従ってsigned属性に書き換えるかは、判断の分かれるところでしょう。

ここで象徴的な例として、sizeof()演算子に関する問題を紹介します。

次の関数は、ウェーブ再生で用いるバッファを準備するwaveOutPrepareHeader()関数です。引数の3番目には、WAVEHDR構造体のサイズをバイト単位で指定するよう指示があります。

MMRESULT waveOutPrepareHeader(
    HWAVEOUT    hwo,
    LPWAVEHDR   pwh,
    UINT        cbwh)

sizeof()は、オブジェクトのサイズを求める演算子で、unsigned intの値を戻します。そのため以下のコードはすんなりと定義に当てはまり、何の問題も起こしません。

MMRESULT result = waveOutPrepareHeader(m_hWaveOut, pWaveHdr, sizeof(WAVEHDR));

一方、.NETでアンマネージ互換の構造体のサイズを求めるときは、Marshal.SizeOf()を用います。WaveHdr構造体のサイズを求めるときは、以下のようになります。

Marshal.SizeOf(typeof(WaveHdr))

このSizeOf()は、.NETの習慣に従ってintを戻します。

もし、waveOutPrepareHeader関数の3番目の変数のように、サイズを指定する変数をunsigned属性で定義した場合、.NET環境では必ずMarshal.SizeOf()演算子の前に(uint)のキャストが必要になります。

常にキャストが必要になるなら、始めからintで定義しておけば、ということになります。

enumとclass

.NET環境でアンマネージ環境のエラーコードを利用する場合、その移植作業は避けて通れません。MMRESULTはまだ数が少ないので、がんばれば完全な定義を書けるかも知れません。しかし、COMで用いられるHRESULTは規模が非常に大きく、移植作業は困難です。そこで多くの場合、当面使う分だけを記述し、必要があれば追加するという方法を取ります。

C#でenumを用いてMMResultやHResultなどの型を定義すると、元の定義ファイルを修正することなしに新しいエラーコードを追加することはできません。定義ファイルの変更が簡単なら問題ありませんが、複数のスタッフやプロジェクトで共有する場合、定義ファイルのバージョン管理が必要になります。

一方、関数の戻り値をintで定義し、エラーコードをクラスでラップすれば、基本の定義ファイルを修正することなしに拡張することができます。

public partial class MidiErr
{
    internal const int  Base = 64;
    public const int    Unprepared = (Base + 0);    // header not prepared
    public const int    StillPlaying = (Base + 1);  // still something playing
    // 省略
    public const int    LastError = (Base + 7);     // last error in range
}

元のクラスを拡張するため新しいコードを書きます。ここで鍵になるのが「partial class」です。

public partial class MidiErr
{
    public const int    HogeError = (LastError + 1);    // My Hoge Error
}

もう一度MMRESULTについて

.NET環境でマルチメディア関連のWin32APIを用いるとき、関数が戻す値は、intとする。というのが.NET流の解釈でしょう。もちろんこれは一つの考え方であり、こうしなければ動かないというものではありません。

.NET環境の中にWin32APIの環境を再現しようとする考えと、.NETの流儀に溶け込むよう解釈し直そうとする2つの立場があります。本技術文書は、後者の立場を取っていますが、どちらを選ぶかは読者の判断です。

ドキュメントの先頭へ

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