Chapter 6:
インスタンス生成関数の書き方

インスタンス生成関数は、オブジェクトの新しいコピーを作る必要がある場合に呼び出されます。これは、ファイルが読み込まれた結果として、またはユーザがオブジェクトボックスにオブジェクト名を入力した場合(ユーザインタフェースオブジェクトでは、パッチャーウィンドウのパレットから選ばれた場合)に生じます。

Maxから呼び出されるすべての関数と同様に、オブジェクトのインスタンス生成関数は、あなたが指定したアーギュメントの型リストに基づいています。他のメソッドと違って、インスタンス生成関数のアーギュメントの型指定は、addmessではなく、初期化時に setup を呼び出すことによって行われます。

インスタンス生成関数のアーギュメントは、1つの例外を除いて、「データ型とアーギュメントリスト」の章で述べられたものと同じです。応答を引き起こすメッセージは、クラスのオブジェクトではなく、特別な new オブジェクトへ送られるため、最初の引数はクラスのオブジェクトへのポインタにはなっていません。new オブジェクトにポインタを渡すことは意味がないため、Maxは単に最初の引数を完全にスキップします。例えば、あなたの生成関数のアーギュメントの型リストが次のようなものである場合

A_DEFLONG, 0

生成関数は次のように宣言されます。

void *myObject_new (long arg);

インスタンス生成関数の仕事は、あなたのクラスのインスタンスを作ることです。生成関数はオブジェクトが生成された場合には、そのオブジェクトへのポインタを、また、問題が生じた場合には0を返さなければなりません。

一般的に、最初に行うのは、クラスのインスタンスにメモリを割り当てるために newobject を呼び出し、すべてのMaxオブジェクトで必要とされるシステムレベルの初期化を行うことです。次に、オブジェクトのフィールドの必要な初期化を追加します。例えばアウトレットやClock(オブジェクトは自分自身のスケジューリングができます)、またはQelem(オブジェクトが、割り込みレベルで行うことができない描画や何かのアクションを実行すること)を追加したいと思うかもしれません。いくつかのフィールドはあなたのオブジェクト生成関数が受け取ったアーギュメントに基づく値に割り当てられます。例えば、あなたがこのようにタイプした場合

オブジェクトの生成関数はアーギュメントの'20'を受け取り、新しく作られたオブジェクトの内部に保存します。

注:オブジェクトのパッチャーや、パッチャーの中のt_box構造体にアクセスしたい場合には、オブジェクト生成関数の中で、それらに対する参照を獲得しなければなりません。これを行う方法については、Chapter 11 の patcher_dirty 関数に関する記述を参照して下さい。

インレットとアウトレット

インレットアウトレットは、あなたのオブジェクトが、普通に他のオブジェクトとコミュニケートする手段です。これらは、オブジェクト間のコネクションを保持し、このコネクションを「通して」簡単にメッセージの送信を行うことができるようにするための構造です。

オブジェクトが作られると、通常1つのインレットが与えられます。これは、t_object 構造体の中で参照され、あなたのオブジェクトの第1のフィールドに置かれなければなりません。インレットが現れないオブジェクトもある点に注意して下さい。これらは「インレットはいらないよ」という意味の特別なフラグをクラスのフィールドでセットしています。インスタンス生成関数の中で newobject を呼び出す直前に、あなた自身がこのフラグをセットすることができます。

void *myclass; /* setup の呼出しによって初期化される */ void *myobject_new (void) { myObject *x; x = newobject(myclass); class_noinlet(myclass); /* フィールドの初期化を追加する */ return (x); }

インスタンス生成のためのルーチン

これらの関数は、あなたのクラスの新しいオブジェクトを作り、インレットとアウトレットを与える機能を持っています。

newobject

     
  クラスのインスタンスにメモリ空間を割り当て、そのオブジェクトヘッダの初期化を行うために newobject を使います。
     
  void *newobject (void *class);
     
  class メインルーチンの中で、setup関数によって初期化されるグローバルクラス変数。
     
  生成関数の中で、クラスのインスタンスを作る場合に newobject を呼び出します。 newobject はクラスのオブジェクトに適した量のメモリを割り当て、オブジェクトにクラスへのポインタをインストールします。これによって、メッセージを受信した際に、クラスのメソッドを利用して応答することができるようになります。

インレット作成ルーチン

追加のインレットを作成するために、オブジェクト生成関数の中で使用される関数がいくつかあります。一度作成されたインレットは、それ以上何もする必要はありません。そのため、これらの関数は作成されたインレットへのポインタを返すことはありません。インレットの主要な目的は、「存在する」ということです。それによって、アウトレットはそれらに「食い付く」ことができます。Maxでは、オブジェクト間のコミュニケーションはすべてアウトレットの動作によって進められます。インレットはオブジェクトの freeメソッド(メモリ開放メソッド)によって開放される必要はありません。この点に注意して下さい。

