作者godfat (godfat 真常)
看板Ruby
标题[分享] SWIG/Ruby
时间Sun Aug 6 17:37:56 2006
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
1F:推 dotZu:13种语言怎麽有Java但没Java Script 08/06 18:53
2F:→ godfat:Java 和 JavaScript 没有因果关系 08/06 19:02
3F:→ SBT:Java 和 JavaScript 完全不一样 08/06 22:43
4F:推 PsMonkey:ㄟㄟ... 把那些 html 还有论坛的 tag 去掉好呗... 08/07 22:22
5F:→ godfat:其实那是我在这里打时打上去的 XD 另一种颜文字吧 @_@ 08/07 23:21
※ 编辑: godfat 来自: 220.135.28.18 (08/07 23:25)
6F:推 dotZu:二、三楼:正是因为不一样才奇怪啊 Java 不是 Scripting 08/08 13:30
7F:推 cplusplus:反正只是想要利用C\C++的效率吧? 那JAVA也可以用啊~ 08/08 14:14
8F:→ cplusplus:又话说JAVASCRIPT一般在BROWSER上执行 比较少会去用这个 08/08 14:14
9F:→ cplusplus:编好的MODULE吧...? 08/08 14:16