初探mRuby,C程序嵌入以及自定义扩展

博客荒废好长一段时间了,总感觉再不写点什么我说不定都提不起劲给VPS续费了……

mRuby已经release了1.0.0版本,虽然无论是文档还是社区建设mRuby都还有很长的一段路要走,但是对我来说尝鲜已经是足够了。

既然你会翻到这里估计我也就不用再费口舌介绍什么是mRuby了,那么下面我们就直接进入正题好了。

编译

Linux

之前在linux上编译过一次,不过说起来linux上编译起来真的有难度么?连configure都不需要直接装上bison、automake和ruby然后在代码根目录直接make就好。

Windows

曾经试过在Windows上编译一个Ruby出来,不过因为种种原因作罢了,这次编译mRuby也算是补回这个遗憾。

  1. 去GNUWIN32下载bison、make以及mingw32
  2. 安装一个版本的ruby
  3. 在环境变量中添加bison、make、gcc(mingw32提供)所在的路径,具体方法自己google
  4. 在mRuby源码的根目录下make
  5. Done

编译好了以后在build/host/bin/下提供了mruby、mirb和mrbc,分别对应ruby、irb以及rbc

mruby和mirb不用多说,mrbc好像是编译rb程序用的,我没用过就不多说了。

嵌入

正如mRuby名字所揭示的那样,她是一门专门用于嵌入(Embedded)的轻量级语言,类似于Lua。既然如此我们就首先来测试一下将Ruby嵌入到C程序中去。

#include<stdio.h>
#include<stdlib.h>
#include<mruby.h>
#include<mruby/compile.h>

int main(){
char ruby_code[] = "puts ‘Hello World’";
mrb_state *mrb = mrb_open();
mrb_load_string(mrb,ruby_code);
return 0;
}

嗯……好像还真是比Ruby简单不少……顺带一提之前我曾经试着在windows下将Ruby嵌入到C程序中去,可是使用RubyInstaller提供的名字叫1.9.1的lib总是会在运行的时候报段错误……(Ruby版本1.9.3),当然在Linux下没有任何问题就是了。

顺带一提,请自行设置include文件的搜索位置,以及需要链接的lib(libmruby.a,位于build\host\lib下),在此仅提供Code::Blocks自动生成的编译命令

mingw32-gcc.exe -Wall  -g    -IF:\mruby\mruby-1.0.0\include -c F:\mruby\main.c -o obj\Debug\main.o
mingw32-g++.exe -LF:\mruby\mruby-1.0.0\build\host\lib -o bin\Debug\mruby.exe obj\Debug\main.o   -lmingw32 F:\mruby\mruby-1.0.0\build\host\lib\libmruby.a 

好像同时指定了lib的搜索位置以及lib文件的绝对路径,嘛,不要在意

扩充

在C程序里单独嵌入一个Ruby命令没什么意思,我们来为其扩充一些我们自己定义的内容。

#include<stdio.h>
#include<stdlib.h>
#include<mruby.h>
#include<mruby/compile.h>
mrb_value foo_add(mrb_state\* mrb,mrb_value self){
  int a,b;
  mrb_get_args(mrb,"ii",&a,&b);
  return mrb_fixnum_value(a+b);
}
int main(){
  char ruby_code[] = "p Foo.new.add(1,2)";
  mrb_state \*mrb = mrb_open();
  struct RClass\* foo_class;
  foo_class = mrb_define_class(mrb,"Foo",mrb->object_class);
  mrb_define_method(mrb,foo_class,"add",foo_add,MRB_ARGS_REQ(2));
  mrb_load_string(mrb,ruby_code);
  return 0;
}

这样,我们就添加了一个名为Foo的类以及其一个实例方法add

参考代码,我们可以了解一下mrb定义方法的一般方式,foo_add是作为方法的具体实现函数,其返回类型以及参数类型都是固定的。而ruby传给该方法的参数则需要通过mrb_get_args(mrb,args...)获取。其中args表示各个参数的类型。具体参考此表:

string  mruby type     C type                 note

o:      Object         [mrb_value]
C:      class/module   [mrb_value]
S:      String         [mrb_value]
A:      Array          [mrb_value]
H:      Hash           [mrb_value]
s:      String         [char\*,int]            Receive two arguments.
z:      String         [char\*]                NUL terminated string.
a:      Array          [mrb_value\*,mrb_int]   Receive two arguments.
f:      Float          [mrb_float]
i:      Integer        [mrb_int]
b:      Boolean        [mrb_bool]
n:      Symbol         [mrb_sym]
&amp;:      Block          [mrb_value]
\*:      rest argument  [mrb_value\*,int]       Receive the rest of the arguments as an array.
|:      optional                              Next argument of '|' and later are optional.

\*来源于src/class.c内部的注释

需要指出的是,所有的ruby变量都是mrb_value类型, foo_add 函数的self参数指代instance自己。而对于mrb_define_class_method定义的类方法等self指代的是该类(Class类的instance)本身。

至于mrb_define_class以及mrb_define_method及其参数意义就不用我说了吧?稍微懂点英语就可以看懂了。

还有ruby中每个方法必须要有一个返回值,所以即使你没有返回值也要return mrb_nil_value();,而且函数必须返回mrb_value。(我一开始就直接想都没想用了void作为返回类型……)

很明显,这种方法只适合少量的零散的定义,而对于大量的,更加结构化的工程这种方法也有其局限性,而我们又常常会想要在Ruby类中储存一些持久化数据,但这又会牵扯到Ruby的垃圾回收(GC)工作机理,这一切我们会在后面讲解。

下一讲:

《mRuby:mrbgems以及在Ruby对象中储存struct》