intin

     
  整数のみを受け取るインレットを作成するために、intin を使います。
     
  void intin (void *object, short index)
     
  object あなたのオブジェクト
  index 1〜9でインレットの場所を指定します。1は最も左のインレットのすぐ右隣を表します。
     
 

intin は整数インレットを作ります。これは新しいオブジェクトへのポインタと、1〜9の整数 n を引数として取ります。数値には、得られるメッセージのタイプを指定します。これによって1つのインレットを他のインレットから識別することができます。例えば、インレット1に送られた整数は in1というメッセージタイプになり、インレット4に送られた浮動小数(float)は ft4というタイプになります。これらのメッセージに応答するメソッドを追加するには、addinxaddftx を使います。

追加のインレットを作成する順序は重要です。最も右のインレットに一番大きい数を持つ in- や ft- メッセージを受け持たせる場合(通常の場合、このようにします)、最初に一番大きい数値をもつメッセージのインレットを作成しなければなりません。4つの整数インレットを追加する場合(合計5つになります)次のような形になります。

 

floatin

     
  浮動小数のみを受け取るインレットを作成するために、floatin を使います。
     
  void floatin (void *object, short index)
     
  object あなたのオブジェクト
  index 1〜9でインレットの場所を指定します。1は最も左のインレットのすぐ右隣を表します。
     
 

この関数は浮動小数インレットを作ります。これは浮動小数を扱うという点以外は、 intin と同様です。


inlet_new

     
  特定のメッセージを受け取る、またはあらゆるメッセージを受け取るインレットを作るために、inlet_new を使います。
     
  void inlet_new (void *object, char *msg)
     
  object あなたのオブジェクト
  msg メッセージの文字列, あらゆるメッセージを受け取る場合は 0Lを指定します。
     
 

inlet_new は汎用のインレットを作成します。左端のインレット以外のインレットでも特別なメッセージを受信する環境が必要な場合、これを使うことができます。

特定のメッセージを受け取るインレットを作成するためには、メッセージの文字列を渡します。例えば、bangメッセージだけを受け取るインレットを作成するには、次のようにします…

inlet_new (myObject,"bang");

どのようなメッセージでも受信できるインレットを作成する場合は、msg に0を渡します…

inlet_new (myObject,0);

「プロキシ」は汎用インレットのための代理のメソッドですが、これには多くの利点があります。上の例のようなインレットを複数作成した場合、どのインレットがメッセージを受信したかを知る方法がありません。後述の「プロキシの使用」セクションの説明を参照して下さい。


inlet_4

     
  受信したメッセージを解釈して、コントロールを行なうことができるインレットを作成するためには、inlet_4 を使います。
     
  void inlet_4 (void *owner, void *dst, t_symbol *in, t_symbol *out);
     
  owner オブジェクト自身
  dst メッセージを受け取るオブジェクト、インレットが「閉じ」られていれば 0 になります。dst は、あなたのオブジェクト自身であるという規約になっています。
  in 一致する受信メッセージ
  out インレットによって作られるメッセージ。これは、オブジェクトのメソッドの1つと一致するメッセージか、オブジェクトの anything メソッドの2番目の引数として受け取られる t_symbol になります。
     
 

inlet_4 は最も一般的なインレット生成関数です。これによって、受信したメッセージに対してオブジェクト自身の解釈を指定することができるようになります。オブジェクト自身へのポインタを owner として渡す必要がありますが、 解釈されたメッセージを受信するように、dst パラメータによってその他のオブジェクトを指定することができます。例えば、オブジェクト自身が持つアウトレットの1つを指定することができます。dst パラメータは、後述する inlet_to 関数を用いて、動的に変更(または0をセット)することができます。しかしこれを行うためには、オブジェクトの中に inlet_4 が返す Inlet オブジェクトを保存しておかなければなりません。gate オブジェクトは、このテクニックを使って、インレットをいくつかのアウトレットの1つに割り当てたり、全く出力しないようにしています。

ここでは、1つの例として、inlet_4によって、intin(x,1)がどのように実現されるかを示します。

inlet_4 (myObject, myObject, gensym("int"), gensym("in1"));


inlet_to

     
  インレットによって対応させられた受信メッセージの行き先を変更するために、 inlet_toを使います。
     
  void inlet_to (Inlet *in, t_object *newdest);
     
  in 変更するインレット
  newdest インレットで対応させられたメッセージを受け取る新しいオブジェクト(inlet_4で指定されたような、インレットのオーナーに送られます)。newdest が0の場合、インレットは閉じられ、メッセージはどのオブジェクトにも受け取られません。
     
 

