椿の日記

たぶんプログラムの話をします

Haskellでiconv

苦戦しちゃったので作業メモを残しておきます。
ビルド環境はGHC 6.10.4です。

iconv.dllをインポートする

dllをダウンロード

http://www.kaoriya.net/
ここから、ビルド1.10-20060516のdllを取得してくる。

reimpのインストール

http://sourceforge.net/projects/mingw/files/
ここから、mingw utilitiesを取得してくる。
但し、Version 0.4.1だと、何故か上記dllのreimpに失敗してしまうため、Version 0.3を使うこと。

reimpを実行する
reimp -c -d iconv.lib

これで、.defが手に入る。

dlltoolを実行する
dlltool -v -d iconv.def -D iconv.dll -l libiconv.dll.a

これで、.dll.aが手に入る。

Haskell版iconvパッケージをインポートする

Hackageからダウンロード

http://hackage.haskell.org/package/iconv
ここから、iconv-0.4.0.2.tar.gzをダウンロード。
ここの説明に6.10でビルドできると書いてあるのに…罠じゃないのか、これは。

configure
runghc setup configure --extra-include-dirs=C:\sdk\iconv-1.10-20060516-dll --extra-lib-dirs=C:\sdk\iconv-1.10-20060516-dll

ディレクトリは適宜修正する。

build
runghc setup build

エラーが出ます。

[2 of 2] Compiling Codec.Text.IConv ( Codec\Text\IConv.hs, dist\build\Codec\Text\IConv.o )

Codec\Text\IConv.hs:152:29:
    Not in scope: data constructor `Exception.IOException'

http://d.hatena.ne.jp/sirocco/20090507/1241668523

ここのコメントによると、Exceptionの仕様変更らしい。

--- IConv.hs.old	2009-05-08 05:06:30.000000000 +0900
+++ IConv.hs	2009-05-08 05:07:06.000000000 +0900
@@ -53,8 +53,8 @@

import Prelude hiding (length, span)

-import Control.Exception (assert)
-import qualified Control.Exception as Exception
+import Control.OldException (assert)
+import qualified Control.OldException as Exception
import Foreign.C.Error as C.Error (Errno, errnoToIOError)

import qualified Data.ByteString.Lazy as L (ByteString, toChunks, fromChunks)

これで、buildできるようになる。

install
runghc setup install

一応、ここまでは成功する。

iconvのテストプログラムを書いてビルド

適当に、ソースを書いてみます。
やっぱりCで書くのに比べると断然楽だよなあ。

import Codec.Text.IConv (convert)
import qualified Data.ByteString.Lazy as L

main :: IO ()
main = L.interact $ convert "ISO-2022-JP" "UTF-8"

で、ビルド。

E:\iconvtest>ghc --make iconvtest.hs
[1 of 1] Compiling Main             ( iconvtest.hs, iconvtest.o )
Linking iconvtest.exe ...
C:\Program Files (x86)\Haskell\iconv-0.4.0.2\ghc-6.10.4/libHSiconv-0.4.0.2.a(Int
ernal.o):fake:(.text+0x2196): undefined reference to `iconv_open'
C:\Program Files (x86)\Haskell\iconv-0.4.0.2\ghc-6.10.4/libHSiconv-0.4.0.2.a(Int
ernal.o):fake:(.text+0x2348): undefined reference to `iconv_close'
C:\Program Files (x86)\Haskell\iconv-0.4.0.2\ghc-6.10.4/libHSiconv-0.4.0.2.a(Int
ernal.o):fake:(.text+0x33f3): undefined reference to `iconv'
collect2: ld returned 1 exit status

むぅ…。

iconv.dll.aへのリンクが通ってないと思われるので、-liconvなどのオプションを色々試すもダメ。
根本的に何かが違うらしい。

ソースの見直し

Internal.hsの中身を見てみると、以下のような部分を発見した。

----------------------
-- The foreign imports

newtype ConversionDescriptor = ConversionDescriptor (ForeignPtr ConversionDescriptor) -- iconv_t

foreign import ccall unsafe "iconv.h iconv_open"
  c_iconv_open :: CString  -- to code
               -> CString  -- from code
               -> IO (Ptr ConversionDescriptor)

foreign import ccall unsafe "iconv.h iconv"
  c_iconv :: Ptr ConversionDescriptor
          -> Ptr (Ptr CChar)  -- in buf
          -> Ptr CSize        -- in buf bytes left
          -> Ptr (Ptr CChar)  -- out buf
          -> Ptr CSize        -- out buf bytes left
          -> IO CSize

foreign import ccall unsafe "iconv.h &iconv_close"
  c_iconv_close :: FinalizerPtr ConversionDescriptor

関数プロトタイプでも違うんだろうか、と思ってiconv.hの中身を開いてみる。

#ifndef LIBICONV_PLUG
#define iconv_open libiconv_open
#endif
extern LIBICONV_DLL_EXPORTED iconv_t iconv_open (const char* tocode, const char* fromcode);

正直、こういう定義はあまりやって欲しくないなあ…


まあそれはともかく、恐らくGHC側でLIBICONV_PLUGを定義しているとは考えにくいし、
.defや.dllの中身を見ると、明らかに関数名はlibiconv_openなどの定義になっているので、
Internal.hsの中身をちょいちょい修正して、libiconv_openなどの名前に変えてやります。

これでconfigure/build/install…するも、やっぱりダメ。
これ自体は合ってるらしいんだけど、まだ足りない。

cabalのソース見直し

(意味を調べるのが面倒だったので)cabalの中身は見てなかったのだけれど、問題になりそうなところ発見。

  if os(darwin) || os(freebsd)
    -- on many systems the iconv api is part of the standard C library
    -- but on some others we have to link to an external libiconv:
    extra-libraries: iconv

どうやら、ここでiconv.dll.aをリンクするかどうか決定しているらしいです。
(どう見てもソース的にアレですが)どうせwindowsでしか使わないので以下のようにコードを追加してみる。

  if os(darwin) || os(freebsd)
    -- on many systems the iconv api is part of the standard C library
    -- but on some others we have to link to an external libiconv:
    extra-libraries: iconv
  else
    extra-libraries: iconv.dll

これでconfigure/build/install。ビルドしたところ、正常に動作しました。