最近版工在把玩 Golang 這個新玩具
後來發現到可以用 Golang 寫 Ruby gem,就上來分享一下
這個功能可以實現,主要有兩點:
1. Go 在 1.5 版後可以輸出給 C/C++ 使用的 shared library
2. Ruby 可透過 FFI 模組讀取 shared library
其他的就是一些實作時的細節,請見下文。
首先,我們寫一個簡單的 Go 程式:
package main
import "C"
//export DoubleIt
func DoubleIt(x int) int {
return x * 2
}
func main() {}
然後,將其編譯成 shared library:
$ go build -o libdoubler.so -buildmode=c-shared
會得到 libdoubler.h 和 libdoubler.so 這兩個檔案,可以後續給 C/C++ 使用
如果要給 Ruby 使用,需要用 FFI 這個模組讀取 shared library,以下是範例:
require 'ffi'
module Lib
extend FFI::Library
ffi_lib 'c'
ffi_lib './libdoubler.so'
attach_function :DoubleIt, [:int], :int
end
puts Lib.DoubleIt(2)
接下來,我們要把這個過程寫成 gem。
我們這裡僅描述重點,其他的細節請自行參考這裡:
https://github.com/cwchentw/doubler
傳統的 Ruby extension gem,使用 C/C++ 或 Java,較少使用 Golang
所以,我們要自行撰寫 extconf.rb,以便 gem 自動編譯 shared library
以下是例子:
require 'mkmf' # For find_executable method
# You should check 1) Go environment exists and 2) Go version >= 1.5
# Check whether go command exists here
if find_executable('go').nil? then
abort <<NO_GO
No Go environment installed
NO_GO
end
# Implement code to check Go version
# We didn't check the version of Go here.
# In a formal gem, you should check that.
# Finally, call related go command to build shared library
system "go build -o libdoubler.so -buildmode=c-shared main.go"
本來,mkmf 模組為會我們製作 Makefile
然而,這個功能是給 C/C++ 用的,我們用不到
在這裡,我們做一個沒功能的 Makefile,以騙過 gem
all:
true
install:
true
最後,撰寫相關的 gemspec 即可
Gem::Specification.new do |s|
s.name = "doubler"
s.version = "0.1.0"
s.licenses = ["MIT"]
s.summary = "A Demo Ruby Gem with Golang"
s.author = "Michael Chen"
s.email = "[email protected]"
s.extensions = %w[ext/doubler/extconf.rb]
files = Dir.glob("ext/**/*") + Dir.glob("lib/**/*.rb")
s.files = files
s.homepage = "https://github.com/cwchentw/doubler"
end
經實測,Golang 輸出的函數,可以處理基本的數據,像是 int, float, string 等
但是,對於 array, map, struct 等進階的資料類型則無法有效傳遞
以目前來說,大概就是把少數複雜的運算步驟,轉給 Go 程式去運算
如果要在 C/C++ 層級處理複雜的資料類型和運算
一個就是回歸 Ruby C API,但是這樣就享受不到寫 Golang 的便利
一個就是將資料轉為 JSON 字串,在 Golang 和 Ruby 間傳遞
但是,這樣效率可能不好,有興趣的朋友可自行嘗試
分享給有需要的 Rubyist