Ruby1.8.7-p160でirbが落ちる件の顛末 (Cランタイム問題)

artonさんにもコメントを頂いているが、スタックトレースを見るとランタイムを経由しつつも、やはりreadlineが原因で落ちているのは明らかだ。

私が元々使用しているreadlineは(う)さんがリンクを張っているサイトから頂いてきたアーカイブの中のバイナリ(readline.dll)である。
Porting Libraries to Win32
readline-4.3-2-mswin32.zip
readline-4.3-2-src.zip

このソースコードを使って拡張ライブラリィ(readline.so)を作成し、Rubyに組み込んで使っているのだが、readline.dllが問題があるのはほぼ明らかになったので、今回は同バイナリと同じサイトからダウンロードできるreadline-4.3-2-src.zip中のソースコードを使ってDLLを作る所からやってみた。

結果からするとこれは上手くいった。ソースから新たにコンパイルされたreadline.dllとreadline.soの配置により、irbは無事動作したのである。

しかしこれでは駄目だった。後述するが根本的な解決にはならないのである。



以下は、私がVC 2005に付属のnmakeを使用してコンパイルしたruby.exeとartonさんが公開しているruby.exeを、それぞれDependecy Walkerでライブラリィの依存性を表示した結果である。

私がコンパイルしたruby.exe


artonさんが公開されているruby.exe

こうすると一目瞭然、私がコンパイルしたruby.exeは一般的なMSVCRT.DLLではなくVisual C++ 2005のCランタイムであるMSVCR80.DLLに依存しているのである。
単に依存しているだけであればこのランタイムを配布して動かせば良いのだが、このランタイムはMSVCRT.DLLとの混在で問題を引き起こす場合があるらしいのだ。

以下はMicrosoft Visual Studio 2005/.NET Framework 2.0におけるCランタイムの扱いに関する記述だ。

複数の DLL または EXE がある場合は、異なるバージョンの Visual C++ を使用しているかどうかにかかわらず、複数の CRT が存在することがあります。たとえば、CRT を複数の DLL に静的にリンクした場合も、同じ問題が発生します。この静的 CRT の問題が発生した場合は、/MD でコンパイルして CRT DLL を使用するという対処が一般的でした。CRT DLL は、msvcr80.dll という名前に変更されたため、アプリケーションの一部のコンポーネントは msvcrt.dll にリンクされ、他のコンポーネントは msvcr80.dll にリンクされる場合があります。msvcrt.dll と msvcr80.dll の境界を越えて CRT リソースを渡す場合は、CRT の不一致によって問題が発生するため、Visual C++ 2005 でプロジェクトをコンパイルし直す必要があります。

アプリケーションで msvcrt.dll と msvcr80.dll の両方を使用した場合に発生する問題 /C ランタイム ライブラリ (CRT) - MSDNライブラリィ

ここからのリンクには具体的に発生する問題についても言及がある。

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

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

私がコンパイルしたMSVCR80.DLLに依存しているruby.exeは同様にコンパイルされたであろう、MSVCRT.DLLを使用している他の拡張ライブラリィとの間で問題が発生する可能性があるということだ。

対策は2通りか

1. MSVCR80.DLLのままで使う
2. MSVCR80.DLLへの依存は一切持たない

せっかくコンパイルしたのだから1.で良さそうなものだが、現在出回っているWindowsプラットホーム向けのRuby拡張ライブラリィやバイナリの殆どはMSVCRT.DLLを基準にMakeFileなりバイナリを作成しており、できればそちらに合わせたい。
ランタイムをMSVCR80.DLLに統一する案もあるが、そうすると使用する全ての拡張ライブラリィをコンパイルする必要がある。

やはり2が無難ということだな。

いやーまいった。やはり修行が足りないなぁ。