S-JIS[2025-06-14]

ODBCドライバー実装メモ

ODBCドライバーを実装する際の注意点。


概要

ODBCドライバーを実装する(自作する)ということは、ODBCで定義されている(C言語の)関数を実装してライブラリー(Windowsならdllファイル)を提供するということである。

そういう意味では通常のライブラリーを作るのと変わらないが、
ODBCの場合は、自作ライブラリーの関数がアプリケーションから直接呼ばれるのではなく、ODBCドライバーマネージャー(Windowsの場合はodbc32.dll)経由で呼ばれるという点が異なる。


ハンドルの生成順序と値

ODBCのアプリケーションでは、以下の順序でハンドルを生成してDBに接続する。

  1. SQLAllocHandle(ENV)を呼んでhEnvのインスタンスを生成する。
  2. SQLSetEnvAttr()を呼んでODBCバージョンを設定する。
  3. SQLAllocHandle(DBC)を呼んでhDbcのインスタンスを生成する。
  4. ODBCドライバー名を指定してSQLDriverConnect()を呼ぶ。

ODBCドライバーもSQLAllocHandle()を実装し、自分のライブラリー内で使用するインスタンスを生成する。

ここで疑問なのは、SQLDriverConnect()でドライバー名を指定するので、そこで初めて自分のODBCドライバーがロードされはずであり、すると、SQLAllocHandle()の呼び出しはどうなってるんだ?ということ。


実際には、おそらく以下のように動作している。
(ここでは、ODBCドライバーマネージャーをDMと略す)

  1. アプリケーションがSQLAllocHandle(ENV)を呼ぶ。
    1. DMが、DM用のEnvインスタンスを生成し、hEnvとして返す。
  2. アプリケーションがODBCバージョンを指定してSQLSetEnvAttr()を呼ぶ。
    1. DMが、EnvインスタンスにODBCバージョンを保存する。
  3. アプリケーションがSQLAllocHandle(DBC)を呼ぶ。
    1. DMが、DM用のDbcインスタンスを生成し、hDbcとして返す。
  4. アプリケーションがODBCドライバー名を指定してSQLDriverConnect()を呼ぶ。
    1. DMは、ODBCドライバー名を元にしてODBCドライバーを探し、ライブラリーをロードする。
    2. DMが、ODBCドライバーのSQLAllocHandle(ENV)を呼ぶ。
      1. ODBCドライバーは、ドライバー独自のEnvインスタンスを生成し、hEnvとして返す。
      2. DMは、自分のEnvインスタンスの中にドライバーのhEnvを保存する。
    3. DMが、保存していたODBCバージョンを引数にして、ODBCドライバーのSQLSetEnvAttr()を呼ぶ。
      1. ODBCドライバーは、バージョンチェックを行う。
    4. DMが、ODBCドライバーのSQLAllocHandle(DBC)を呼ぶ。
      1. ODBCドライバーは、ドライバー独自のDbcインスタンスを生成し、hDbcとして返す。
      2. ODMは、自分のDbcインスタンスの中にドライバーのhDbcを保存する。
    5. DMが、ODBCドライバーのSQLDriverConnect()を呼ぶ。
      1. ODBCドライバーは、DBに接続する。

つまり、SQLAllocHandle()の呼び出しは、どのODBCドライバーが使われるかが決定するまで遅延されるイメージ。
もしODBCドライバーのSQLAllocHandle(ENV)やSQLAllocHandle(DBC)でエラーが発生したら、アプリケーションにはSQLDriverConnect()のエラーとして伝わることになる。

実際、アプリケーションが受け取るhEnvやhDbcの値は、ODBCドライバーが返すhEnvやhDbcの値(構造体のアドレス)とは異なっている。

アプリケーションはODBCドライバーマネージャーから受け取ったhEnvやhDbcを使って関数を呼び出すが、
ODBCドライバーマネージャーは、内部で保持している「ドライバー用のhEnvやhDbc」を使ってODBCドライバーの関数を呼び出すのだろう。


関数名の末尾のA・W

ODBCの関数の中には、関数名の末尾にAやWが付いているものがある。

