HaskellからRubyを呼んでみる(2)
id:ha-tan:20061031:1162240689の続き。拡張ライブラリをロードできるようにしてみました。とりあえずopen-uriをrequireしてWebページをダウンロードしてみます。昨日は静的ライブラリをリンクしていましたが、今日はDLLをリンクしています(そうしないと.soの拡張ライブラリがロードできません)。
ファイル: ruby_test.hs
module Main (main) where import Foreign.C.String (CString, newCString, withCString) import Foreign.Marshal.Alloc (free) import Foreign.Marshal.Array (withArrayLen) import Foreign.Marshal.Utils (new) import Foreign.Ptr (Ptr) foreign import ccall unsafe "NtInitialize" cNtInitialize :: Ptr Int -> Ptr (Ptr CString) -> IO () foreign import ccall unsafe "ruby_init" rubyInit :: IO () foreign import ccall unsafe "ruby_options" cRubyOptions :: Int -> Ptr CString -> IO () foreign import ccall unsafe "rb_eval_string" cRubyEvalString :: CString -> IO () withCStringArrayLen :: [String] -> (Int -> Ptr CString -> IO a) -> IO a withCStringArrayLen ss f = do css <- mapM newCString ss a <- withArrayLen css f mapM_ free css return a ntInitialize :: [String] -> IO () ntInitialize ss = do withCStringArrayLen ss $ \ len css -> do len' <- new len css' <- new css cNtInitialize len' css' free css' free len' rubyInitOptions :: [String] -> IO () rubyInitOptions ss = do ntInitialize ss rubyInit rubyOptions ss rubyOptions :: [String] -> IO () rubyOptions = flip withCStringArrayLen cRubyOptions rubyEvalString :: String -> IO () rubyEvalString = flip withCString cRubyEvalString main :: IO () main = do rubyInitOptions ["<unknown>", "-I./lib", "-I./lib/i386-mingw32", "-e", ""] rubyEvalString "require 'open-uri'" rubyEvalString "open('http://d.hatena.ne.jp/ha-tan/', 'r') {|http| print http.read }"
実行手順
1. ruby-1.8.4-i386-mingw32.tar.gzから必要なファイルを抜き出します。
$ tar zxvf ruby-1.8.4-i386-mingw32.tar.gz $ cp usr/local/lib/libmsvcrt-ruby18.dll.a . $ cp usr/local/bin/msvcrt-ruby18.dll . $ cp -r usr/local/lib/ruby/1.8/ ./lib $ rm -rf usr
2. 以下のコマンドでコンパイルします。
$ ghc -W -fno-warn-unused-matches -ffi -o ruby_test ruby_test.hs libmsvcrt-ruby18.dll.a
3. できあがったプログラムを実行します。
$ ./ruby_test.exe <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=euc-jp"> <meta http-equiv="Content-Style-Type" content="text/css"> <meta http-equiv="Content-Script-Type" content="text/javascript" ...
現在の技術的な課題
- 例外が発生すると[BUG] Segmentation faultとかいやな感じで終了するので何とかする必要があります。rb_eval_stringを直に使ってるのがまずいんじゃないかなと予想しています。rb_eval_string_protectあたりを使えば何とかなるかと。でもそうするとスタックトレースをどう出すのかが問題になりそうです。
- Ruby側で標準出力をhackしているみたいで、HaskellとRubyの両方で標準出力に出力すると出力が混ざります。Ruby側の出力オブジェクトを変更して、Haskell側に返すとかした方がよさそう。
- 上の問題にもからむのですが、Ruby側からHaskell側に値を返す方法がありません。それ用の拡張ライブラリをCで書いて、それ経由でRuby側から値を返すのがよさそう。
- GCの動きがよくわかりません(特にHaskell)。GCが走ったときにホントに大丈夫なんだろうか…