14 OCIでのユーザー定義コールバック関数

Oracle Call Interfaceでは、OCIコールの他に、ユーザー固有のコードを実行できます。

この機能は次のことに使用できます。

  • ユーザーがアプリケーションをチューニングできるように、トレースやパフォーマンス測定のためのコードを追加します。

  • 特定のOCIコールに対するコードの事前処理または事後処理を実行します。

  • Oracle Database固有のOCIインタフェースを使用して、OCI付きの他のデータ・ソースにアクセスし、OCIコールがOracle以外のデータ・ソースへのユーザー・コールバックを使用するようにします。

OCIコールバック機能は、OCIコールの実行前後におけるユーザー・コードのコールをサポートしています。また、OCIコードを実行するかわりに、ユーザー定義コードを実行できる機能も用意されています。

アプリケーションのソース・コードを修正することなく、ユーザー・コールバック・コードを動的に登録できます。動的な登録は、OCIEnvCreate()コール時に環境ハンドルを初期化した後、ユーザーが作成した5つ以下の動的リンク・ライブラリをロードすることによって実装されます。ユーザーが作成したライブラリ(Windowsの動的リンク・ライブラリ(DLL)またはSolaris上の共有ライブラリなど)では、選択したOCIコールに対するユーザー・コールバックをアプリケーションに透過的に登録します。

サンプル・アプリケーション

OCIユーザー・コールバック機能を説明するすべてのデモ・プログラムのリストについては、付録Bを参照してください。 

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コールから戻されます。

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);

ユーザー・コールバック関数

ユーザー・コールバック関数について詳しく説明します。

ユーザー・コールバック関数は、次の構文を使用する必要があります。

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パラメータであるため、*errnoperrnopの内容を指します。

コールバックが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を取得します。

ユーザー・コールバックの制御フロー

ユーザー・コールバックの制御フローを示します。

例14-1では、典型的なOCIコールの全体的な処理を説明する擬似コードを示しています。

例14-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
}

OCIErrorGet()のユーザー・コールバック

OCIコード全体がコールバックに置き換わる場合、コールバックでは、通常、コール・コンテキストに独自のエラー情報を維持し、これを使用して、OCIErrorGet()コールの置換コールバックのbufpパラメータおよびerrcodepパラメータにエラー情報を戻します。

ただし、コールバックがOCIコードの一部をオーバーライドするか、他のなんらかの後処理のみを行う場合は、最後のコールバックを使用して、エラー・テキストや OCIErrorGet()コールのerrcodepパラメータを、独自のエラー・メッセージやエラー番号で修正できます。最後のコールバックに渡される*errnopは、エラー・ハンドルまたは環境ハンドル内のエラー番号です。

関連項目:

OCIErrorGet()

最初のコールバックのエラー

最初のコールバックからOCIコールのコール元にエラーを戻す場合は、置換コールバックまたは最後のコールバックを登録する必要があります。

これは、OCIコードが実行される場合、最初のコールバックのエラー・コードが無視されるためです。したがって、最初のコールバックから独自のコンテキストによって、置換コールバックまたは最後のコールバックにエラーが渡される必要があります。

動的なコールバック登録

ユーザー・コールバックは、OCI動作の監視または他のデータ・ソースへアクセスするために使用されることが多いので、コールバックの登録は、割込みが発生しないよう透過的に行われることが望まれます。

これは、OCIの初期化時に、ユーザーが作成した動的リンク・ライブラリをロードすることで可能になります。動的にリンクされているこれらのライブラリはパッケージと呼ばれます。ユーザーが作成したパッケージは、選択したOCIコール用のユーザー・コールバックを登録します。これらのコールバックでは、実行時に制御を受け取ったときに、必要に応じてユーザー・コールバックの追加登録または登録解除を行うことができます。

OCIデモ・プログラムとともに、パッケージを作成するためのMakefile (Solarisのociucb.mk)が用意されています。このパッケージの正確な名前と位置は、オペレーティング・システムによって異なります。パッケージのソース・コードには、OCIの初期化時および環境作成時にコールされる特別なコールバックのコードを含める必要があります。

オペレーティング・システムの環境変数ORA_OCI_UCBPKGを設定することで、パッケージのロードを制御します。この変数によって、汎用的な方法でパッケージに名前が付けられます。パッケージは、$ORACLE_HOME/libディレクトリに格納してください。

複数のパッケージのロードについて

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_UCBPKGociucbを設定する必要があります。パッケージ名が.soで終わっている場合、SolarisではOCIEnvCreate()または OCIEnvNlsCreate()がエラーになります。パッケージ名は、.so.1.0で終わる必要があります。

動的リンク・ライブラリの作成の詳細は、オペレーティング・システムのデモ・ディレクトリにあるMakefileをお読みください。ユーザー定義のコールバックの詳細は、使用されているオペレーティング・システムのドキュメントでアプリケーションのコンパイルとリンクの項を参照してください。

パッケージ・フォーマット

パッケージ・ソースにより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パラメータは、新しく作成された環境ハンドルです。

