DLLとはダイナミックリンクライブラリの略で,プログラムの任意のタイミングでリンクすることができるライブラリです.通常,ライブラリは,コンパイルしたオブジェクトをリンクする際にプログラム本体に埋め込むのが普通です(これを静的リンクという)が,DLLは必ずしもコンパイル時にリンクする必要はなく,プログラムの実行時にリンクして利用することが可能です.
RTCをDLLとする理由はいくつかあります.私が考えるのは「DLLとすることで複数のRTCを一つのプロセスから生成でき,メモリなどの面から有利」という点,同一プロセス,同一実行コンテキスト上ではCORBA層の通信が高速化する点,一つのリリースパッケージとしてエンドユーザに提供できる点,などです.
また,後述するRTC Daemonからの実行を行うと,DLLで提供されるRTCを設定ファイルの書き換えのみで再利用できます.これは非常に便利な特性です.
RTCをDLLとして開発するには
RTCの新バージョンでは,RTC Builderで生成したソリューションに,DLLを生成するプロジェクトが含まれています(コンポーネント名と同じプロジェクト.コンポーネント名Compというプロジェクトが今まで使っていたexeファイルを生成するプロジェクト.).これを使うだけです.
モジュール定義ファイルの追加
DLLを生成するために,モジュール定義ファイルを追加しなくてはなりません.ソリューションエクスプローラでDLLを生成するプロジェクトを選択し,右クリック→「追加」→「新しい項目」で「モジュール定義ファイル(.def)」を選びます.名前は何でもいいはずなのですが,やはりコンポーネント名.defとするのが良いでしょう.
ビルド
ビルドすると「コンポーネント名.dll」と「コンポーネント名.lib」というファイルが出来上がります.これと「コンポーネント名.h」というヘッダーファイルがあれば使うことができます.
ポイント(リリースとデバッグを厳密に分けること)
ここでは必ずデバッグビルドとリリースビルドを分けて考えなければなりません.たとえばRTC本体のライブラリはデバッグビルドではRTC100D.libというライブラリを使いますが,リリースビルドではRTC100.libというライブラリを使っており,どちらかを利用しますが,DLLが利用している側を使わなければエラーが起きます.
基本的に誰かに提供する場合はリリースビルドを使うべきです.
後述するRTC Daemonをリリースビルドのみ対応です.
DLLのRTCを再利用する(明示的方法)
DLLの利用法には「明示的方法」と「暗黙的方法」とがありますが,ここでは明示的方法を用いた場合の利用法をお教えします.
LoadLibrary,GetProcAddress,FreeLibrary関数
まずmain関数内でLoadLibrary関数を呼び出し,DLLに対するハンドルを取得します.
次にハンドルに対してGetProcAddress関数を呼び出し,関数へのポインタを取得します.
GetProcAddressの返り値を所望の関数のタイプにキャストして,関数を呼び出します.
最後にFreeLibrary関数でハンドルを破棄します.
サンプルコードをお見せします.
// 関数ポインタをtypedefしておくと便利 typedef void (*RTCInitFunction)(RTC::Manager* pManager); // DLLからロードする関数へのポインタ RTCInitFunction InInitFunc; // モジュール初期化プロシージャ void MyModuleInit(RTC::Manager* manager) { InInitFunc(manager); RTC::RtcBase* comp1; // Create a component comp1 = manager->createComponent("PeriodicDataIn"); if (comp1==NULL) { std::cerr << "Component create failed." << std::endl; abort(); } } // メイン関数 int main(int argc, char* argv[]) { // DLLをロード HMODULE hInDll = LoadLibrary( TEXT("PeriodicDataIn.dll") ); if (hInDll == NULL) { ShowError("LoadLibrary", GetLastError()); return -1; } // 関数へのポインタを取得 InInitFunc = (RTCInitFunction)GetProcAddress(hInDll, ("PeriodicDataInInit")); //関数名での指定 if (InInitFunc == NULL) { ShowError("GetProcAddress", GetLastError()); return -2; } // ここからがRTCのマネージャを初期化 RTC::Manager* manager; manager = RTC::Manager::init(argc, (char**)argv); // Initialize manager manager->init(argc, (char**)argv); // Set module initialization proceduer // This procedure will be invoked in activateManager() function. manager->setModuleInitProc(MyModuleInit); // Activate manager and register to naming service manager->activateManager(); // run the manager in blocking mode // runManager(false) is the default. manager->runManager(); // DLLを破棄 if (!FreeLibrary(hInDll)) { ShowError("FreeLibrary", GetLastError()); return -3; } return 0; }
ちょっと終わり方が良くないので,そこは考え直した方がよさそうです.このままだと,FreeLibraryを呼べない….暗黙的なDLL利用のほうが良いかな.
さらに進んだサンプルとして,上記のようなPeriodicDataInとPeriodicDataOutの二つのRTCを起動するプログラムをアップしておきます.
RTCの実行
ManagerTest.zipをコンパイルして実行すれば,RT System Editor上に二つのコンポーネントが表示されるのがわかります.
別々に起動するよりもかなりシンプルにプログラムを記述できます.
また,DLLのRTCを上述のRTC Daemonから使用すると,開発するコード量がぐぐっと減ります.これは便利.
RTC Daemonについてはいずれ解説します.