日付 | 0000/00/00 |
---|
4DからOracle®に接続するためのプラグインを作成しました。
はじめに過去から現在のOracleコネクティビティを振り返ってみましょう。
4D for Oracle (1993~2003)4D for Oracleは,Oracleデータベースと4D(4th Dimension)のコネクティビティを実現した最初の本格的なプラグインです。インストールすることにより,Oracleデベロッパーと4Dデベロッパーの両方を想定した二重のコマンド体系,エディターやデバッガなどのユーザーインタフェース群,データベース仕様の違い(ヌル値・プライマリキーの有無)を吸収するためのメカニズムなどが提供されました。
ハイレベルコマンド: 4D指向(データオブジェクトのバインドが主体)
ローレベルコマンド: Oracle指向(SQL言語の実行が主体)
ローレベルコマンドは,OLON=OD Login,OFETCH=OD Load rows cursorなど,基本的にOCI7のコマンドと直接的な関連があり,Oracleに対する問い合わせはすべてSQLで実行されるのが特徴です。一方,ハイレベルコマンドは「SQL言語を使用しなくても」Oracleと対話できることが利点とされており,4Dアプリケーションとの親和性に重点が置かれています。
/60/4D for ORACLE1.5J Reference.pdf
1997年,Oracle 8がリリースされると,OCIも大幅にブラッシュアップされ,2007年のOracle 11gまで段階的に改良が施されてきました。OCIは高い信頼性を得ており,「Oracleデータベースが実行できるSQLは,すべてOCIで実行できる(Oracle)と言われるまでになりました。
そうした事情があり,また,簡易性を追求したハイレベルコマンドよりも「ローレベルコマンド(OCI)」が直接コールできたほうが嬉しいという見方がデベロッパの間で支配的になったこともあり,4D for ORACLEはバージョン2003を最後に「引退」することが決定され,Oracleコネクティビティは「4D for OCI」に統合されることになりました。
4D for OCI (2001~)4D for OCIは,Oracleデータベースと4D(4th Dimension)のコネクティビティを実現した第二世代のプラグインです。OCI 8.1.6以上(プラグインは,マシンのORACLE_HOMEにインストールされているDLL/dylibを検索してリンクします)の代表的な関数をコールすることができるプラグインです。4D for ORACLEの「ローレベルコマンド」のように,Oracleに対する問い合わせはすべてSQLで実行されますが,コマンド名と引数リストは,OCI8(C言語)のものを採用しているため,4D for ORACLEとは互換性がありません。
/68/OCI_StartGuide_J.pdf
4D for OCIがリリースされたのは,4Dバージョン6.7でのことです。つまり,3世代(6.7, 6.8, 2003)は,4D for ORACLEと4D for OCIの両方がサポートされていたわけですが,旧プラグインの使用者は,この期間中に移行を完了することが呼びかけられ,旧プラグインの「ローレベルコマンド」を新プラグインにマッピングするOCI Mapperというコンポーネントや,その方法を論じたテクニカルノートが数度にわたって発表されました。
http://kb.4d.com/assetid=42333
http://kb.4d.com/assetid=41779
http://kb.4d.com/assetid=30141
その後,予告されていたように,4D for ORACLEのサポートは終了しましたが,4D for OCIのほうは,v11でMacIntelに対応するなど,4Dバージョン13までメンテナンスされています。バージョン14では,64ビット版で提供される予定です。
カスタムプラグイン作成の理由
4D for OCIを使用するためには,SQL/OCI8の理解が不可欠です。これは,4D for ORACLEのローレベルコマンドに不足を感じていたデベロッパには朗報ですが,そうではないという人には少々厄介な話です。長年,4D for ORACLEで問題なく動作していたプログラムであれば,書き換えることに不安を覚えるとしても無理もないかもしれません。また,4D for OCIは,非Unicodeプラグインであり,64ビット版の4D Serverでは実行できないというのも悩みの種です。最後に,Mac版の4D for OCIは,所定の場所にOracle Instant Clientがインストールされていないと動作しない,という不便さがあります。
そこで,今回はつぎのような目標を設定しました。
Unicodeモード
64ビット版
Mac版でライブラリの有無に左右されない
最低限の4D for ORACLE互換コマンド
Unicodeモード
プラグインのUnicodeモードは,4D(本体)のUnicodeモードとは関連がありません。エントリーポイントのFourDPackを処理すれば,非Unicodeモードの引数が渡され,FourDPackexを処理すれば,Unicodeモードの引数(UTF-16のポインタ)が渡されます。また,Plugin SDKには,Unicode文字列を処理するための関数やエントリーポイントが揃っています。
OCIのほうは,OCIEnvCreate()にOCI_UTF16を渡すことにより,すべての文字列がUTF-16扱いとなり,4Dとの連携がスムーズです。C言語のwhar_tは,プラットフォーム非互換(WindowsはUTF-16,MacはUTF-32)であるため,プラグイン開発ではあまり使用する機会がないかもしれません。
問題は,文字列のサイズ計算です。OCIのドキュメントによれば,UTF-16のサイズは,引数タイプがtext*であればコードポイント数で表現され,dvoid*であれば,バイト単位である,とされています。
しかし,実際には,OCIStmtPrepare()のように,引数タイプがtext*であっても,コードポイント数ではなく,バイト数である場合が少なくないようです。また,文字列サイズには,基本的に末端のNULL文字を含めなければなりません。
64ビット版以前にブログで取り上げたように,Visual StudioのProfessional版を使用すれば,IDEが標準で64ビット実行ファイルのビルドをサポートしています。無料のExpress版を使用するのであれば,Windows SDKを別途インストールすることが必要です。
Instant Clientのlib/dllは,Windows-x86,Windows-x64それぞれのSDKに含まれています。oraociei11.dll,orannzsbb11.dllは,oci.dllが動的にロードする仕組みになっているので,プラグインにはoci.libだけをリンクします。
Mac版でライブラリの有無に左右されないMacのダイナミックライブラリ(.dylib)は,インストール名でサーチされますが,通常,そのインストール名は絶対パス名のハードコーディングなので,別の場所に移動されると,WindowsのDLLとは違い,たとえ連接していたとしても,ダイナミックリンカ(dyld)に見つけられません。プラグインに含まれた.dylibのパスは,運用する環境に左右されるので,絶対パスでインストール名を決めるわけにはゆかない,という問題に陥ります。
そこで,プラグインの場合,install_name_tools -idで@loader_path/というプリフィックスを追加し,実行ファイルからの相対的な位置でインストール名を設定するのがポイントです。ライブラリや実行ファイルのインストール名は,otool -Lで確認することができます。
ところが,libclntsh.dylib(Windows版のoci.dllに該当)に@loader_path/プリフィックスを設定しても,OCIコールは失敗コード(-1)を返しました。ライブラリ自体はリンクされているのですが,その後,ライブラリが動的にロードするイブラリを見つけることができないのです。
OCIのドキュメントには,Mac版のライブラリサーチパスには,標準の場所(/usr/lib/)以外にも,環境変数DYLD_LIBRARY_PATHの場所も含まれていると説明されています。しかし,実行時にsetenv()で環境変数を書き換えても,うまくゆきません。ダイナミックリンカ(dyld)は,なぜライブラリに隣接するライブラリを見つけることができないのでしょうか。
この問題を理解する上で役立ったのが,ダイナミックリンカ(dyld)にログを出力させる環境変数です。特に有用だったのが次のふたつです。
DYLD_PRINT_LIBRARIES
DYLD_PRINT_APIS
前者はロードされたダイナミックライブラリ名を出力するモード,後者はdyldがコールされたAPI関数名(と引数)を出力するモードを有効にします。これにより,内部的にdlopen("libociei.dylib")がコールされていること,これに失敗しているために,OCIが失敗していることが判明しました。dlopenは,プロセスの環境変数DYLD_LIBRARY_PATHに影響されないようです。
実際,Xcodeで「実行ファイル/引数/環境変数にDYLD_LIBRARY_PATHを追加し,プラグインのインストールディレクトリを入力すると,ライブラリは問題なく起動できることが確認できました。また,非表示フォルダにファイル/.MacOSX/environment.plistを作成し,システム環境変数としてDYLD_LIBRARY_PATHを定義してもうまくゆきました。問題は,この環境変数に依存することなく,ライブラリに隣接する場所にあるライブラリをどうすれば,dlopenで見つけられるのか,という点です。
Mac OS Xのドキュメントを調べたところ,環境変数などに加え,カレントディレクトリも検索パスに含まれていることが分かりました。そこでOCIを初期化する直前にchdir()を使用し,サーチパスを誘導したところ,無事にライブラリをロードすることができました。
最低限の4D for ORACLE互換コマンドハイレベルコマンドを排除し,主要なコマンドに焦点を合わせました。ただ,ヌル値のネイティブサポートなど,当時の4Dには存在しなかったものなど,若干の追加もしています。
http://sources.4d.com/trac/4d_keisuke/wiki/Plugins/4D%20for%20ORACLE