modextramem_szおよびusrmemppは、OCIEnvCreate()コールに渡されるパラメータです。最後のパラメータucbDescは、パッケージに渡される記述子です。後述するように、パッケージではこの記述子を使用して、ユーザー・コールバックを登録します。

サンプルのociucb.cファイルがdemoディレクトリにあります。Solarisでは、パッケージ作成用のMakefile ociucb.mkも、demoディレクトリに用意されています。これはオペレーティング・システムによって異なりますので、注意してください。さらにdemoディレクトリには、これを説明するユーザー・コールバックのデモ・プログラムがすべて(cdemoucb.c、cdemoucbl.c)揃っています。

ユーザー・コールバックの連鎖

ユーザー・コールバックは、アプリケーション自体に静的に登録することも、実行時にDLL内に動的に登録することもできます。

動的な登録の目的の動作を維持するには、以前に登録されていたコールバックを上書きした後に、上書きしたコールバックを新しく登録したコールバック内でコールするという、アプリケーションのメカニズムが必要です。この結果、ユーザー・コールバックが連鎖することがあります。

OCIUserCallbackGet()関数により、どの関数およびコンテキストをOCIコール用に登録するかが決まります。

関連項目:

OCIUserCallbackGet()

OCIを介した他のデータ・ソースへのアクセスについて

Oracle Databaseはアクセス対象となる最も重要なデータベース・ソフトウェアであるため、アプリケーションはOCIインタフェースを有効に利用し、ユーザー・コールバックを使用してOracle以外のデータにアクセスできます。

これにより、OCIで記述されたアプリケーションは、パフォーマンスを低下させずにOracleデータにアクセスできます。ユーザー・コールバックでは、Oracle以外のデータにアクセスするためのドライバを記述できます。OCIには非常に強力なインタフェースが備えられているため、通常、大部分のデータ・ソースに対してOCIコールを直接マッピングできます。このソリューションは、すべてのデータ・ソースに対してパフォーマンスの低下を引き起こす、ODBCのような他の中間層用のアプリケーションを記述することより優れています。OCIを使用するとOracleデータ・ソースにアクセスするのに特に問題は発生しませんが、ODBCがOracle以外のデータ・ソースにアクセスする場合と同じ問題が発生します。

コールバック関数の制限

コールバック関数の制限について詳しく説明します。

OCIEnvCallback()などのコールバック関数の使用方法には、いくつかの制約があります。

  • コールバックからは、OCIUserCallbackRegister()OCIUserCallbackGet()OCIHandleAlloc()およびOCIHandleFree()以外のOCI関数をコールできません。これらの関数であっても、ユーザー・コールバックでコールされている場合、コールバックは再帰を避けるためにコールされません。たとえば、OCIHandleFree()OCILogoff()のコールバックでコールされている場合は、OCILogoff()のコールバックの実行中にOCIHandleFree()のコールバックを行うことはできません。

  • コールバックでは、環境ハンドルまたはエラー・ハンドルなどのOCIデータ構造を変更できません。

  • OCIUserCallbackRegister()コール自体または次のいずれのコールに対しても、コールバックは登録できません。

    • OCIUserCallbackGet()

    • OCIEnvCreate()

    • OCIInitialize() (非推奨)

    • OCIEnvNlsCreate()

OCIコールバックの例

OCIコールバックの使用例を示します。

OCIStmtPrepare2()コールに対してそれぞれ最初のコールバック、置換コールバックおよび最後のコールバックを登録している5つのパッケージがあるとします。つまり、ORA_OCI_UCBPKG変数が、例14-2のように設定されています。

各パッケージpkgN (Nは1から5)では、pkgNInit()関数およびPkgNEnvCallback()関数を、例14-3のように指定します。

例14-4では、pkgNEnvCallback()関数での最初、置換および最後のコールバックの登録方法を示しています。

最後に、例14-5では、アプリケーションのソース・コードで、ユーザー・コールバックをNULL ucbDescに登録する方法を示しています。

例14-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()が次に実行されます。

例14-2 ORA_OCI_UCBPKG変数に対する環境変数の設定

setenv ORA_OCI_UCBPKG "pkg1;pkg2;pkg3;pkg4;pkg5" 

例14-3 pkgNInit()関数とPkgNEnvCallback()関数の指定

pkgNInit(void *metaCtx, void *libCtx, ub4 argfmt, sword argc, void **argv)
{
  return OCISharedLibInit(metaCtx, libCtx, argfmt, argc, argv, pkgNEnvCallback);
}

例14-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;
}
 

例14-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);
 

例14-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()

関連項目:

OCIStmtPrepare2()

外部プロシージャからのOCIコールバック

外部プロシージャからのOCIコールバックの使用に関する追加参照情報を提供します。

外部プロシージャからコールバックとして使用できるいくつかのOCI関数があります。

関連項目:

  • 外部プロシージャからコールバックとして使用できる関数のリストについては、「OCIカートリッジ関数」を参照してください。

  • PL/SQLコードからコールできるCサブルーチンの作成方法の詳細は、使用可能なOCIコールのリストおよびコード例も含め、『Oracle Database開発ガイド』を参照してください。