EternalWindows
ODBC / ドライバへの接続

アプリケーションがODBCドライバを通じてデータベースへアクセスするには、常にodbc32.dllを介することになります。 odbc32.dllは、アプリケーションの代わりにODBCドライバの適切な関数を呼び出すのが目的ですが、 それ以外にもODBCドライバから返されたエラーを特定のメモリに格納する機能を提供しています。 アプリケーションはこのメモリを、環境ハンドル、接続ハンドル、ステートメントハンドルのいずれかで表すことになります。

環境ハンドルは、接続ハンドルを内部で管理するハンドルであり、 アプリケーションが接続しているODBCドライバの数が格納されています。 つまり、odbc32.dllは、このハンドルを通じて、 アプリケーションがどれだけのODBCドライバに接続しているかを把握することができます。 一方、接続ハンドルは、1つのODBCドライバに関する接続情報が格納し、 次節で取り上げるステートメントハンドルもこのハンドルの内部で管理されます。 アプリケーションがSQLAllocHandleを呼び出すと、 内部で情報を格納するためのメモリが確保され、 それを識別するハンドルが返ります。

SQLRETURN SQLAllocHandle(
  SQLSMALLINT HandleType,
  SQLHANDLE InputHandle,
  SQLHANDLE *OutputHandlePtr
);

HandleTypeは、ハンドルのタイプを表す定数を指定します。 InputHandleは、取得するハンドルに関連付けるハンドルを指定します。 OutputHandlePtrは、ハンドルを受け取る変数のアドレスを指定します。

不要になったハンドルは、SQLFreeHandleで開放することになります。

SQLRETURN SQLFreeHandle(
  SQLSMALLINT HandleType,
  SQLHANDLE Handle
);

HandleTypeは、ハンドルのタイプを表す定数を指定します。 Handleは、開放したいハンドルを指定します。

SQLAllocHandleで接続ハンドルを取得することができたら、 SQLConnectでODBCドライバに接続することができます。

SQLRETURN SQLConnect(
  SQLHDBC ConnectionHandle,
  SQLCHAR *ServerName,
  SQLSMALLINT NameLength1,
  SQLCHAR *UserName,
  SQLSMALLINT NameLength2,
  SQLCHAR *Authentication,
  SQLSMALLINT NameLength3
);

ConnectionHandleは、接続ハンドルを指定します。 ServerNameは、データソース名を指定します。 このデータソースに含まれているODBCドライバと接続されることになります。 NameLength1は、SQL_NTSを指定します。 SQL_NTSは、NULL文字で終了する文字列であることを意味しています。 UserNameは、ユーザー名を指定します。 NameLength2は、SQL_NTSを指定します。 Authenticationは、パスワードを指定します。 NameLength3は、SQL_NTSを指定します。

SQLConnectが成功すれば、接続ハンドルにODBCドライバの情報が格納され、 ODBCドライバに接続したことになるわけですが、 具体的には内部でどのようなことが行われているのでしょうか。 まず、対象のODBCドライバ(DLL)が呼び出し側プロセスのアドレス空間にロードされます。 次に、そのODBCドライバがエクスポートしているSQLAllocHandleとSQLConnectが呼ばれ、 ODBCドライバ自身に必要な作業を行わせるようにします。 そして、目的の作業を終えたODBCドライバは、 自身がエクスポートする各種関数のアドレスを関数テーブルとして接続ハンドルに格納します。 これにより、ODBCドライバマネージャは、 アプリケーションから呼び出された関数内で、 対応するODBCドライバの関数を呼び出すことができます。 SQLConnectの呼び出しを終えたアプリケーションは、 SQLを実行するための作業に入ることになりますが、 これについては次節で説明します。

ODBCドライバに対しての接続を終了する場合は、SQLDisconnectを呼び出します。 この関数を呼び出して、さらに各種ハンドルも開放した場合、 ODBCドライバはプロセスのアドレス空間からアンロードされることになります。

SQLRETURN SQLDisconnect(
  SQLHDBC ConnectionHandle
);

ConnectionHandleは、接続ハンドルを指定します。

ODBCドライバに接続できた場合は、 SQLGetInfoを呼び出してドライバに関する情報を取得することができます。

