LoginSignup
6
2

More than 5 years have passed since last update.

Haskellを使ってRubyのクラスメソッドを書く(その1) FFI

Last updated at Posted at 2018-04-17

(2018.5.13)
(その4)まとめを公開しました。ベンチマーク結果もありますのでご参考に。

はじめに

以前C++を使ってRubyのクラスメソッドを書くを投稿しましたが、大きな配列を扱うことが多いので、C++でデバッグするのはしんどいところがあります。
比較的デバッグがやり易く、速度も出やすいHaskellで書いた整数、浮動小数点、文字列、配列を使う関数をRubyから呼び出す方法を紹介いたします。

GHCのインストールなどでstackを利用している方が多いと思いので、それを使います。
buildなどはPythonからHaskell関数を呼ぶを参考にさせていただきました。

以下
$ stack new testFFI
で作成したプロジェクトを前提としてます。

ファイルの変更

app/Main.hsとsrc/Lib.hsにプログラムを書き込みます。

Main.hs
module Main where

import Foreign.C.Types
import Foreign.C.String
import Foreign.Marshal.Array (peekArray, newArray)
import Foreign.Marshal.Alloc (free)
import GHC.Ptr
import Lib

main = undefined

convertStringBy :: (String -> String) -> CString -> IO CString
convertStringBy func cstring = do
    str <- peekCString cstring
    newCString $ func str

convertArrayBy func n ptr = do
    lst <- peekArray (fromIntegral n) ptr
    newArray $ func lst

foreign export ccall "twicefold_hi" twicefold :: CLong -> CLong
foreign export ccall "twicefold_hd" twicefold :: CDouble -> CDouble

upcase_hs str = convertStringBy upcase str
foreign export ccall upcase_hs :: CString -> IO CString

twicefold_had n darray_ptr = convertArrayBy twicefold_list n darray_ptr
foreign export ccall twicefold_had :: CLong -> Ptr CDouble -> IO (Ptr CDouble)

foreign export ccall "free_ptr" free :: Ptr a -> IO ()
Lib.hs
module Lib
    (twicefold, twicefold_list, upcase) where

import Data.List
import Data.Char (toUpper)

twicefold n = n*2
twicefold_list lst = map (*2) lst
upcase str = map toUpper str

foreign export〜
はどのファイルに書いても問題ないようですが、ghciでエラーが出てデバッグがやりにくいので、分けた方が良いように思います。

package.yamlを一部変更してください。

package.yaml
(省略)
dependencies:
- base >= 4.7 && < 5

ghc-options:
- -shared
- -dynamic

library:
  source-dirs: src

executables:
  testFFI.so:
    main:                Main.hs
    source-dirs:         app
    extra-libraries:
    - HSrts-ghc8.2.2
    ghc-options:
    - -shared
    dependencies:
    - testFFI
(省略)

HSrts-ghc8.2.2はGHCのバージョンに合わせてください。

$ stack build
でtestFFI.soが作成されます。
最後の方に次のようなメッセージのが出ますので、パスをffi_libのあとに書き込んでください。
Installing executable testFFI.so in /path..

テスト用プログラム

test.rb
require "ffi"

module Test
    extend FFI::Library
    ffi_lib "./.stack-work/install/x86_64-osx/lts-11.5/8.2.2/bin/testFFI.so"
    attach_function :c_hs_init, :hs_init, [:pointer, :pointer], :void
    attach_function :hs_exit, [], :void
    attach_function :hs_free, :free_ptr, [:pointer], :void

    attach_function :twicefold_hi, [:int64], :int64
    attach_function :twicefold_hd, [:double], :double
    attach_function :twicefold_had, [:int64, :pointer], :pointer
    attach_function :upcase_hs, [:string], :pointer

    def self.hs_init() c_hs_init(nil, nil) end

    def self.upcase(str)
        ptr=Test.upcase_hs str
        read_str=ptr.read_string
        self.hs_free(ptr)
        read_str
    end

    def self.twicefold_array(array)
        arg_ptr=FFI::MemoryPointer.new(:double, array.size)
        arg_ptr.write_array_of_double(array)
        ret_ptr=Test.twicefold_had(array.size, arg_ptr)
        ret_array=ret_ptr.read_array_of_double(array.size)
        self.hs_free(ret_ptr)
        ret_array
    end

end
Test.hs_init
puts Test.twicefold_hi(100001)
puts Test.twicefold_hd(10000.0)
puts Test.upcase "abcdEF"
p Test.twicefold_array([*10..20])
Test.hs_exit

(2018.4.17 test.rbの使っていないメソッドを削除)

それとhs_initの実行を忘れないように

ffiがSegmentation faultで落ちる時の対処法

私の環境では最初、attach_function命令のところでSegmentation faultが発生して動かない事態になっていました。調べてみるとlibffiが2つ存在し、どうも読んでるffi.hとリンクしているlibffiの実体が違うようでした。
ffiをgemでインストールするときに、--with-ffi_c-includeや--with-ffi_c-libで指定することで解決できました。

sudo gem install ffi -- --with-ffi_c-include=xxxx
(xxxxはffi.hのあるパス名)

余談ですが、--with-yyy-includeのyyyに何を書くかは、次のようにして探せば良いようです。
ffiのextconf.rbファイルの中にdir_config("ffi_c")があり、その少し下にhave_library("ffi",...
があります。この場合は
--with-ffi_c-include=xxxx
とします。

OS MacOS 10.12.6 or Scientific Linux 7.3
GHC 8.2.2
Ruby 2.5.1p57
stack 1.6.5

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2