[文件] SWIG/Ruby
http://www.swig.org/
SWIG is an interface compiler that connects programs written in C and C++
with scripting languages such as Perl, Python, Ruby, and Tcl.
也就是,讓 C/C++ 與 scripting language 溝通的介面產生器
目前支援 13 種 scripting language:
* Allegro Common Lisp
* C#
* Chicken
* Guile
* Java
* Modula-3
* Mzscheme
* OCAML
* Perl
* PHP
* Python
* Ruby
* Tcl
之前我測了一些 Ruby 與 C++ 的溝通方式,以下將簡單介紹一下測試的方法
首先先看到使用方式,我想這個應該是最重要的部份
畢竟如果使用不便,那其他的也不用再多說什麼了…
(以下由於 BBS 之故,稍作排版修改)
以下這個程式是一個簡單的 type wrapper,(傳值版)
由 template parameter 指定型別,
data() 當 getter, data(type) 當 setter.
另外在 c'tor 和 d'tor 中插入 IO 來追蹤物件的生成與摧毀
// in Wrapper.h
#include <iostream>
template <class Data>
class Wrapper{
public:
Wrapper(Data new_data): data_(new_data){
std::cout << "C++: Wrapper is created, which is '"
<< data_ << "' .\n";
}
~Wrapper(){
std::cout << "C++: Wrapper is decayed, which is '"
<< data_ << "' .\n";
}
Data data() const{ return data_; }
void data(Data new_data){ data_ = new_data; }
private:
Data data_;
};
有了這個 .h 檔的介面後(這裡同時也包含實作),
接下來我們需要的是一個由 swig 產生的膠水,將 C++ 介面與 Ruby 介面混合
暫時命名為:Wrapper.i, 詳細做法等一下我們再來看
總之最終我們將產生一個動態連結檔,也就是一個 Ruby 的 module, 叫 Wrapper
不過需要一提的是,由於 Ruby 本身不支援 template,
所以我們在 Wrapper.i 中必須明確指出我們需要什麼型別,
否則 Ruby 會無法使用(更明確來講,純 template 根本無法 compile)
所以在 Wrapper.i 中,裡面有一行是這個:
%template(Integer) Wrapper<int>;
這句話的意思是,將 Wrapper<int> 這個型別取作 Integer
於是 Ruby module 做好後,可以使用 Integer 這個型別
雖然我中間還有很多測試,不過直接看目前的最後結果吧
寫一個 Ruby 程式,內容如下:
#!ruby
# in test.rb
require 'Wrapper' # 讀入剛剛做好的 Wrapper module
class Test < Wrapper::Integer # 繼承 C++ 寫好的 Wrapper<int>
def initialize(new_data) # c'tor 其實可以省略,因為可以使用
super(new_data) # C++ 中寫好的 Wrapper<int>
end # 這裡只是展示 super 是可以用的
def plus(num)
data(data + num)
data
end
end
# 正式測試開始
fat = Test.new(123) # 建構出 Test 物件,傳入 123 當引數
puts fat.plus(7) # output: 130
fat.data(456) # 設值為 456
puts fat.plus(4) # output: 460
ok, 可是原本插入 C++ 的 c'tor 中的 IO 呢?
整個程式的輸出結果是這樣:
C++: Wrapper is created, which is '123' .
130
460
C++: Wrapper is decayed, which is '460' .
也就是說,C++ 的 c'tor 與 d'tor 有被確實喚起
這樣說不定可以替 Ruby 實作出 d'tor...不過這是題外話
另外,剛剛程式中的 fat.data 與 fat.data(456) 確實是
喚起正確的 getter 與 setter, 可是 Ruby 沒有 overload?
我沒有實際去看,但我猜他內部大概是這樣實作的:
def data(*args)
case args.size
when 0 then return self.data_a
when 1 then return self.data_b(args[0])
end
end
所以如果你想要重新定義其中一個 overloaded method, 做不到…
可能必須找到他實際的名字才有辦法
那麼我們再來看到由 C++ 呼喚 Ruby... 否則不就太寂寞了嗎?
基本上 Ruby 本身就提供了良好與 C 溝通的機制,照用不就好了?
是這樣說沒錯,可是說真的我很討厭 C 的介面…囧rz
常常會要你記下一堆有的沒的,感覺很討厭
所以我覺得需要一個 C++ 的 wrapper, 一個可以輕鬆呼叫 Ruby 的方式
像上面推文中所提到的,我找到這個:
http://www.sourcepole.com/sources/software/c++ruby/
不過這個東西實在是寫得不好,沒有處理 finalize 的部份
不知道還有沒有其他類似的 wrapper 可以用,所以我就暫時用這個來改
改寫他 singleton 的實作,還有記得在 d'tor 中呼叫 ruby_finalize();
最後的結果是,我可以這樣寫:
#include "rubyeval.h" // 就是上面抓來的那個
#include <ruby.h> // embedded ruby 必要的東西,去 ruby-lang 就可以抓
int main(){
RubyEval& ruby = RubyEval::instance();
ruby.eval("require 'Wrapper'"); // 直接用字串執行 Ruby
// 這邊是含入剛剛做好的 C++ => Ruby mod
// 當然這東西要先 compile 好
ruby.eval("fat = Wrapper::Integer.new(123)"); // 直接建立物件
std::cout << NUM2INT(ruby.eval("fat.data")) << std::endl; // 輸出 123
ruby.eval("fat.data(456);"); // 設值 456
std::cout << NUM2INT(ruby.eval("fat.data")) << std::endl; // 輸出 456
ruby.run_file("test.rb"); // 直接執行 ruby 程式
}
那個 test.rb, 就是上面寫好拿來測試 Ruby 呼叫 C++ 的程式
於是整個輸出結果就是:
C++: Wrapper is created, which is '123' .
123
456
C++: Wrapper is created, which is '123' .
130
460
C++: Wrapper is decayed, which is '460' .
C++: Wrapper is decayed, which is '456' .
第一行是從 C++ 建構的 Integer;
123, 456 則是 cout 輸出的
第四行的 123 則是 test.rb 產生 class Test < Wrapper::Integer 那個
130, 460 則是 Ruby 的 puts 產生出來的
最後兩個 decayed 則是 Ruby 的 gc 正確在程式結束時摧毀物件輸出的
順序剛好跟 c'tor 反過來,一切正常
最後就來看怎麼實現這個的
Wrapper.i 是這樣寫的:
%module Wrapper
%{
#include "Wrapper.h"
%}
%include "Wrapper.h"
%template(Integer) Wrapper<int>;
這些語法請參考 SWIG 的網站,那邊都有詳細說明
甚至是 C++ 的多重繼承,在 Ruby 中也能使用
當然多少可能會有點限制,但似乎可以實現一定的功能
下指令:
swig -c++ -ruby Wrapper.i
這樣就可以以 Wrapper.i 這個介面檔實作出由 Ruby 溝通 C++ 的程式
那個程式會叫做 Wrapper_wrap.cxx, 也就是 YOUR_NAME_wrap.cxx
再來就是將所有的程式打包成一個動態連結檔了,所有的檔案是:
Wrapper.h (包含實作), Wrapper_wrap.cxx (SWIG 的膠水)
方便的做法是使用 Ruby 的 mod, 叫 mkmf
寫一個 Ruby 程式叫 mkmf.rb, 內容是:
require 'mkmf'
$libs = append_library($libs, "stdc++")
create_makefile(ARGV[0])
這邊是由於我個人方便,所以這樣寫的
第一行含入 mkmf, 第二行是因為我用到 C++ 標準函數庫(cout),
所以必須連結 stdc++ 才行(我的系統是 gcc, VC++ 的話我不清楚)
接著由 create_makefile 產生出我要的 makefile, 名稱由 cmd line 輸入
這邊我是輸入 Wrapper
(btw, 有人知道 stdc++ 可否動態連結嗎?)
接著就可以由 make 將 Wrapper.h 和 Wrapper_wrap.cxx 合併做出 Wrapper.so
(我想 VC++ 系統應該會做出 Wrapper.dll 之類的)
最後要提的是,整個程式的執行環境
由於我是在 cygwin 下作業的,所以獨立執行這些程式需要的是:
cygwin1.dll 1.78 MB
cygruby18.dll 703 KB
cygcrypt-0.dll 6.5 KB
我想如果在 windows 下由 VC++ compile 的話,
應該就只會需要 Ruby interpreter 的 dll 檔(這裡是 cygruby18.dll)
不過我就沒有做這一步的測試了,留給讀者當作練習吧 XD
(忽然心血來潮所以整理了這些東西)
--
Nobody can take anything away from him.
Nor can anyone give anything to him.
What came from the sea,
has returned to the sea.
Chrono Cross
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 220.135.28.18
推
08/06 18:53, , 1F
08/06 18:53, 1F
→
08/06 19:02, , 2F
08/06 19:02, 2F
→
08/06 22:43, , 3F
08/06 22:43, 3F
推
08/07 22:22, , 4F
08/07 22:22, 4F
→
08/07 23:21, , 5F
08/07 23:21, 5F
※ 編輯: godfat 來自: 220.135.28.18 (08/07 23:25)
推
08/08 13:30, , 6F
08/08 13:30, 6F
推
08/08 14:14, , 7F
08/08 14:14, 7F
→
08/08 14:14, , 8F
08/08 14:14, 8F
→
08/08 14:16, , 9F
08/08 14:16, 9F
討論串 (同標題文章)
Ruby 近期熱門文章
PTT數位生活區 即時熱門文章