SQLRETURN SQLGetInfo(
  SQLHDBC ConnectionHandle,
  SQLUSMALLINT InfoType,
  SQLPOINTER InfoValuePtr,
  SQLSMALLINT BufferLength,
  SQLSMALLINT *StringLengthPtr
);

ConnectionHandleは、接続ハンドルを指定します。 InfoTypeは、取得する情報のタイプを表す定数を指定します。 InfoValuePtrは、情報を受け取るバッファを指定します。 BufferLengthは、InfoValuePtrのサイズを指定します。 StringLengthPtrは、InfoValuePtrに格納されたサイズを受け取る変数のアドレスを指定します。

環境ハンドルや接続ハンドルなどの各種ハンドルを使用して関数が失敗した場合、 そのハンドルにエラー情報が格納されることになっています。 このエラー情報を取得するには、SQLGetDiagRecを呼び出します。

SQLRETURN SQLGetDiagRec(
  SQLSMALLINT HandleType,
  SQLHANDLE Handle,
  SQLSMALLINT RecNumber,
  SQLCHAR *SQLState,
  SQLINTEGER *NativeErrorPtr,
  SQLCHAR *MessageText,
  SQLSMALLINT BufferLength,
  SQLSMALLINT *TextLengthPtr
);

HandleTypeは、ハンドルのタイプを表す定数を指定します。 Handleは、ハンドルを指定します。 RecNumberは、取得するエラーのインデックスを指定します。 通常は、1つだけエラーが発生していると思われるため、 最初のインデックスを示す1を指定します。 SQLStateは、5文字の状態コードを受け取る変数のアドレスを指定します。 NativeErrorPtrは、エラーコードを受け取る変数のアドレスを指定します。 状態コードはどのODBCドライバでも同じ意味を持ちますが、 エラーコードはODBCドライバによって意味が異なります。 MessageTextは、エラー内容を示す文字列を受け取るバッファを指定します。 BufferLengthは、MessageTextに指定したバッファのサイズを指定します。 TextLengthPtrは、MessageTextに格納された文字列のサイズを受け取る変数のアドレスを指定します。

今回のプログラムは、SQLConnectを呼び出してODBCドライバに接続し、 そのドライバに関する情報をSQLGetInfoで取得します。

#include <windows.h>
#include <sqlext.h>

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	SQLHENV     henv;
	SQLHDBC     hdbc;
	SQLRETURN   nResult;
	TCHAR       szDriverName[256];
	TCHAR       szDbmsName[256];
	SQLSMALLINT nSize;

	nResult = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
	if (nResult != SQL_SUCCESS && nResult != SQL_SUCCESS_WITH_INFO)
		return 0;
	
	nResult = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0);
	if (nResult != SQL_SUCCESS && nResult != SQL_SUCCESS_WITH_INFO) {
		SQLFreeHandle(SQL_HANDLE_ENV, henv);
		return 0;
	}
	
	nResult = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
	if (nResult != SQL_SUCCESS && nResult != SQL_SUCCESS_WITH_INFO) {
		SQLFreeHandle(SQL_HANDLE_ENV, henv);
		return 0;
	}

	nResult = SQLConnect(hdbc, (SQLTCHAR *)TEXT("sample_dsn"), SQL_NTS, (SQLTCHAR *)TEXT(""), SQL_NTS, (SQLTCHAR *)TEXT(""), SQL_NTS);
	if (nResult != SQL_SUCCESS && nResult != SQL_SUCCESS_WITH_INFO) {
		TCHAR       szState[6];
		TCHAR       szErrorMsg[1024];
		SQLINTEGER  nErrorCode;
		
		SQLGetDiagRec(SQL_HANDLE_DBC, hdbc, 1, (SQLTCHAR *)szState, &nErrorCode, (SQLTCHAR *)szErrorMsg, sizeof(szErrorMsg), &nSize);
		MessageBox(NULL, szErrorMsg, NULL, MB_ICONWARNING);
		SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
		SQLFreeHandle(SQL_HANDLE_ENV, henv);
		return 0;
	}
	
	SQLGetInfo(hdbc, SQL_DRIVER_NAME, szDriverName, sizeof(szDriverName) / sizeof(TCHAR), &nSize);
	SQLGetInfo(hdbc, SQL_DBMS_NAME, szDbmsName, sizeof(szDbmsName) / sizeof(TCHAR), &nSize);
	MessageBox(NULL, szDriverName, szDbmsName, MB_OK);

	SQLDisconnect(hdbc);
	SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
	SQLFreeHandle(SQL_HANDLE_ENV, henv);

	return 0;
}

