13 OCIでのユーザー定義コールバック関数
Oracle Call Interfaceでは、OCIコールの他に、ユーザー固有のコードを実行できます。
この機能は次のことに使用できます。
-
ユーザーがアプリケーションをチューニングできるように、トレースやパフォーマンス測定のためのコードを追加します。
-
特定のOCIコールに対するコードの事前処理または事後処理を実行します。
-
Oracle Database固有のOCIインタフェースを使用して、OCI付きの他のデータ・ソースにアクセスし、OCIコールがOracle以外のデータ・ソースへのユーザー・コールバックを使用するようにします。
OCIコールバック機能は、OCIコールの実行前後におけるユーザー・コードのコールをサポートしています。また、OCIコードを実行するかわりに、ユーザー定義コードを実行できる機能も用意されています。
アプリケーションのソース・コードを修正することなく、ユーザー・コールバック・コードを動的に登録できます。動的な登録は、OCIEnvCreate()
コール時に環境ハンドルを初期化した後、ユーザーが作成した5つ以下の動的リンク・ライブラリをロードすることによって実装されます。ユーザーが作成したライブラリ(Windowsの動的リンク・ライブラリ(DLL)またはSolaris上の共有ライブラリなど)では、選択したOCIコールに対するユーザー・コールバックをアプリケーションに透過的に登録します。
サンプル・アプリケーション
OCIユーザー・コールバック機能を説明するすべてのデモ・プログラムのリストについては、付録Bを参照してください。
関連項目:
13.1 OCIでのユーザー・コールバックの登録について
アプリケーションから、OCIUserCallbackRegister()
関数を使用してユーザー・コールバック・ライブラリを登録できます。
コールバックは、環境ハンドルのコンテキスト内に登録されます。アプリケーションでは、OCIUserCallbackGet()
関数を使用してハンドルに登録されたコールバックについての情報を取り出すことができます。
ユーザー定義コールバックは、OCIコールおよび環境ハンドルに対して登録されたサブルーチンです。ユーザー定義コールバックには、最初のコールバック、置換コールバックまたは最後のコールバックを指定できます。
-
最初のコールバックの場合は、プログラムからOCI関数をコールするときにコールされます。
-
置換コールバックは、最初のコールバック実行の後に実行されます。置換コールバックから
OCI_CONTINUE
の値が戻されると、後続の置換コールバックまたは通常のOCIコードが実行されます。置換コールバックからOCI_CONTINUE
以外の値が戻される場合は、後続の置換コールバックおよびOCIコードは実行されません。 -
置換コールバックから
OCI_CONTINUE
以外の値が戻されるか、またはOCI関数が正常に実行されると、プログラムの制御は最後のコールバックに移ります(最後のコールバックが登録されている場合)。
置換コールバックまたは最後のコールバックからOCI_CONTINUE
以外が戻された場合は、コールバックからのリターン・コードは、対応付けられたOCIコールから戻されます。
ユーザー・コールバックでは、無効なハンドルまたは無効なコンテキストが渡された場合に、OCI_INVALID_HANDLE
を戻すことがあります。
注意:
任意のコールバックからOCI_CONTINUE
以外の値が戻された場合は、リターン・コードが後続のコールバックに渡されます。置換コールバックまたは最後のコールバックからOCI_CONTINUE
以外のリターン・コードが戻された場合は、最後の(OCI_CONTINUE
ではない)リターン・コードがOCIコールから戻されます。
13.1.1 OCIUserCallbackRegister
ユーザー・コールバックの登録には、OCIUserCallbackRegister()コールを使用します。
現段階では、OCIUserCallbackRegister()
は環境ハンドルでしか登録できません。typedef OCIUserCallback
のユーザー・コールバック関数は、OCI関数コードfcodeによって識別されるOCIコールのコンテキストとともに登録します。コールバックの型は、最初のコールバック、置換または最後のコールバックのいずれの場合もwhenパラメータによって指定します。
たとえば、最初のコールバック関数stmtprep_entry_dyncbk_fn
とそのコンテキストdynamic_context
は、次のパラメータを使用してOCIUserCallbackRegister()
関数をコールすることにより、OCIStmtPrepare2()
コールの環境ハンドルhndlp
に対して登録されます。
OCIUserCallbackRegister( hndlp, OCI_HTYPE_ENV, errh, stmtprep_entry_dyncbk_fn, dynamic_context, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_ENTRY (OCIUcb*) NULL);
13.1.2 ユーザー・コールバック関数
ユーザー・コールバック関数について詳しく説明します。
ユーザー・コールバック関数は、次の構文を使用する必要があります。
typedef sword (*OCIUserCallback) (void *ctxp, /* context for the user callback*/ void *hndlp, /* handle for the callback, env handle for now */ ub4 type, /* type of handlp, OCI_HTYPE_ENV for this release */ ub4 fcode, /* function code of the OCI call */ ub1 when, /* type of the callback, entry or exit */ sword returnCode, /* OCI return code */ ub4 *errnop, /* Oracle error number */ va_list arglist); /* parameters of the oci call */
コールバックは、OCIUserCallbackRegister()
コールで説明したパラメータの他に、リターン・コードerrnop、およびプロトタイプ定義で宣言された元のOCIのすべてのパラメータを使用してコールします。
最初のコールバックでは、リターン・コードは常にOCI_SUCCESS
として渡され、*errnop
は常に0として渡されます。errnop
はIN/OUTパラメータであるため、*errnop
はerrnop
の内容を指します。
コールバックがOCIリターン・コードを変更しない場合は、OCI_CONTINUE
を戻す必要があり、*errnop
に戻される値は無視されます。ただし、コールバックからOCI_CONTINUE
以外のリターン・コードが戻された場合は、最後に戻されたリターン・コードがそのコールのリターン・コードになります。この時点で、*errnop
に戻された値は、エラー・ハンドルに設定されるか、または環境ハンドルにエラー情報が戻された場合は環境ハンドルに設定されます。これは、OCIHandleAlloc()
などの特定のOCIコールではエラー・ハンドルが存在しないためです。
置換コールバックの場合は、returnCode
は前のコールバックまたはOCIコールから戻されるOCI_CONTINUE
以外のリターン・コードであり、*errnop
はエラー・ハンドル内に戻されるエラー番号の値です。これによって、後続のコールバックでは、必要に応じてリターン・コードやエラー情報を変更できます。
置換コールバックからOCI_CONTINUE
以外のリターン・コードが戻されると、後続の置換コールバックおよびOCIコードは無視され、最後のコールバックの処理に移るという点で、置換コールバックの処理は異なります。
置換コールバックからOCI_CONTINUE
が戻されてOCIコードの処理が可能になった場合は、最初のコールバックからのリターン・コードが無視されることに注意してください。
OCIコールの元のパラメータはすべて、変数パラメータとしてコールバックに渡され、コールバックでは、それらをva_argを使用して取り出す必要があります。コールバックのデモ・プログラムには例が提供されています。
コールバックの登録を解除するために、NULL値を登録することができます。つまり、OCIUserCallbackRegister()
コールのコールバック(OCIUserCallback()
)の値がNULL
の場合は、ユーザー・コールバックの登録は解除されます。
スレッドセーフ・モードを使用している場合、OCIプログラムでは、ユーザー・コールバックをコールする前にすべてのmutexを取得します。
13.1.3 ユーザー・コールバックの制御フロー
ユーザー・コールバックの制御フローを示します。
例13-1では、典型的なOCIコールの全体的な処理を説明する擬似コードを示しています。
例13-1 典型的なOCIコールの全体的な処理を説明する擬似コード
OCIXyzCall() { Acquire mutexes on handles; retCode = OCI_SUCCESS; errno = 0; for all ENTRY callbacks do { EntryretCode = (*entryCallback)(..., retcode, &errno, ...); if (retCode != OCI_CONTINUE) { set errno in error handle or environment handle; retCode = EntryretCode; } } for all REPLACEMENT callbacks do { retCode = (*replacementCallback) (..., retcode, &errno, ...); if (retCode != OCI_CONTINUE) { set errno in error handle or environment handle goto executeEXITCallback; } } retCode = return code for XyzCall; /* normal processing of OCI call */ errno = error number from error handle or env handle; executeExitCallback: for all EXIT callbacks do { exitRetCode = (*exitCallback)(..., retCode, &errno,...); if (exitRetCode != OCI_CONTINUE) { set errno in error handle or environment handle; retCode = exitRetCode; } } release mutexes; return retCode }
13.1.4 OCIErrorGet()のユーザー・コールバック
OCIコード全体がコールバックに置き換わる場合、コールバックでは、通常、コール・コンテキストに独自のエラー情報を維持し、これを使用して、OCIErrorGet()
コールの置換コールバックのbufp
パラメータおよびerrcodep
パラメータにエラー情報を戻します。
ただし、コールバックがOCIコードの一部をオーバーライドするか、他のなんらかの後処理のみを行う場合は、最後のコールバックを使用して、エラー・テキストや OCIErrorGet()
コールのerrcodep
パラメータを、独自のエラー・メッセージやエラー番号で修正できます。最後のコールバックに渡される*errnop
は、エラー・ハンドルまたは環境ハンドル内のエラー番号です。
関連項目:
13.1.5 最初のコールバックのエラー
最初のコールバックからOCIコールのコール元にエラーを戻す場合は、置換コールバックまたは最後のコールバックを登録する必要があります。
これは、OCIコードが実行される場合、最初のコールバックのエラー・コードが無視されるためです。したがって、最初のコールバックから独自のコンテキストによって、置換コールバックまたは最後のコールバックにエラーが渡される必要があります。
13.1.6 動的なコールバック登録
ユーザー・コールバックは、OCI動作の監視または他のデータ・ソースへアクセスするために使用されることが多いので、コールバックの登録は、割込みが発生しないよう透過的に行われることが望まれます。
これは、OCIの初期化時に、ユーザーが作成した動的リンク・ライブラリをロードすることで可能になります。動的にリンクされているこれらのライブラリはパッケージと呼ばれます。ユーザーが作成したパッケージは、選択したOCIコール用のユーザー・コールバックを登録します。これらのコールバックでは、実行時に制御を受け取ったときに、必要に応じてユーザー・コールバックの追加登録または登録解除を行うことができます。
OCIデモ・プログラムとともに、パッケージを作成するためのMakefile (Solarisのociucb.mk
)が用意されています。このパッケージの正確な名前と位置は、オペレーティング・システムによって異なります。パッケージのソース・コードには、OCIの初期化時および環境作成時にコールされる特別なコールバックのコードを含める必要があります。
オペレーティング・システムの環境変数ORA_OCI_UCBPKG
を設定することで、パッケージのロードを制御します。この変数によって、汎用的な方法でパッケージに名前が付けられます。パッケージは、$ORACLE_HOME/lib
ディレクトリに格納してください。
13.1.7 複数のパッケージのロードについて
ORA_OCI_UCBPKG
変数には、パッケージ名をセミコロンで区切ったリストを含めることができます。パッケージは、リストに指定されている順序でロードされます。
たとえば、以前パッケージは次のように指定されました。
setenv ORA_OCI_UCBPKG mypkg
現在でも前述の指定はできますが、さらに複数のパッケージを次のように指定できます。
setenv ORA_OCI_UCBPKG "mypkg;yourpkg;oraclepkg;sunpkg;msoftpkg"
これらのパッケージはすべて順番にロードされます。つまり、最初にmypkg
がロードされ、最後にmsoftpkg
がロードされます。
最大5パッケージまで指定できます。
注意:
サンプルのMakefile ociucb.mk
によって、Solarisではociucb.so.1.0
、Windowsシステムではociucb.dll
が作成されます。ociucb
パッケージをロードするには、環境変数ORA_OCI_UCBPKG
にociucb
を設定する必要があります。パッケージ名が.so
で終わっている場合、SolarisではOCIEnvCreate()または OCIEnvNlsCreate()がエラーになります。パッケージ名は、.so.1.0
で終わる必要があります。
動的リンク・ライブラリの作成の詳細は、オペレーティング・システムのデモ・ディレクトリにあるMakefileをお読みください。ユーザー定義のコールバックの詳細は、使用されているオペレーティング・システムのドキュメントでアプリケーションのコンパイルとリンクの項を参照してください。
13.1.8 パッケージ・フォーマット
パッケージ・ソースにより2つの関数を指定する必要があります。
以前は、パッケージによりOCIEnvCallback()
関数のソース・コードを指定する必要がありました。ただし、OCIEnvCallback()
関数は廃止されています。かわりに、パッケージ・ソースにより2つの関数を指定する必要があります。最初の関数の名前はpackagenameにして、Initという接尾辞を付ける必要があります。たとえば、パッケージの名前がfoo
の場合、ソース・ファイル(たとえば、必ずしもfoo.cというわけではない)には、次のように正確に指定されたOCISharedLibInit()
関数のコールが記述されたfooInit()
関数が含まれている必要があります。
sword fooInit(metaCtx, libCtx, argfmt, argc, argv) void * metaCtx; /* The metacontext */ void * libCtx; /* The context for this package. */ ub4 argfmt; /* package argument format */ sword argc; /* package arg count*/ void * argv[]; /* package arguments */ { return (OCISharedLibInit(metaCtx, libCtx, argfmt, argc, argv, fooEnvCallback)); }
この場合、OCISharedLibInit()
関数の最後のパラメータであるfooEnvCallback()
は、第2の関数の名前になります。任意の名前にできますが、通常はpackagenameにして、EnvCallbackという接尾辞を付けます。
この関数は、OCIEnvCallback()
にかわるものです。現在のところ、すべての動的なユーザー・コールバックは、この関数に登録する必要があります。関数は、次のように指定されるOCIEnvCallbackType
型にしてください。
typedef sword (*OCIEnvCallbackType)(OCIEnv *env, ub4 mode, size_t xtramem_sz, void *usrmemp, OCIUcb *ucbDesc);
環境ハンドルが作成されると、このコールバック関数が最後にコールされます。env
パラメータは、新しく作成された環境ハンドルです。
mode
、xtramem_sz
およびusrmempp
は、OCIEnvCreate()コールに渡されるパラメータです。最後のパラメータucbDesc
は、パッケージに渡される記述子です。後述するように、パッケージではこの記述子を使用して、ユーザー・コールバックを登録します。
サンプルのociucb.c
ファイルがdemo
ディレクトリにあります。Solarisでは、パッケージ作成用のMakefile ociucb.mk
も、demo
ディレクトリに用意されています。これはオペレーティング・システムによって異なりますので、注意してください。さらにdemo
ディレクトリには、これを説明するユーザー・コールバックのデモ・プログラムがすべて(cdemoucb.c、cdemoucbl.c
)揃っています。
13.1.9 ユーザー・コールバックの連鎖
ユーザー・コールバックは、アプリケーション自体に静的に登録することも、実行時にDLL内に動的に登録することもできます。
動的な登録の目的の動作を維持するには、以前に登録されていたコールバックを上書きした後に、上書きしたコールバックを新しく登録したコールバック内でコールするという、アプリケーションのメカニズムが必要です。この結果、ユーザー・コールバックが連鎖することがあります。
OCIUserCallbackGet()
関数により、どの関数およびコンテキストをOCIコール用に登録するかが決まります。
関連項目:
13.1.10 OCIを介した他のデータ・ソースへのアクセスについて
Oracle Databaseはアクセス対象となる最も重要なデータベース・ソフトウェアであるため、アプリケーションはOCIインタフェースを有効に利用し、ユーザー・コールバックを使用してOracle以外のデータにアクセスできます。
これにより、OCIで記述されたアプリケーションは、パフォーマンスを低下させずにOracleデータにアクセスできます。ユーザー・コールバックでは、Oracle以外のデータにアクセスするためのドライバを記述できます。OCIには非常に強力なインタフェースが備えられているため、通常、大部分のデータ・ソースに対してOCIコールを直接マッピングできます。このソリューションは、すべてのデータ・ソースに対してパフォーマンスの低下を引き起こす、ODBCのような他の中間層用のアプリケーションを記述することより優れています。OCIを使用するとOracleデータ・ソースにアクセスするのに特に問題は発生しませんが、ODBCがOracle以外のデータ・ソースにアクセスする場合と同じ問題が発生します。
13.1.11 コールバック関数の制限
コールバック関数の制限について詳しく説明します。
OCIEnvCallback()
などのコールバック関数の使用方法には、いくつかの制約があります。
-
コールバックからは、
OCIUserCallbackRegister()
、OCIUserCallbackGet()
、OCIHandleAlloc()
およびOCIHandleFree()
以外のOCI関数をコールできません。これらの関数であっても、ユーザー・コールバックでコールされている場合、コールバックは再帰を避けるためにコールされません。たとえば、OCIHandleFree()
がOCILogoff()
のコールバックでコールされている場合は、OCILogoff()
のコールバックの実行中にOCIHandleFree()
のコールバックを行うことはできません。 -
コールバックでは、環境ハンドルまたはエラー・ハンドルなどのOCIデータ構造を変更できません。
-
OCIUserCallbackRegister()
コール自体または次のいずれのコールに対しても、コールバックは登録できません。-
OCIUserCallbackGet()
-
OCIEnvCreate()
-
OCIInitialize()
(非推奨) -
OCIEnvNlsCreate()
-
13.1.12 OCIコールバックの例
OCIコールバックの使用例を示します。
OCIStmtPrepare2()
コールに対してそれぞれ最初のコールバック、置換コールバックおよび最後のコールバックを登録している5つのパッケージがあるとします。つまり、ORA_OCI_UCBPKG
変数が、例13-2のように設定されています。
各パッケージpkgN
(Nは1から5)では、pkgNInit()
関数およびPkgNEnvCallback()
関数を、例13-3のように指定します。
例13-4では、pkgNEnvCallback()
関数での最初、置換および最後のコールバックの登録方法を示しています。
最後に、例13-5では、アプリケーションのソース・コードで、ユーザー・コールバックをNULL
ucbDescに登録する方法を示しています。
例13-6では、OCIStmtPrepare2()
コールが実行されると、コールバックが次の順にコールされることを示しています。
注意:
最後のコールバックは、最初のコールバックおよび置換コールバックとは逆の順にコールされます。
最初のコールバックと最後のコールバックからは任意のリターン・コードが戻され、続いて次のコールバックが処理されます。ただし、置換コールバックからOCI_CONTINUE
以外のリターン・コードが戻された場合は、連鎖の次のコールバック(または、最後の置換コールバックの場合はOCIコード)が無視され、最後のコールバックの処理に移ります。たとえば、pkg3_replace_callback_fn()
からOCI_SUCCESS
が戻された場合、pkg4_replace_callback_fn()
、pkg5_replace_callback_fn()
およびOCIStmtPrepare2()
コールのOCI処理は無視されます。かわりに、pkg5_exit_callback_fn()
が次に実行されます。
例13-2 ORA_OCI_UCBPKG変数に対する環境変数の設定
setenv ORA_OCI_UCBPKG "pkg1;pkg2;pkg3;pkg4;pkg5"
例13-3 pkgNInit()関数とPkgNEnvCallback()関数の指定
pkgNInit(void *metaCtx, void *libCtx, ub4 argfmt, sword argc, void **argv) { return OCISharedLibInit(metaCtx, libCtx, argfmt, argc, argv, pkgNEnvCallback); }
例13-4 pkgNEnvCallback()を使用した最初、置換および最後のコールバックの登録
pkgNEnvCallback(OCIEnv *env, ub4 mode, size_t xtramemsz, void *usrmemp, OCIUcb *ucbDesc) { OCIHandleAlloc((void *)env, (void **)&errh, OCI_HTYPE_ERROR, (size_t) 0, (void **)NULL); OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, pkgN_entry_callback_fn, pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_ENTRY, ucbDesc); OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, pkgN_replace_callback_fn, pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_REPLACE, ucbDesc); OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, pkgN_exit_callback_fn, pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_EXIT, ucbDesc); return OCI_CONTINUE; }
例13-5 ユーザー・コールバックのNULL ucbDescへの登録
OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, static_entry_callback_fn, pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_ENTRY, (OCIUcb *)NULL); OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, static_replace_callback_fn, pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_REPLACE, (OCIUcb *)NULL); OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, static_exit_callback_fn, pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_EXIT, (OCIUcb *)NULL);
例13-6 コールバックを順にコールするためのOCIStmtPrepare()の使用
static_entry_callback_fn() pkg1_entry_callback_fn() pkg2_entry_callback_fn() pkg3_entry_callback_fn() pkg4_entry_callback_fn() pkg5_entry_callback_fn() static_replace_callback_fn() pkg1_replace_callback_fn() pkg2_replace_callback_fn() pkg3_replace_callback_fn() pkg4_replace_callback_fn() pkg5_replace_callback_fn() OCI code for OCIStmtPrepare call pkg5_exit_callback_fn() pkg4_exit_callback_fn() pkg3_exit_callback_fn() pkg2_exit_callback_fn() pkg1_exit_callback_fn() static_exit_callback_fn()
関連項目:
13.2 外部プロシージャからのOCIコールバック
外部プロシージャからのOCIコールバックの使用に関する追加参照情報を提供します。
外部プロシージャからコールバックとして使用できるいくつかのOCI関数があります。
関連項目:
-
外部プロシージャからコールバックとして使用できる関数のリストについては、「OCIカートリッジ関数」を参照してください。
-
PL/SQLコードからコールできるCサブルーチンの作成方法の詳細は、使用可能なOCIコールのリストおよびコード例も含め、『Oracle Database開発ガイド』を参照してください。