LabVIEWでプログラムを書くときの強みの一つは、ユーザーインタフェースであるフロントパネルをドラッグアンドドロップの操作だけで簡単に構築することができることだと思います。
本ブログのまずこれのシリーズでは主にプログラムのアルゴリズムの部分の書き方について解説してきましたが、アルゴリズムを知っているだけではプログラムは書けず、どのような選択肢があるかということも知っておく必要があります。
使い方集は、まずこれのシリーズでステートマシンまでの知識はある程度知っている前提で、アルゴリズム以外に関わるプログラムの書き方について紹介するシリーズです。
本記事ではVIの呼び出しに使えるVIサーバーについて紹介しています。
VIサーバーでVIを呼び出す
あるメインのVIから他のVIを呼び出す場合には、その呼び出されるVIをサブVIという名の関数に見立て、メインVIのブロックダイアグラム上に配置することが多いと思います。
何のことはない、以下の図のような感じで、自分で作ったVIを配置しているような状態です。
もちろんこれはこれで機能するのですが、VIを呼び出す方法としては他に、VIサーバー経由で呼び出すという方法もあります。
VIサーバー経由で呼び出す場合には、サブVIで呼び出すよりも少し複雑で、関数をいくつか使用して呼び出す感じになります。具体的には、呼び出したいサブVIのリファレンスを取得し、そのリファレンスで持ってサブVIを操作するといったイメージでプログラムを書きます。
上のプログラムと全く同じことをVIサーバー経由で行おうとすると、例えば以下のようになります。
なぜこんな面倒くさいことをしてまで呼び出すかと言うと、このやり方だとメモリの節約につながるからです。
サブVIがメインVIのブロックダイアグラムにある場合、サブVIがメモリにロードされるのはメインVIがメモリにロードされるタイミングと同じになるので、メインVIが実行されている間はサブVIが一度しか実行されていなくてもずっとサブVI分のメモリが消費され続けていることになるようです。
一方で、VIサーバーを使用した方法だとサブVIが呼び出されたときだけメモリにロードされ、リファレンスを閉じることでメモリは解放されるようです。
実験をしてみます。以下の図で、上にある左右のプログラムはどちらも同じ動作をして、メインVIから渡されたオフセット値を、あらかじめサブVIで定めた大量の波形データ(配列の定数)に足し合わせることをします。
VIを実行して終了させた段階でメモリ使用の合計量を見るとVIサーバーで呼び出す方のプログラムの方が少なくなっています。
サブVIは呼び出されたときだけ実行されてそれ以外の実行されていない時にはメモリを占有していないに越したことはないので、メモリ使用量を気にするようなプログラムではVIサーバーによる呼び出しの方が適しています。
なお、National Instruments社の資料だと、メインVIのブロックダイアグラムにサブVIが直に置かれているのを静的呼び出しと言うのに対して、VIサーバーで呼び出すのは動的呼び出しと表現されています。
VIサーバーによる呼び出しの利点
VIサーバーによる呼び出しの利点としては、既に上で例を紹介したように、呼び出しをしている間しかメモリを占有しないため、メモリ使用量を気にするようなプログラムでメモリ効率を上げるのに役に立つという点が挙げられますが、他の利点もあります。
その一つが非同期呼び出しが可能になることです。
非同期呼び出しを行うと、サブVIの実行が終了しなくてもメインVIでサブVIより後にある処理を実行できるようになります。
こちらも簡単な例を紹介します。3つのパターンのプログラムでどれも同じサブVIである乱数発生機を呼び出すことを考えます。この乱数発生機は繰り返し処理を用いていますが、わざとそれなりに時間がかかるようにしています。
このサブVIの呼び出し方法としてまずは静的なやり方、つまりブロックダイアグラムに直接置かれている場合で試してみます。サブVIの実行時間を計測するための機能を設けて、およそ10秒かかっていることがわかりました。
次にVIサーバーの同期呼び出しをしている場合です。こちらも、形こそ違えど、直接サブVIをおいているのと同じ結果を得られます。
最後に、非同期呼び出しを行う場合です。非同期呼び出しには、「非同期呼び出しを開始」と「非同期呼び出しを待機」の二つを使用して、「開始」の方でサブVIの実行を開始し、その結果を「待機」の方で受け取ります。
これを踏まえて結果を見てみます。
非同期呼び出しをしているVIの結果を見てみると、経過時間の部分が0となっているのがわかります。これは、非同期呼び出しによってサブVIの実行は開始したものの、メインVIとしては開始の関数にてサブVIの実行が終わるのを待つことなくそのまま実行され続けることになり、最終的に非同期呼び出しを待機の関数で結果が出るのを待つためです。
・・・少しこの例では非同期感がわかりにくいので、以下のようにわざと間で待ち時間を設ける事でよりはっきりすると思います。
結局、呼び出しを待機の関数が実行される時点でサブVIが終わっていなければ待つことになるのですが、十分時間が経過しているのであれば非同期呼び出しを待機の関数ですぐに結果を受け取れるとも言えます。
サブVI自体の実行そのものは決まった時間としておよそ10秒かかりますが、メインVIはサブVIとは関係なく処理を進められる、これが非同期呼び出しの特長です。
VIサーバーによる呼び出しのもう一つの利点としては、プログラムの変更に対してサブVIの置き換えをメインVIを変更することなく行える点です。
VIサーバーでサブVIを呼び出す場合、後述する呼び出し方の種類にも依りますが、必要な情報はその呼び出すサブVIのパスの情報になります。
メインVIのブロックダイアグラムを変える必要がないため、例えばプログラムをEXE化して他のPCに配布した際に、中のサブVIに対してだけ修正を加える場合であっても、メインVIのブロックダイアグラムにサブVIが直にある場合にはメインVIごと再コンパイルして作りなおして再度配布する必要がありますが、VIサーバーにした場合にはそのサブVIのみを同じパスに入れ替えるだけで修正ができるようになります。
VIサーバによる呼び出しの3つの方法
それでは、実際にVIサーバによるサブVIの呼び出し方について実装の仕方を見ていきます。
大きく分けて以下の3種類に分かれます。
- 同期呼び出しの関数を使用して実行
- 非同期呼び出しの関数を使用して実行
- インボークノードを使用して実行
それぞれの方法について、同じサブVIに対して行う際の例と、特徴を紹介していきます。
同期呼び出しの関数を使用して実行
同期呼び出しの関数を使用して実行する場合には、以下が基本の形となります。
呼び出したいサブVIのパスを指定して、そのリファレンスを取得、リファレンスの情報から呼び出して実行、実行後にリファレンスを閉じる、という具合です。
特徴としては、通常のサブVIを呼び出すのと感覚がとても似ています。メインVIからの入力、そして結果の出力も、サブVIをそのままメインVIに置いた場合と使い勝手が同じです。
また、サブVIの実行が終わるまで待機し続ける点も共通しています。
なお、上の図はワイヤが壊れていますが、VIリファレンスを開くの関数の「タイプ識別子VI refnum」が指定されていないため壊れてしまっています(タイプ識別子VI refnumの定数はリファレンスを開くの関数の端子部分で右クリックして定数を作成して出したものです)。
呼び出したいサブVIのアイコンをドラッグアンドドロップすることで「このコネクタペーンを持つサブVIのリファレンスを使用します」と指定することになります。
そのため、ある特定のサブVIだけでなく、そのサブVIと全く同じコネクタペーンを持つサブVIであれば、同じコードでサブVIパスだけ変えれば呼び出せてしまいます。
もちろんサブVIパスは制御器として後から変更できるので、メイン(呼び出す側)のVIのブロックダイアグラムを変えなくても、サブVIパスの値さえ変えれば複数のサブVIを切り替え可能、というわけです(これが利点の最後に挙げていた内容のことです)。
さて、具体例として、calculation.viを呼び出した場合の例が以下のような感じです。
リファレンス呼び出しの関数に表れるコネクタペーンに入力のための制御器や出力のための表示器を設けるだけなので、もうほんと、静的にサブVIを呼び出すのと感覚は一緒です。
filesave.viでも試すと以下のような結果となります。
非同期呼び出しの関数を使用して実行
非同期呼び出しの関数を使用して実行する場合には、以下が基本的な形となります。
リファレンス呼び出しの代わりに、非同期呼び出しを開始や非同期呼び出しを待機の関数を使用しています。もちろん、この開始と待機の間に何か別の処理をおいてもかまいませんし、サブVIが結果を出力するものではない場合には待機の関数すら要りません。
タイプ識別子VI refnumの指定は同期呼び出しと似ていますが、もし非同期呼び出しで結果が欲しい、つまり非同期呼び出しを待機で値を取得する場合には、VIリファレンスを開くの関数へ「オプション」入力として16進数で「100」を指定する必要があります。オプションは他にもあるので、詳しくはヘルプを確認してみてください。
特徴としては、上でも紹介した通り、サブVIを開始させてからそこで待ち続けるわけではなくメインVI側は次の処理に進める点です。そしてサブVIの結果が欲しくなったタイミングで非同期実行で待機の関数を呼び出して結果を得る、という使い方ができます。
以下はシンプルに開始と待機をつなげて使用していますが、calculation.viを呼び出した場合の例です。
filesave.viの例も紹介します。
インボークノードを使用して実行
まだ一度も紹介していない、インボークノードを使用して実行する場合には、以下が基本の形となります。
上の図で、上側にある3つの関数、つまりリファレンスを開く、インボークノード、リファレンスを閉じるの3つが最小構成の場合です。コネクタペーンもなく、単にサブVIを走らせるだけの場合にはこれでOKです。
コネクタペーンがなくてどのように値を入力したり出力するかというと、下側に書いたプログラムのように、これまたインボークノードを使って「制御器の値」を使用して値を入れたい制御記名や値を取得したい表示器名を書いて、あとはそれぞれのデータを指定したり取得したりします。
このように特徴としては、特定のコネクタペーンにとらわれずに使える点が挙げられます。ただし、値を渡したい場合や受け取りたい場合にはインボークノードを多用する必要があります。
具体的なイメージをつけるためにcalculation.viを呼び出してみます。
上の図のように、calculation.viの入力値である「ターゲット値」や「たーげっと値」という制御記名をサブVIのフロントパネル上の表記通りに指定して、その制御器に渡したい値をValueに配線します。このValueはバリアントデータとして受け付けるので、渡したいデータ(上の例では数値)をそのまま配線したり、あるいはバリアントデータに変換させて配線します。
一方、実行後には結果を取得するために「result」表示器を指定し、Ctrl Val Getから得られたバリアントデータを適切なデータタイプに変換(上の例では数値)する必要があります。変換には、「バリアントからデータに変換」関数を使用します。
filesave.viの例も見てみます。
やっていることは同じですね。
注意点としては、インボークノードの「VIを実行」で選べるオプション「Wait Until Done」で、これをTRUEにするとサブVIの実行が終わるまでこのインボークノードで待機するのに対し、FALSEとすると実行が終わるのを待ちません。非同期呼び出しと似たような感覚で使うことになります。
例えば、以下のように処理に時間がかかるサブVIを呼び出す場合、非同期実行で呼び出すと、非同期呼び出しを待機の関数で結果が出るのを待つことになります。
ですが同じサブVIの呼び出しにインボークノードを使用する場合には、実行してすぐに結果を取得しようとしてもサブVIが終了していなければ正しい結果を取得することが出来ません。
以下の図ではループ回数として6を入力しているので600ミリ秒待つ必要があるため、VIを実行のインボークノード直後にある表示器の値を取得するためのインボークノードでは一切待っていないのでまだ処理が終わっておらず表示器のデフォルト値の0が返ってきていて、1000ミリ秒後、つまり十分時間が経った後にもう一度値を取得してようやく正しい値が得られています。
3つの呼び方でそれぞれ特徴があることを紹介しました。
どれがいいというより、どのように呼び出したいかで使い分けができればいいと思います。
- 同期呼び出しの関数を使用して実行:使い勝手が静的なサブVI呼び出しと同じ
- 非同期呼び出しの関数を使用して実行:サブVIの実行を開始させてメインVIはすぐに次の処理に移れる。結果を取得するにはサブVIが終わっている必要があるためその時点で待機する可能性あり。
- インボークノードを使用して実行:コネクタペーンの指定は必要ないため、入出力を必要としないならどんなサブVIでも同じ形で実行できる。入出力を必要とする場合にはインボークノードを多用する。
本記事では、サブVIの呼び出し方として、メインVIのブロックダイアグラムにサブVIを置くやり方ではなく、VIサーバーを使用して呼び出す方法を紹介しました。
複数の呼び出し方それぞれに特徴があるのですが、どれもメモリの節約やブロックダイアグラムを変更することなく複数のサブVIを扱えるようになる点が共通しています(インボークノードを使用する方法以外はコネクタペーンが同じサブVIである必要がありますが)。
プログラムの種類や用途に応じて使い分けられるよう、覚えておいて損はない内容だと思うので、記事を参考にいろいろ試すきっかけになればうれしいです。
ここまで読んでいただきありがとうございました。
コメント
VIサーバーの動的呼出は面倒くさいのほかにデメリットはありますか?
コメントいただきありがとうございます!
VIサーバーでの動的呼び出しのデメリットですか・・・。確かに静的呼び出しはサブVIそのままメインVIの中に置くだけで実装できるのに対して
面倒くさいというのはネガティブなポイントですよね。
他に挙げるとしたら、静的呼び出しは既に呼んでいるのでそのサブVIの実行がすぐにできるのに対して、動的呼び出しは一度メモリにその呼び出すサブVIを
ロードしなくてはいけないので、呼び出すサブVIが大きいと呼び出しの時点でロードに時間がかかる可能性があることなんかが挙げられると思います。
あとは、組み方によってはLabVIEWの特長であるアイコンでの表示ができないかなと思うので見た目でそのサブVIの役割を判断できなくなるのもデメリットになるのかなと思います。
・・・すいません、あまり参考にならないかもしれませんが、思いつくのはこれくらいですかね。