DLL の境界を越えて CRT オブジェクトを渡す場合に発生する可能性のあるエラー

更新 : 2007 年 11 月

ファイル ハンドル、ロケール、環境変数などの C ランタイム (CRT) オブジェクトを DLL の境界を越えて渡す場合 (DLL の境界を越えた関数の呼び出し)、DLL またはその DLL を呼び出すファイルが異なる CRT ライブラリのコピーを使用していると、予想外の動作が発生する可能性があります。

メモリを (new または malloc で明示的に、または strdupstrstreambuf::str などで暗黙的に) 割り当て、DLL の境界を越えてポインタを渡してそこで解放する場合、同じような問題が発生する可能性があります。これは、DLL とそのユーザーが異なる CRT ライブラリのコピー使用している場合、メモリ アクセス違反またはヒープ破損の原因になります。

この問題の症状としては、デバッグ中の出力ウィンドウに次のようなエラーが出力されることもあります。

HEAP[]: Invalid Address specified to RtlValidateHeap(#,#)

原因

CRT ライブラリの各コピーは固有の状態を持ちます。このため、ファイル ハンドル、環境変数、ロケールなどの CRT オブジェクトは、これらのオブジェクトが割り当てられた、または設定された CRT のコピーに対してのみ有効です。DLL とそのユーザーが異なる CRT ライブラリのコピーを使用している場合、DLL の境界を超えてこれらの CRT オブジェクトを渡して、それらが相手側で適切に処理されることを期待することはできません。

また、CRT ライブラリのコピーごとに独自のヒープ マネージャを持つため、ある CRT ライブラリでメモリを割り当て、ポインタを DLL の境界を越えて渡し、これを別の CRT ライブラリのコピーによって解放すると、ヒープが破損する可能性があります。

境界を越えて CRT オブジェクトを渡す DLL を設計したり、メモリの割り当て後、DLL の外部で解放される DLL を設計したりする場合には、その DLL と同じ CRT ライブラリのコピーを使用するようにその DLL のユーザーを制限します。DLL とそのユーザーは、両方が同じバージョンの CRT DLL にリンクしている場合のみ、同じ CRT ライブラリのコピーを使用します。これは、Visual C++ 5.0 でビルドされたアプリケーションと、4.1 またはそれ以前のバージョンの Visual C++ でビルドされた DLL を混同して使用する場合に問題になります。Visual C++ 4.1 によって使用される CRT ライブラリの DLL バージョンは msvcrt40.dll で、Visual 5.0 によって使用される DLL バージョンは msvcrt.dll であることから、これらの DLL と同じ CRT ライブラリのコピーを使用するようにアプリケーションをビルドすることはできません。

ただし、これには例外があります。Windows 2000 の英語 (US) バージョンと、ドイツ語、フランス語、チェコ語などのいくつかのローカライズ版には、msvcrt40.dll のフォワーダ バージョン (バージョン 4.20) が付属しています。その結果、DLL が msvcrt40.dll にリンクし、そのユーザーが msvcrt.dll にリンクしている場合でも、msvcrt40.dll への呼び出しはすべて msvcrt.dll に転送されるため、同じ CRT ライブラリのコピーを使用できます。

ただし、日本語版、韓国版、中国語版などのいくつかのローカライズ版の Windows 2000 では、この msvcrt40.dll のフォワーダ バージョンを使用できません。そのため、アプリケーションがこれらのオペレーティング システムをターゲットとしている場合には、msvcrt40.dll に依存しない DLL のアップグレード バージョンを取得するか、同じ CRT ライブラリのコピーの使用に依存しないようにアプリケーションを変更する必要があります。自身で DLL を開発した場合には、Visual C++ 4.2 以降で DLL をビルドし直します。サードパーティの DLL の場合、販売元に連絡して、アップグレード バージョンを要求してください。

この msvcrt40.dll のフォワーダ DLL バージョン (バージョン 4.20) は再頒布できません。

サンプル

説明

DLL の境界を越えてファイル ハンドルを渡す例を次に示します。

DLL と .exe ファイルは /MD でビルドされ、CRT の同じコピーを共有します。

CRT の別々のコピーを使用するように /MT でビルドし直した場合、ビルド結果の test1Main.exe を実行すると、アクセス違反になります。

コード

// test1Dll.cpp
// compile with: /MD /LD
#include <stdio.h>
__declspec(dllexport) void writeFile(FILE *stream)
{
   char   s[] = "this is a string\n";
   fprintf( stream, "%s", s );
   fclose( stream );
}

コード

// test1Main.cpp
// compile with: /MD test1dll.lib
#include <stdio.h>
#include <process.h>
void writeFile(FILE *stream);

int main(void)
{
   FILE  * stream;
   errno_t err = fopen_s( &stream, "fprintf.out", "w" );
   writeFile(stream);
   system( "type fprintf.out" );
}

[出力]

this is a string

サンプル

説明

DLL の境界を越えて環境変数を渡す例を次に示します。

コード

// test2Dll.cpp
// compile with: /MT /LD
#include <stdio.h>
#include <stdlib.h>

__declspec(dllexport) void readEnv()
{
   char *libvar;
   size_t libvarsize;

   /* Get the value of the MYLIB environment variable. */ 
   _dupenv_s( &libvar, &libvarsize, "MYLIB" );

   if( libvar != NULL )
      printf( "New MYLIB variable is: %s\n", libvar);
   else
      printf( "MYLIB has not been set.\n");
   free( libvar );
}

コード

// test2Main.cpp
// compile with: /MT /link test2dll.lib
#include <stdlib.h>
#include <stdio.h>

void readEnv();

int main( void )
{
   _putenv( "MYLIB=c:\\mylib;c:\\yourlib" );
   readEnv();
}

出力

MYLIB has not been set.

DLL と .exe ファイルの両方が、同じ CRT のコピーを使用するように /MD でビルドされている場合、プログラムの実行に成功し、次のメッセージが出力されます。

New MYLIB variable is: c:\mylib;c:\yourlib

参照

参照

C ランタイム ライブラリ