6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?