注:このルーチンは初期化関数ではあまり使用されませんが、インレットに影響を及ぼすことのできる唯一のルーチンであるため、ここで記述しました。

アウトレット作成ルーチン

オブジェクトはデフォルトではアウトレットを持たないため、アウトレットを作成するにはルーチンを使用しなければなりません。インレットと同様、アウトレットも整数、浮動小数、またはその他の指定されたメッセージといった型による分類ができます、これから述べるすべてのアウトレット生成関数は、生成されたアウトレットへのポインタを戻り値とし、最初の引数(アーギュメント)としてオブジェクト自身へのポインタを要求します。アウトレットは明示的に参照する必要があるので、アウトレットへのポインタをオブジェクトの中に保持することは大切です。左端のアウトレットは次のようにアクセスされます。

myObject->m_ob.o_outlet

この例では、オブジェクトの最初のフィールドの t_object を仮に m_ob としています。

オブジェクトの開放ルーティンで、アウトレットのメモリを開放する必要がないということを心にとめておいて下さい。

bangout

     
  常にbangメッセージを送るアウトレットを作成するために、bangout を使います。
     
  Outlet *bangout (void *owner);
     
  owner オブジェクト自身
     
 

汎用のアウトレットから bang メッセージを送信することはできますが、bangout を使ってアウトレットを作成すると、ユーザが接続しようとした時、Maxの型チェックによって bang メッセージを受信できないオブジェクトにアウトレットを接続することを拒否させることができます。bangout は作成されたアウトレット(へのポインタ)を返します。


floatout

     
  常にfloat(浮動小数)メッセージを送るアウトレットを作成するために、floatoutを使います。
     
  Outlet *floatout (void *owner);
     
  owner オブジェクト自身

intout

     
  常にint(整数)メッセージを送るアウトレットを作成するために、intout を使います。
     
  Outlet *intout (void *owner);
     
  owner オブジェクト自身
     
 

次の例は、整数を送信するアウトレットを作成するintoutの使用例です。

mynewobject->m_intout = intout(mynewobect);


listout

     
  常にlistメッセージを送信するアウトレットを作成するためにlistoutを使います。
     
  Outlet *listout (void *owner);
     
  owner オブジェクト自身

outlet_new

     
  特定の標準でないメッセージ、またはあらゆるメッセージを送信するアウトレットを作成するために、outlet_new を使います。
     
  Outlet *outlet_new (void *owner, char *msgType);
     
  owner オブジェクト自身
  msgType このアウトレットから送信されるメッセージを指定するCの文字列。0の場合は、様々なメッセージを送信するアウトレットを表します。
     
 

アウトレットから、様々なデータ型やメッセージを送信する場合、outlet_new を関数を使用して、msgType に0を渡します。これは、最も基本的なタイプのアウトレットを作成します。この種のアウトレットの柔軟性による利点は次のような事実のバランスの上に立っています。Maxは、パッチが作られた時ではなく、メッセージが送られるたびにリアルタイムでメッセージの探索を行わなければなりません。たとえ他のアウトレットにふさわしいメッセージであったとしてもです。一方、アウトレットの型が決まっていると、プログラムが実行される前にメッセージの探索が行われるため、パッチャーは速く実行されます。

次は、outlet_newの使用例です。

void *outpointer; outpointer = outlet_new (mynewobject,0L); /* 汎用アウトレット*/


Proxyの使用

Maxオブジェクトが、メッセージを受信したインレットを検出するために使用するメカニズムであるリコール(recall)は、メッセージを他のメッセージに変換します。例えば、intin でインレットを作成した場合、左端のインレットのすぐ右側のインレットへの int が i n1に変換される場合です。このメカニズムは左端のインレット以外のインレットが受信できるメッセージの型を制限します。実際に、左端の「インレット」は完全なインレットではなく、むしろ、あなたのオブジェクトを直接参照していると言えます。

インレットですべてのメッセージを受信することができ、どのインレットにメッセージが届いたかを判断できるようにする必要が生じる場合があります。例えば、個々のインレットを独立したトラックとして使える「マルチトラック・レコーダ」オブジェクトを考えてみましょう。この場合、3番目のトラックに録音をする際に、左端のインレットにrecord3というようなメッセージを送らなくても、直接、3番目のインレットにrecordメッセージを送信できるようにしたいでしょう。さらに、レコーダには、整数だけではなく、それぞれのインレットに届くどんな種類のメッセージでも「記録」することを可能にしておきたいでしょう。

