[FIX] SQL_NUMERIC または SQL_C_BINARY データを使用した Jet ODBC Driver のメモリ リーク

この記事は、以前は次の ID で公開されていました: JP273772
この資料は、アーカイブされました。これは "現状のまま" で提供され、更新されることはありません。
現象
Jet ODBC ドライバの SQLBindParameter 機能を呼び出すとき、SQL_NUMERIC の SQL タイプを結合するかまたは SQL_C_BINARY を SQL_WCHAR に結合すると、メモリ リークが発生します。

パフォーマンス モニタ (PerfMon) を使用してプライベート バイトの処理を観察していると、メモリは少しずつ着実に増加していき、ステートメントや接続が閉じられてもメモリは解放されません。
原因
バッファは、Jet ODBC ドライバ内部で、ODBC パラメータ データ型を Jet データベース エンジン用のネイティブなデータ型に変換するために使用されます。これらのバッファは、ステートメント ハンドルに保持されているポインタを通して、パラメータ記述子 (IPD) の一部として参照されます。

普通、コードはポインタが有効なメモリ バッファを既に参照しているかをチェックし、バッファが存在すればそれを再利用します。しかし、SQL_NUMERIC データ型を結合しているとき、または SQL_C_BINARY を SQL_WCHAR に結合しているとき、新しいメモリはそれを最初にチェックすることなしにポインタに割り当てられ、前の値は上書きされます。

状況

Windows 2000

この問題を解決するためのモジュールは、Windows 2000 日本語版 Service Pack 2 以降に含まれております。
Windows 2000 日本語版の最新 Service Pack については、以下 Web サイトから入手できます。
詳細
処理を実行している間、処理されているメモリを定期的に検査すると、そのたびに 0x100000 (1048576) のバイト数が割り当てられていくことに気づくはずです。さらに処理が続行されると、その処理によってメモリは最終的に使い果たされ、応答しなくなる (ハングアップする) するか、障害が発生します。

