PureData のメッセージ引数と C の関数の引数の順番の関係

Ruby/PureData のために netreceive というオブジェクトのソースを読んでいる時にひとつ疑問点があったのを今日やっと解明したのでメモ。

netreceive オブジェクトのソースコードは Pd のソースツリーの x_net.c にあります。
ここで netreceive のクラスの定義はこんなふうになってます。

static void netreceive_setup(void)
{
    netreceive_class = class_new(gensym("netreceive"),
        (t_newmethod)netreceive_new, (t_method)netreceive_free,
        sizeof(t_netreceive), CLASS_NOINLET, A_DEFFLOAT, A_DEFFLOAT, 
            A_DEFSYM, 0);
}

3つの引数を取ることができて、数値(ポート番号)、数値(udp or tcp)、シンボル(文字列)の順です。この順番は PureDatacanvas 上に置くオブジェクトの引数の順序と一致しています。

ところが、これを受けるインスタンス化の関数 netreceive_new の宣言は下のようになってます。

static void *netreceive_new(t_symbol *compatflag,
    t_floatarg fportno, t_floatarg udpflag)
{
:

順序がいれかわってしまっていて、最後の引数のはずの t_symbol 型の compatflag*1が最初の引数になってしまっています。

おそらく引数の順番についてのルールが何かしらあるのだろうと思ってその時はスルーしていましたが、今日メッセージ送信の処理を少しさかのぼってその部分をみつけました。

m_class.c の pd_typedmess() 関数の中にあります。

void pd_typedmess(t_pd *x, t_symbol *s, int argc, t_atom *argv)
{
:
    t_int ai[MAXPDARG+1], *ap = ai;
    t_floatarg ad[MAXPDARG+1], *dp = ad;

まず、引数を格納する配列が t_int 型と t_floatarg 型の2つ用意されています。そしてメッセージの引数(argv) はその実際の型によって格納先が振り分けられています。
Pointer, Blob, Symbol は t_int 型の ai 配列に詰められます。
Flot の時は ad 配列に詰められます。

そして、ai に詰められた引数の数(narg)によって、関数に渡す引数を変更しています。

        switch (narg)
        {
        case 0 : bonzo = (*(t_fun0)(m->me_fun))
            (ad[0], ad[1], ad[2], ad[3], ad[4]); break;
        case 1 : bonzo = (*(t_fun1)(m->me_fun))
            (ai[0], ad[0], ad[1], ad[2], ad[3], ad[4]); break;
        case 2 : bonzo = (*(t_fun2)(m->me_fun))
            (ai[0], ai[1], ad[0], ad[1], ad[2], ad[3], ad[4]); break;
        case 3 : bonzo = (*(t_fun3)(m->me_fun))
            (ai[0], ai[1], ai[2], ad[0], ad[1], ad[2], ad[3], ad[4]); break;
        case 4 : bonzo = (*(t_fun4)(m->me_fun))
            (ai[0], ai[1], ai[2], ai[3],
                ad[0], ad[1], ad[2], ad[3], ad[4]); break;
        case 5 : bonzo = (*(t_fun5)(m->me_fun))
            (ai[0], ai[1], ai[2], ai[3], ai[4],
                ad[0], ad[1], ad[2], ad[3], ad[4]); break;
        case 6 : bonzo = (*(t_fun6)(m->me_fun))
            (ai[0], ai[1], ai[2], ai[3], ai[4], ai[5],
                ad[0], ad[1], ad[2], ad[3], ad[4]); break;
        default: bonzo = 0;
        }

つまり、オブジェクトの生成時に限らず、class_addmethod で宣言した引数の順番は、GUI上の順番とは一致しますが、コールバックに登録した C の関数の引数の順番はまず Float 以外の型が先に並んで、その後に Float 型の引数が渡されるというふうに順番が変更されます。独自のオブジェクトを実装する際には少し注意が必要です。

*1:old かどうかで挙動を変更する