DLL の境界を越えて CRT オブジェクトを渡す場合に発生する可能性のあるエラー
ファイル ハンドル、ロケール、環境変数などの C ランタイム (CRT) オブジェクトを DLL との間で渡すと、コードでエラーが発生する可能性があります。 DLL と DLL を呼び出すファイルで CRT ライブラリの異なるコピーが使用されている場合、DLL 境界を越えた関数呼び出しが予期しない動作を引き起こす可能性があります。
関連する問題は、メモリを割り当てた後 (明示的に、またはmalloc
暗黙的に new
strdup
、 などstrstreambuf::str
) を割り当て、解放された DLL 境界を越えてポインターを渡すと発生する可能性があります。 このようなポインターは、DLL とそのコンシューマーが CRT ライブラリの異なるコピーを使用している場合、メモリ アクセス違反またはヒープの破損を引き起こす可能性があります。
この問題の別の兆候としては、HEAP[]: Invalid Address specified to RtlValidateHeap(#,#)
のようなデバッグ中の出力ウィンドウのエラーがあります
原因
CRT ライブラリのコピーはそれぞれ状態が異なります。アプリまたは DLL により、スレッド ローカル ストレージに保存されます。
ファイル ハンドル、環境変数、ロケールなどの CRT オブジェクトは、オブジェクトが割り当てられたか、または設定されたアプリや DLL の CRT コピーに対してのみ有効になります。 DLL とそのクライアントが CRT ライブラリの異なるコピーを使用している場合、DLL 境界を越えて渡されるときに、これらの CRT オブジェクトが正しく使用されるとは考えられません。
特に、Visual Studio 2015 以降のユニバーサル CRT より前の CRT バージョンに当てはまります。 Visual Studio 2013 以前で構築されたあらゆるバージョンの Visual Studio について、バージョン固有の CRT ライブラリがありました。 CRT の内部実装詳細 (データ構造や命名規則など) は、バージョンごとに異なります。 あるバージョンの CRT のためにコンパイルされたコードを別のバージョンの CRT DLL に動的にリンクすることは、以前はできませんでした。 そのように機能する場合もありましたが、あくまで偶然であり、設計によるものではありませんでした。
CRT ライブラリの各コピーには、独自のヒープ マネージャーがあります。 1 つの CRT ライブラリにメモリを割り当て、DLL 境界を越えてポインターを渡して CRT ライブラリの別のコピーによって解放すると、ヒープが破損する可能性があります。 DLL が DLL 境界を越えて CRT オブジェクトを渡す場合、または DLL の外部で解放されたメモリを割り当てる場合、DLL のクライアントは、DLL と同じ CRT ライブラリのコピーを使用する必要があります。
DLL とそのクライアントは通常、読み込み時に両方とも同じバージョンの CRT DLL にリンクされている場合にのみ、CRT ライブラリの同じコピーを使用します。 Visual Studio 2015 以降で使用されるユニバーサル CRT ライブラリの DLL バージョンは、一元的に展開された Windows コンポーネント (ucrtbase.dll
) であるため、Visual Studio 2015 以降のバージョンでビルドされたアプリでも同じです。 ただし、CRT コードが同じでも、あるヒープで割り当てられたメモリを別のヒープを使用するコンポーネントに渡すことはできません。
例: DLL 境界を越えてファイル ハンドルを渡す
説明
この例では、DLL 境界を越えてファイル ハンドルが渡されます。
DLL と .exe ファイルは /MD
で開発されます。そのため、1 つのコピーの CRT を共有します。
CRT の /MT
個別のコピーを使用するようにリビルドすると、結果として得られる test1Main.exe
アクセス違反が発生します。
DLL ソース ファイル test1Dll.cpp
:
// test1Dll.cpp
// compile with: cl /EHsc /W4 /MD /LD test1Dll.cpp
#include <stdio.h>
__declspec(dllexport) void writeFile(FILE *stream)
{
char s[] = "this is a string\n";
fprintf( stream, "%s", s );
fclose( stream );
}
実行可能なソース ファイル test1Main.cpp
:
// test1Main.cpp
// compile with: cl /EHsc /W4 /MD test1Main.cpp 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 境界を越えて環境変数を渡す
説明
この例では、DLL の境界を越えて環境変数が渡されます。
DLL ソース ファイル test2Dll.cpp
:
// test2Dll.cpp
// compile with: cl /EHsc /W4 /MT /LD test2Dll.cpp
#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
:
// test2Main.cpp
// compile with: cl /EHsc /W4 /MT test2Main.cpp 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.
CRT のコピーが 1 つだけ使用 /MD
されるように DLL ファイルと EXE ファイルの両方をビルドすると、プログラムは正常に実行され、次の出力が生成されます。
New MYLIB variable is: c:\mylib;c:\yourlib