問題の再現手順

  1. Microsoft Visual C++ コンソール アプリケーション内に続くコードをコピーし、その後そのコードをコンパイルします。その際、データソース名、ユーザー ID、およびパスワードを変更しなければならないことにご注意ください。
    #include <windows.h>#include <sql.h>#include <sqlext.h>#include <tchar.h>#include <stdlib.h>#include <stdio.h>#define LEAK_NUMERIC 1		//Use this to determine NUMERIC or BINARY leakvoid HandleError(SQLHANDLE	hHandle, SQLSMALLINT hType, RETCODE RetCode){	SQLSMALLINT	iRec = 0;	SQLINTEGER	iError;	TCHAR		szMessage[1000];	TCHAR		szState[SQL_SQLSTATE_SIZE];	if (RetCode == SQL_INVALID_HANDLE)	{		fprintf(stderr,"Invalid handle!\n");		return;	}	while (SQLGetDiagRec(hType,			 hHandle,			 ++iRec,			 (SQLCHAR *)szState,			 &iError,			 (SQLCHAR *)szMessage,			 (SQLSMALLINT)(sizeof(szMessage) / sizeof(TCHAR)),			 (SQLSMALLINT *)NULL) == SQL_SUCCESS)	{		fprintf(stderr,TEXT("[%5.5s] %s (%d)\n"),szState,szMessage,iError);	}}char* szConnStringIn = "Driver={Microsoft Access Driver (*.mdb)};DBQ=E:\\JetLeak\\TestDatabase.mdb";char* szDropTable = "DROP TABLE LeakTable";//char* szInsertStatement = "INSERT INTO LeakTable VALUES (?)";char* szSelectStatement = "SELECT * FROM LeakTable WHERE val1 = ?";const int nParamCount = 1; #if LEAK_NUMERICchar* szCreateTable = "CREATE TABLE LeakTable (val1 long)";#elsechar* szCreateTable = "CREATE TABLE LeakTable (val1 varchar(10))";#endifvoid main(int argc, char* argv[]){	SQLHENV henv;	SQLHDBC hdbc;	SQLHSTMT hstmt;	SQLRETURN nstatus;	char szConnStringOut[1024];	SQLSMALLINT cbConnOut;	SQLINTEGER status[nParamCount];#if LEAK_NUMERIC	SQLCHAR szParam[nParamCount][10] = {"12345"};#else	BYTE szParam[nParamCount][10] = {0x31,0x33,0x34,0x39};#endif    	//Not checking the return codes in some cases for clarity.		nstatus = SQLAllocHandle(SQL_HANDLE_ENV,NULL,&henv);	nstatus = SQLSetEnvAttr(henv,SQL_ATTR_ODBC_VERSION,(SQLPOINTER) SQL_OV_ODBC3,0);	nstatus = SQLAllocHandle(SQL_HANDLE_DBC,henv,&hdbc);	nstatus = SQLDriverConnect(hdbc,			NULL,			(SQLCHAR*) szConnStringIn,			SQL_NTS,			(SQLCHAR*) szConnStringOut,			sizeof (szConnStringOut),			&cbConnOut,			SQL_DRIVER_COMPLETE);								if (nstatus != SQL_SUCCESS && nstatus != SQL_SUCCESS_WITH_INFO)	{		HandleError(hdbc,SQL_HANDLE_DBC,nstatus);		return;	}	nstatus = SQLAllocHandle(SQL_HANDLE_STMT,hdbc,&hstmt);		nstatus = SQLExecDirect(hstmt, (SQLCHAR*) szDropTable, SQL_NTS);	nstatus = SQLExecDirect(hstmt, (SQLCHAR*) szCreateTable, SQL_NTS);	if (!SQL_SUCCEEDED(nstatus))	{		HandleError(hstmt,SQL_HANDLE_STMT,nstatus);	}	int i;	//only one parameter in this case	for (i=0; i < nParamCount; i++)	{		status[i] = SQL_NTS;#if LEAK_NUMERIC		nstatus = SQLBindParameter(hstmt,			i+1,			SQL_PARAM_INPUT,			SQL_C_CHAR,				SQL_NUMERIC, 			10, 			0,			szParam[i],			10,			&status[i]);#else		nstatus = SQLBindParameter(hstmt,			i+1,			SQL_PARAM_INPUT,			SQL_C_BINARY,				SQL_WCHAR,			10, 			0,			szParam[i],			10,			&status[i]);#endif			}	nstatus = SQLPrepare(hstmt,(SQLCHAR*) szSelectStatement, SQL_NTS);	if (nstatus != SQL_SUCCESS)	{		HandleError(hstmt,SQL_HANDLE_STMT,nstatus);	}	for (i=0; i < 100000; i++)	{		if (i % 100 == 0)		{			printf("Selected %d times\n", i);			//printf("Inserted %d records\n", i);			Sleep(100);		}		nstatus = SQLExecute(hstmt);		if (nstatus != SQL_SUCCESS)		{			HandleError(hstmt,SQL_HANDLE_STMT,nstatus);		}		SQLFreeStmt(hstmt, SQL_CLOSE);	}	nstatus = SQLExecDirect(hstmt, (SQLCHAR*) "DELETE FROM LeakTable", SQL_NTS);	if (nstatus != SQL_SUCCESS)	{		HandleError(hstmt,SQL_HANDLE_STMT,nstatus);	}	SQLFreeStmt(hstmt, SQL_CLOSE);	SQLDisconnect(hdbc);}
    : 定数 LEAK_NUMERIC を使用して、SQL_NUMERIC か SQL_C_BINARY のいずれかのリークを示すことができます。
  2. 接続ストリングの場所によって指定されている、新しい空白の Microsoft Access データベースを作成します。
  3. コードの実行を開始し、Performance Monitor を使用して処理に対するプライベート バイト カウンタを監視します。

    コードが動作している間、プライベート バイト カウンタはたえず増加しています。
関連情報
この資料は米国 Microsoft Corporation から提供されている Knowledge Base の Article ID 273772 (最終更新日 2001-02-22) をもとに作成したものです。

jet odbc driver odbcjt32.dll memory leak sql_numeric sql_c_binary sqlbindparameter parameters
プロパティ

文書番号:273772 - 最終更新日: 01/10/2015 12:49:07 - リビジョン: 3.1

  • Microsoft ODBC (Open Database Connectivity) Driver for Access 4.0
  • Microsoft Data Access Components 2.5
  • Microsoft Data Access Components 2.5 Service Pack 1
  • kbnosurvey kbarchive kbbug kbfix kbjet kbmdac250bug kbgrpdsvcdb kbgrpdsmdac kbmdac250sp1bug kbhotfixserver KB273772
フィードバック