ODBC関数の引数で文字列を扱うものについては、何も付いていない関数(無印)と、A付き・W付きがある。
引数で直接文字列を扱わない関数には、A・Wが付く関数は無い。

AはANSI版とのことで、文字の型はSQL_CHAR(8ビット)。
文字列は基本的にASCIIを扱う。
実際には環境依存らしく、Windowsの場合はShift-JIS(おそらくMS932)で扱われるようだ。

WはWideChar版で、文字の型はSQL_WCHAR(16ビット)。
文字列はUTF-16(リトルエンディアン)で扱う。
(ちなみに、JavaのUTF-16はビッグエンディアンで、リトルエンディアンはUTF-16LE)


アプリケーションから呼ぶ場合、無印とA付きの関数は同じ挙動となる。(文字をSQL_CHARで扱う)

日本語を扱う場合は、アプリケーションからW付きの関数を呼ぶ。(文字をSQL_WCHARで扱う)


アプリケーションからAやWの関数を呼んでも、そのままODBCドライバーのAやWの関数が呼ばれるわけではない。

ODBCでは、アプリケーションからはODBCドライバーマネージャーの関数が呼ばれる。
そして、ODBCドライバーのW付きと無印の関数のどちらを呼ぶかは、ODBCドライバーマネージャーが決めるようだ。

したがって、アプリケーションからA付きの関数を呼んでもODBCドライバーのW付き関数が呼ばれるかもしれないし、
アプリケーションからW付きの関数を呼んでもODBCドライバーの無印関数が呼ばれるかもしれない。
(SQL_CHAR⇔SQL_WCHARの変換は、ODBCドライバーマネージャーがよしなに実施してくれる)

なお、ODBCドライバーのA付きの関数は呼ばれることが無いようだ。
(つまり、ODBCドライバーはA付きの関数を実装する必要は無い)

すなわち、ODBCドライバーとしては、無印の関数をANSI用として実装し、同時にW付きの関数を提供する。


Attr系関数

ODBCの関数には、属性の設定・取得を行う関数がある。

「属性なんかはデフォルト値でいいだろうから、実装は後回しにしよう…」と思うところだが、必須の属性があったりするので、早めに実装した方が良い。

属性は色々あるので、引数をデバッグログ出力しておき、呼ばれた属性について実装するのが良さげ。


Diag系関数

ODBCの関数内でエラーが発生した場合、SQLGetDiagRec(W)関数でエラーコードやエラーメッセージを取得できるようにする。

SQLGetDiagRec()の引数はハンドルになっており、hEnvやhDbc・hStmt等を指定する。
つまり、ハンドルの種類ごとにエラー情報を保持する。

あるハンドルを使う関数が呼ばれたら、まずそのハンドル内のエラー情報をクリアする。
で、関数内でエラーが発生したら、SQLGetDiagRec()で返せるようにエラー情報を構築・保存する。
エラー情報は複数保存・取得できるようにする必要がある。


ODBCドライバーの実装順序

ODBCドライバーの関数を実装するには、Diag系関数を除いて、以下の順に作っていくのがいいと思う。
(Diag系関数はエラーメッセージを返すので早めに実装した方がいいが、EnvやDbcの実装と密接に関係するので、その前後が良さそう)

  1. SQLAllocHandle(ENV), SQLFreeHandle(ENV)
  2. SQLSetEnvAttr()
  3. SQLAllocHandle(DBC), SQLFreeHandle(DBC)
  4. SQLGetInfo()
  5. SQLDriverConnect(), SQLDisconnect()
  6. SQLGetFunctions()
  7. SQLGetInfo()

ここまで実装すれば、アプリケーションからSQLDriverConnect()が呼び出せる。


次いで、hStmt(ステートメント)を扱う関数を実装する。

  1. SQLAllocHandle(STMT), SQLFreeHandle(STMT)
  2. SQLGetStmtAttr()
  3. SQLFetch()
  4. SQLGetData()

後は、SQLTables()やSQLExecDirect()といった関数を実装していく。


ODBCドライバーへ戻る / ODBCへ戻る / 技術メモへ戻る
メールの送信先:ひしだま