まず、SQLAllocHandleにSQL_HANDLE_ENVを指定して環境ハンドルを取得します。 このときは、第2引数に何らかのハンドルを指定する必要がないので、 SQL_NULL_HANDLEを指定します。 環境ハンドルを取得したら、SQLSetEnvAttrを呼び出して、使用するODBCのバージョンを設定します。 第2引数にSQL_ATTR_ODBC_VERSIONを指定した場合は、第3引数がバージョン値として解釈され、 SQL_OV_ODBC3を指定していることから、ODBC 3.xの関数を呼び出せるようになります。 2回目のSQLAllocHandleでは、第1引数にSQL_HANDLE_DBCを指定することによって接続ハンドルを取得しています。 このときは、第2引数に環境ハンドルを指定することになります。 接続ハンドルを取得したら、SQLConnectを呼び出してODBCドライバに接続します。

nResult = SQLConnect(hdbc, (SQLTCHAR *)TEXT("sample_dsn"), SQL_NTS, (SQLTCHAR *)TEXT(""), SQL_NTS, (SQLTCHAR *)TEXT(""), SQL_NTS);
if (nResult != SQL_SUCCESS && nResult != SQL_SUCCESS_WITH_INFO) {
	TCHAR       szState[6];
	TCHAR       szErrorMsg[1024];
	SQLINTEGER  nErrorCode;
	
	SQLGetDiagRec(SQL_HANDLE_DBC, hdbc, 1, (SQLTCHAR *)szState, &nErrorCode, (SQLTCHAR *)szErrorMsg, sizeof(szErrorMsg), &nSize);
	MessageBox(NULL, szErrorMsg, NULL, MB_ICONWARNING);
	SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
	SQLFreeHandle(SQL_HANDLE_ENV, henv);
	return 0;
}

第2引数に指定されたデータソースが実際に存在する場合、 そのデータソースに関連するODBCドライバがロードされます。 第4引数と第6引数はユーザー名とパスワードを指定することができますが、 通常はデータソースに関連付けられたユーザー名とパスワードで接続を行うはずですから、 それらが使用されるように空の文字列を指定します。 関数が失敗した場合は、原因を調べるためにSQLGetDiagRecを呼び出すことができます。 SQLConnectで発生したエラーは接続ハンドルに格納されるため、 第1引数にSQL_HANDLE_DBCを指定し、第2引数に接続ハンドルを指定します。 エラーの内容は、第6引数から確認することができますが、 より詳しく原因を調べたい場合は、第5引数のエラーコードとDBMSの名前を基にインターネットで検索するとよいでしょう。 たとえば、エラーコードが1045でDBMSがMySQLならば、「MySQL 1045」のように検索すればよいでしょう。

SQLConnectでODBCドライバに接続したら、 SQLGetInfoでそのドライバに関する情報を取得することができます。 第2引数にSQL_DRIVER_NAMEを指定した場合はドライバの名前が返り、 SQL_DBMS_NAMEを指定した場合はDBMSの名前が返ります。 接続が不要になったら、まずSQLDisconnectで接続を終了し、 その後にSQLFreeHandleで接続ハンドルを開放します。 そして、最後に環境ハンドルを開放します。

データソースの選択

使用するデータソースをコード上に記述するのではなく、 ユーザーに選択させるようにするには、SQLDriverConnectを呼び出します。

TCHAR szConnectionOut[1024];

nResult = SQLDriverConnect(hdbc, hwnd, (SQLTCHAR *)TEXT(""), SQL_NTS, (SQLTCHAR *)szConnectionOut, sizeof(szConnectionOut) / sizeof(TCHAR), &nSize, SQL_DRIVER_PROMPT);

第2引数にウインドウハンドル(GetDesktopWindowなどの戻り値も可)を指定し、 最後の引数にSQL_DRIVER_PROMPTを指定すると、 データソースを選択するためのダイアログが表示されます。 第3引数は空文字で構いませんが、DSN=sample_dsnのようにすると、 デフォルトでデータソースを選択することができます。 第5引数には、ドライバへの接続に使用された属性文字列が返ります。 これは、前節で取り上げたSQLConfigDataSourceと同じ形式です。



戻る