この動作は Proxy オブジェクトを使うことによって可能になります。Proxyはインレットに届いたメッセージをオブジェクトが認識する前に横取りする「中間のオブジェクト」です。そして、インレットで受信した値を保存した後、Proxyはあなたにメッセージを送り、あなたはそのインレットのナンバーをチェックして、適切な動作をすることができます。Proxyオブジェクトは proxy_new によって作成することができますが、これはインレットやアウトレットとは違い、明示的に(freeobject を使って)あなたのオブジェクトの free関数(メモリ開放関数)で破棄しなければなりません。

注:一つのオブジェクトに、標準のインレットとProxyを混在させることはできません。

加えて、Max4.3以降の新しいスレッド処理モデルや、優先度の低いスレッドが優先度の高いスレッドに割り込みを行うことができる能力により、プロキシに関する以前からの仮定はもはや全ての状況で正しいとは言えません。 すなわち、 "id”の値に設定されたインレットナンバーが必ずしも正しいインレットを反映するというわけではないのです。プロキシを作る場合、今までと同様に、引数 "stuffLoc”に整数へのポインタを渡さなければなりません(下記の proxy_new を参照)が、メッセージを受信したインレットを検出するためにこれを使用すべきではなく、その代わりに、proxy_getinlet 関数を使用しなければなりません。この関数はスレッドに関して安全です。しかし、依然としてフィードバックループによる潜在的な問題点があり、左端のインレットに関しては、現時点でも解決不可能です。ユーザがこのようなフィードバックループによって生じる奇妙な動作に対して不満を持つのであれば、このような制御しきれない再入力に対してはdelay あるいは deferlow を使用することを推奨して下さい。

次のコード例では3つの異なったインレットでメッセージを受信するためにProxyを使用しています。これには、bang が到着したインレットナンバを出力表示する bang メソッドのサンプルが含まれています。

ここでは、私たちのオブジェクトの宣言を行っていますが、その際、Proxyオブジェクトの分に加えてProxy オブジェクトがインレットナンバを保存するためのlong 1個分のスペースを作っています。

typedef struct_myobject { t_object m_ob; void *m_proxy[2]; /* 3つのインレットは2つのproxyを必要とします。*/ long m_inletNumber; /* proxyがインレットナンバを格納する場所 */ } myObject;

これは、オブジェクト生成関数です。

void *myObject_new (void) { myObject *x; x = (myObject *)newobject(class); /* 右から左へ proxy オブジェクトを作ります */ m_proxy[1] = proxy_new(x,2,&x->m_inletNumber); m_proxy[0] = proxy_new(x,1,&x->m_inletNumber); return (x); }

そして、これはbangメッセージに対する応答の中に書かれているメソッドです。

void myObject_bang (myObject *x;) { post("message arrived at inlet %ld",proxy_getinlet(x)); }

proxy_new

     
  新しいProxyオブジェクトを生成するために、proxy_new を使います。
     
  Proxy *proxy_new (t_object *owner, long id, long *stuffLoc);
     
  owner オブジェクト自身
  id 特定の Proxy によってメッセージが受信された際に、あなたのオブジェクトには 0でない値が書き込まれます。通常、id はin1、in2 などと同様なインレットの「ナンバー」です。
  stuffLoc idの値が書き込まれる場所へのポインタ
     
 

このルーチンは新しいProxyオブジェクト(インレットを含みます)を作成します。これによって、stuffLoc によって指定された場所にストアされる id の値によってメッセージを識別することができるようになります。オブジェクト内のメモリ開放関数によってすべてのProxyを開放する必要があるので、proxy_new の返すポインタを保存しておかなければなりません。

メソッドが終了した後、Proxyは stuffLoc の場所を0に戻します。これは、Proxy がオブジェクトの左端のインレットへ来るメッセージを見ることがないためです。 stuffLoc の中身が0であれば、左端のインレットでメッセージを受信したことがわかります。Max4.3では、stuffLoc はメッセージを受信したインレットを正しく示しているという保証はありません。そのため、proxy_getinlet(下記)を使って、インレットのナンバを決定して下さい。


proxy_getinlet

     
  メッセージを受信したインレットナンバを得るために、proxy_getinletを使います。
     
 

long proxy_getinlet (t_object *owner)

     
  owner オブジェクト自身
     
 

このルーチンは、メッセージを受信したインレットを返します。引数 "owner”が、proxy オブジェクトではなく、エクスターナルオブジェクトのインスタンスへのポインタでなければならない点に注意して下さい。

 後述のマクロはMacOS9上で動作するエクスターナルの開発を続けているデベロッパにとって役に立つでしょう。これは、開発するエクスターナルがMaxLibと " 弱いリンク(weaklink)" であることを仮定して、proxy_getinlet 関数が存在するかどうかを試すことによって結果を決めています。

#define PROXY_GETTHEINLET(x,id) (proxy_getinlet ? proxy_getinlet(x) : (id))

“weak linking”に関する詳しい情報は、CodeWarrior のドキュメントを参照して下さい。