CocoaのDistributed Objectsにまつわる話 [プログラミング]
昨日のDistributed Objectsの続き。というか、直接関係ないんだけど、ちょっとしたおまけと、そもそもなんでこんなことをやろうと思ったか、ということ。
普通こういうサーバは処理中も他のクライアントからの要求に応える必要がある。これはスレッドを使えば実現できる。
さっきのサーバ側のプロトコルの実装を
実はこんなナマのNSThreadを使わなくても、10.5以降ならNSOperationがあるし、10.6以降ならGrand Central Dispatchがある。この例ぐらいならどれを使ってもそれほど差はないけど、もうちょっと難しいことをしようとすると、NSOperationやG.C.Dのほうが簡単になる。
ところで今頃気がついたけど、wait:(id)client forInterval:(NSTimeInterval)intervalっていうシグネチャは、サーバがクライアントを待つように読めてしまう。単なる例だからいいとはいえ、こういうのって誤解を招くのでよくないな。
それはbTop-1(いまではディスコンでbTop-2になってむき出しのボードではなく、ちゃんと箱に入っている)という簡単なものだった。
もう6、7年前、似たようなことをするために会社で買った。これは安くて(当時2万円ほどだった)USB2.0で繋ぐことができて、しかもMac OS X用のドライバが存在していたので使ってみようと思った。簡単にプログラムできてちゃんと動いたんだけど、その開発テーマがポシャってしまったので、そのボードを仙台まで持ってきていた。今回大学で自分のMacを使って好き勝手なことができるので、生き返らせて使い始めた。
ところが、だんだん制御や監視のアイテムが増えてくるにつれて、使い勝手が悪くなってきた。ドライバがひとつのプロセスからしか使えないのである。いろいろな制御や監視はひとつのプロセスとは限らない。例えば光学シャッタの開け閉めのタイミングを決めるアプリと光量をモニタするアプリが同じプロセスになっているよりは、別のアプリでそれぞれ独立して使えるほうが便利である。ところがどちらも同じbTop-1を経由してデータをやりとりするので、別アプリというわけにはいかない。
そこで、bTop-1を制御するサーバを作って、他のアプリはそのサーバに対してデータを書いたり読んだりすることで独立できるようにしようと思った。そしてどうせなら、むき出しのボードではなく、コンパクトでこぎれいなNational InstrumentsのUSB-6008を新しく買ってそれに書くことにした。
ちなみにNIはLabVIEWという仮想計測ソフトウェアで有名だけど、最初LabVIEWはMac用のソフトでMac-IIのNuBusスロットに入るボードを制御するソフトだった。それからすぐWindowsに移植されて、そのうちMacからはフェイドアウトしていった。Macで使えるハードがどんどん少なくなっていくのが悲しかった。なつかしい。
そのNIのインターフェイスはシンプルなものだけど安くて(やっぱり2万円)しかもNIらしくしっかりした作りで(中を開けると半導体はDIOを持つUSBコントローラとA/D、D/Aのチップだけ)、なんとMac用ドライバがある。NIはいつのまにかMacに復活していた。すばらしい。
NIの多くのハードウェアをサポートする低レベルのCでのドライバがNI-DAQmx Baseで、WindowsとMac OS XとLinuxで同じものが動く。
ドライバは非常に簡単で、タスクを作って仮想チャンネルをそのタスクに接続する、という考え方でできている。仮想チャンネルというのは実際のI/Oポートに対応するソフトウェアオブジェクトで、タスクとはそれを制御するオブジェクトになっている。タスクには複数の仮想チャンネルがあっていい。動作の制御はタスクに対して開始、停止やデータの読み書きのC関数を発行することで行う。
簡単でいいんだけど、古くさい部分もある。まず、ひとつひとつの関数のパラメータがやたらと多い。
例えばアナログ電圧入力チャンネルを作る関数は
それになにより、USBのホットプラグに対応していない。USB接続以外のハードゥエアも同じドライバで使えるようになっているのである程度はしかたないかもしれないけど、使っている途中でUSBケーブルが抜けると単にエラーが起きるだけで、ケーブルを繋ぎ直してもソフトウェア上は回復する手段がない。「USBケーブルはアプリが立ち上がる前に繋がっていて、アプリの終了前に抜けてはいけない」と言う仕様になっている。
さらに、コンパイルでもトラブった。なぜか10.6のsdkでコンパイルできない。10.5だと通る。なにがいけないのかよくわからない。また、実行時もタスクを作るのに、10秒近くかかる。これも何やってるんだかわからないのでハングしてるんだと思ってしまった。時間のかかる処理は関数をわけて欲しいなあ。
文句はそのくらいにして、面白い部分もある。普通こう言うハードウェアドライバはかならず排他制御のためにopen/closeの手順があるはずだけど、このNIのドライバにはそれがない。
違うプロセスから同時にアクセスするのを防ぐため、ハードを使いたいプロセスはまずそのハードをopenしてから使う。openすると、あとから他のプロセスがopenしようとしたとき失敗するようになっていて、同時にふたつ以上のプロセスから使えないようにするのが普通である。
どうなっているのかわからない。NIのことだからまた難しいことを中でしていて、ハードの端子レベルで排他制御して、複数のプロセスからの同時アクセスを許しているのかもしれない。すくなくとも今回買ったUSB-6008はふたつのプロセスが独立にタスクを作ってチャンネルを接続することはできた。同じポートに別々に出力したときどうなるかは調べていない。
それでちゃんと動作するか確認するためには外に回路を繋がないといけないから面倒でやっていない。
それに今回ドライバを直接アクセスするのはひとつのサーバプロセスに限定して、読み書きはそのサーバにプロセス間通信でデータをやりとりすることで行うように書くと、クライアント側のプログラミングではNIのハードの詳細を忘れていても書ける。これはわかりやすいので、今回はNIのドライバがどのくらい賢いのか試すリスクは犯さないことにした。
もう、Distributed Objectが何だったか忘れるような話だわ。
4 おまけ
上の例ではサーバ側はひとつの処理を始めるとその間クライアント側の要求に応えられない。スリープしている間もCallボタンは押せるけど、サーバ側での動作(この場合はスリープ)は、2回目のボタンの押下は通信路のどこかで待たされて、最初のが終わってから2回目のが開始される。普通こういうサーバは処理中も他のクライアントからの要求に応える必要がある。これはスレッドを使えば実現できる。
さっきのサーバ側のプロトコルの実装を
- (oneway void)wait:(id)client forInterval:(NSTimeInterval)intervals { [NSThread detachNewThreadSelector:@selector(waitWith:) toTarget:self withObject:[NSArray arrayWithObjects:client, [NSNumber numberWithDouble:intervals], nil] ]; } - (void)waitWith:(NSArray *)holder { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; id client = [holder objectAtIndex:0]; NSTimeInterval intervals = [[holder objectAtIndex:1] doubleValue]; NSTimeInterval result; NSDate *startDate = [NSDate date]; usleep(intervals * 1000000); result = - [startDate timeIntervalSinceNow]; [client resultFrom:self withValue:result]; [pool drain]; }みたいにすればいい。引数の渡し方が不自然になってるけど、まあこれは今回だけということで。
実はこんなナマのNSThreadを使わなくても、10.5以降ならNSOperationがあるし、10.6以降ならGrand Central Dispatchがある。この例ぐらいならどれを使ってもそれほど差はないけど、もうちょっと難しいことをしようとすると、NSOperationやG.C.Dのほうが簡単になる。
ところで今頃気がついたけど、wait:(id)client forInterval:(NSTimeInterval)intervalっていうシグネチャは、サーバがクライアントを待つように読めてしまう。単なる例だからいいとはいえ、こういうのって誤解を招くのでよくないな。
5 いきさつ
そもそもなんでこんなことをしようと思ったかというと、今、大学で手作り装置を使ってあるプロセスの開発をしている。その装置の動作の制御や監視にアナログとデジタルの両方のI/Oを持ったボードを使っている。それはbTop-1(いまではディスコンでbTop-2になってむき出しのボードではなく、ちゃんと箱に入っている)という簡単なものだった。
もう6、7年前、似たようなことをするために会社で買った。これは安くて(当時2万円ほどだった)USB2.0で繋ぐことができて、しかもMac OS X用のドライバが存在していたので使ってみようと思った。簡単にプログラムできてちゃんと動いたんだけど、その開発テーマがポシャってしまったので、そのボードを仙台まで持ってきていた。今回大学で自分のMacを使って好き勝手なことができるので、生き返らせて使い始めた。
ところが、だんだん制御や監視のアイテムが増えてくるにつれて、使い勝手が悪くなってきた。ドライバがひとつのプロセスからしか使えないのである。いろいろな制御や監視はひとつのプロセスとは限らない。例えば光学シャッタの開け閉めのタイミングを決めるアプリと光量をモニタするアプリが同じプロセスになっているよりは、別のアプリでそれぞれ独立して使えるほうが便利である。ところがどちらも同じbTop-1を経由してデータをやりとりするので、別アプリというわけにはいかない。
そこで、bTop-1を制御するサーバを作って、他のアプリはそのサーバに対してデータを書いたり読んだりすることで独立できるようにしようと思った。そしてどうせなら、むき出しのボードではなく、コンパクトでこぎれいなNational InstrumentsのUSB-6008を新しく買ってそれに書くことにした。
ちなみにNIはLabVIEWという仮想計測ソフトウェアで有名だけど、最初LabVIEWはMac用のソフトでMac-IIのNuBusスロットに入るボードを制御するソフトだった。それからすぐWindowsに移植されて、そのうちMacからはフェイドアウトしていった。Macで使えるハードがどんどん少なくなっていくのが悲しかった。なつかしい。
そのNIのインターフェイスはシンプルなものだけど安くて(やっぱり2万円)しかもNIらしくしっかりした作りで(中を開けると半導体はDIOを持つUSBコントローラとA/D、D/Aのチップだけ)、なんとMac用ドライバがある。NIはいつのまにかMacに復活していた。すばらしい。
5.1 NI-DAQmx Baseドライバ
ますます脇道に逸れてしまうんだけど、NIのドライバの話をちょっと書いておく。NIの多くのハードウェアをサポートする低レベルのCでのドライバがNI-DAQmx Baseで、WindowsとMac OS XとLinuxで同じものが動く。
ドライバは非常に簡単で、タスクを作って仮想チャンネルをそのタスクに接続する、という考え方でできている。仮想チャンネルというのは実際のI/Oポートに対応するソフトウェアオブジェクトで、タスクとはそれを制御するオブジェクトになっている。タスクには複数の仮想チャンネルがあっていい。動作の制御はタスクに対して開始、停止やデータの読み書きのC関数を発行することで行う。
簡単でいいんだけど、古くさい部分もある。まず、ひとつひとつの関数のパラメータがやたらと多い。
例えばアナログ電圧入力チャンネルを作る関数は
int32 DAQmxBaseCreateAIVoltageChan (TaskHandle taskHandle, const char physicalChannel[ ], const char nameToAssignToChannel[ ], int32 terminalConfig, float64 minVal, float64 maxVal, int32 units, const char customScaleName[ ]);というような調子で、30年前のHPのBASICによる測定制御のコマンドを思い出す。ようするにハードウェアの機能をそのまま表したものになっている。
それになにより、USBのホットプラグに対応していない。USB接続以外のハードゥエアも同じドライバで使えるようになっているのである程度はしかたないかもしれないけど、使っている途中でUSBケーブルが抜けると単にエラーが起きるだけで、ケーブルを繋ぎ直してもソフトウェア上は回復する手段がない。「USBケーブルはアプリが立ち上がる前に繋がっていて、アプリの終了前に抜けてはいけない」と言う仕様になっている。
さらに、コンパイルでもトラブった。なぜか10.6のsdkでコンパイルできない。10.5だと通る。なにがいけないのかよくわからない。また、実行時もタスクを作るのに、10秒近くかかる。これも何やってるんだかわからないのでハングしてるんだと思ってしまった。時間のかかる処理は関数をわけて欲しいなあ。
文句はそのくらいにして、面白い部分もある。普通こう言うハードウェアドライバはかならず排他制御のためにopen/closeの手順があるはずだけど、このNIのドライバにはそれがない。
違うプロセスから同時にアクセスするのを防ぐため、ハードを使いたいプロセスはまずそのハードをopenしてから使う。openすると、あとから他のプロセスがopenしようとしたとき失敗するようになっていて、同時にふたつ以上のプロセスから使えないようにするのが普通である。
どうなっているのかわからない。NIのことだからまた難しいことを中でしていて、ハードの端子レベルで排他制御して、複数のプロセスからの同時アクセスを許しているのかもしれない。すくなくとも今回買ったUSB-6008はふたつのプロセスが独立にタスクを作ってチャンネルを接続することはできた。同じポートに別々に出力したときどうなるかは調べていない。
それでちゃんと動作するか確認するためには外に回路を繋がないといけないから面倒でやっていない。
それに今回ドライバを直接アクセスするのはひとつのサーバプロセスに限定して、読み書きはそのサーバにプロセス間通信でデータをやりとりすることで行うように書くと、クライアント側のプログラミングではNIのハードの詳細を忘れていても書ける。これはわかりやすいので、今回はNIのドライバがどのくらい賢いのか試すリスクは犯さないことにした。
もう、Distributed Objectが何だったか忘れるような話だわ。
2012-10-28 21:11
nice!(0)
コメント(0)
トラックバック(0)
コメント 0