用 mruby 执行 Ruby 代码

原作者 Daniel Bovensiepen 译者 Lanza Schneider

通常,我们使用Ruby程序的方式都是直接将源代码分发,并且安装一个有着众多文件的Ruby解释器以执行那些Ruby源代码。因此,如果您有使用其它Ruby实现的经验,那么您可能会对下面列出的一些例子感到熟悉。但mruby不仅能以这种方式运行,还可以构建出独立的程序,甚至可以把Ruby源代码编译成字节码来储存,并在合适的时机加载执行。

我们假设现在有一个Ruby源代码文件 test_program.rb 它的内容是:

puts 'hello world'

REPL(交互式解释器) (mirb)

它并不直接加载源代码文件来执行Ruby程序,不过你可以在mirb程序内直接输入Ruby代码来执行:

$ mruby/bin/mirb
mirb - Embeddable Interactive Ruby Shell

> puts 'hello world'
hello world
 => nil

优缺点

✔ 直接输入源代码执行并即时获取反馈,无需任何间接文件

✘ 不适用于生产场合

✘ 输入的Ruby代码将要解析2次:首先mirb检查代码的完整性,然后mirb 把代码编译为字节码并执行它。

Ruby源代码(.rb)

运行Ruby代码最常见的方式就是把源代码文件名作为参数传递给解释器,在mruby中,这个解释器就叫 mruby

$ mruby/bin/mruby test_program.rb
hello world

优缺点

✔ 非常简单的开发周期:编程 → 测试 → 编程

✘ Ruby源代码会直接向用户暴露

✘ 需要mruby解释器程序,而且需要文件系统的支持(译者注:譬如在某些单片机上就没有支持文件的环境了)

✘ Ruby源代码仍需解析后编译成字节码才能去运行

C源代码(.c)

你可以把Ruby源代码写成一个C字符串。这就好像使用mruby解释器程序时,使用了-e 参数一样。

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

int
main(void)
{
  mrb_state *mrb = mrb_open();
  if (!mrb) { /* handle error */ }
  // mrb_load_string(mrb, str) to load from NULL terminated strings
  // mrb_load_nstring(mrb, str, len) for strings without null terminator or with known length
  mrb_load_string(mrb, "puts 'hello world'");
  mrb_close(mrb);
  return 0;
}

编译链接:

$ gcc -std=c99 -Imruby/include test_program.c -o test_program mruby/build/host/lib/libmruby.a -lm

执行:

$ ./test_program
hello world

优缺点

✔ 简单的开发周期 编程 → 编译 (用譬如gcc的C语言编译器) → 测试 → 编程

✔ 产出的程序将完全是独立的

✘ 需要照抄上面的样板才能去编程和执行(译者注:这里可能指的是需要写很多和业务逻辑关系不大,用于控制mruby的C代码)

✘ Ruby源代码仍需解析后编译成字节码才能去运行

✘ 如果要更新Ruby代码需要重新编译,或者是其它的更新机制

字节码(.mrb)

mruby提供了字节码机制。Ruby代码可以通过预编译的方式产出中间代码(字节码),随后再加载执行,就像Java那样。

首先我们要通过mrbc程序来把Ruby源代码编译成mruby字节码:

$ mruby/bin/mrbc test_program.rb

这样就可以得到一个叫test_program.mrb的文件,其中包含了我们所给Ruby源代码编译后的mruby字节码内容:

$ hexdump -C test_program.mrb
00000000  52 49 54 45 30 30 30 33  e1 c0 00 00 00 65 4d 41  |RITE0003.....eMA|
00000010  54 5a 30 30 30 30 49 52  45 50 00 00 00 47 30 30  |TZ0000IREP...G00|
00000020  30 30 00 00 00 3f 00 01  00 04 00 00 00 00 00 04  |00...?..........|
00000030  00 80 00 06 01 00 00 3d  00 80 00 a0 00 00 00 4a  |.......=.......J|
00000040  00 00 00 01 00 00 0b 68  65 6c 6c 6f 20 77 6f 72  |.......hello wor|
00000050  6c 64 00 00 00 01 00 04  70 75 74 73 00 45 4e 44  |ld......puts.END|
00000060  00 00 00 00 08                                    |.....|
00000065

这个文件可以由mruby解释器程序或者mrb_load_irep_file() 函数来执行。使用mruby解释器程序运行字节码时,需要用-b参数告诉它这是一个字节码文件而不是普通的Ruby源代码:

$ mruby/bin/mruby -b test_program.mrb
hello world

优缺点

✔ 不必给用户分发Ruby源代码

✔ 无需解析Ruby源代码

✔ 可以通过替换.mrb文件的方式轻松更新字节码

✘ 复杂的开发周期: 编程 → 编译 (mrbc) → 测试 (mruby) → 编程

✘ 需要mruby解释器程序,而且需要文件系统的支持

字节码(C语言方式)(.c)

如果你想把Ruby代码直接集成在C程序中,这种方式就很适合了。它将会创建一个包含mruby字节码的C数组,然后您就可以在C程序中执行该字节码。

首先用mrbc 编译程序,提供-B参数并紧跟一个C标识符即可:

$ mruby/bin/mrbc -Btest_symbol test_program.rb

我们就可以得到一个test_program.c文件,它包含名为test_symbol的C数组

/* dumped in little endian order.
   use `mrbc -E` option for big endian CPU. */
#include <stdint.h>
const uint8_t
#if defined __GNUC__
__attribute__((aligned(4)))
#elif defined _MSC_VER
__declspec(align(4))
#endif
test_symbol[] = {
0x45,0x54,0x49,0x52,0x30,0x30,0x30,0x33,0x73,0x0d,0x00,0x00,0x00,0x65,0x4d,0x41,
0x54,0x5a,0x30,0x30,0x30,0x30,0x49,0x52,0x45,0x50,0x00,0x00,0x00,0x47,0x30,0x30,
0x30,0x30,0x00,0x00,0x00,0x3f,0x00,0x01,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x04,
0x06,0x00,0x80,0x00,0x3d,0x00,0x00,0x01,0xa0,0x00,0x80,0x00,0x4a,0x00,0x00,0x00,
0x00,0x00,0x00,0x01,0x00,0x00,0x0b,0x68,0x65,0x6c,0x6c,0x6f,0x20,0x77,0x6f,0x72,
0x6c,0x64,0x00,0x00,0x00,0x01,0x00,0x04,0x70,0x75,0x74,0x73,0x00,0x45,0x4e,0x44,
0x00,0x00,0x00,0x00,0x08,
};

然后你可以用下面的模板示例来执行该字节码(我们将此C程序命名为test_stub.c):

#include <mruby.h>
#include <mruby/irep.h>
#include <test_program.c>

int
main(void)
{
  mrb_state *mrb = mrb_open();
  if (!mrb) { /* handle error */ }
  mrb_load_irep(mrb, test_symbol);
  mrb_close(mrb);
  return 0;
}

这样就可以执行C数组中的字节码了

编译链接:

$ gcc -std=c99 -Imruby/include test_stub.c -o test_program mruby/build/host/lib/libmruby.a -lm

执行:

$ ./test_program
hello world

优缺点

✔ 不必给用户分发Ruby源代码

✔ 无需解析Ruby源代码

✔ 产出的程序将完全是独立的

✘ 更复杂的开发周期:编程 → 编译 (mrbc) → 集成C代码 → 编译 (gcc等C语言编译器) → 测试 → 编程

✘ 需要照抄上面的样板才能去编程和执行

✘ 如果要更新mruby字节码需要重新编译,或者是其它的更新机制

总结

REPL(交互式解释器) (mirb) 的方式主要用于早期开发过程。 Ruby 源代码 (.rb) 是 mruby 的最常用用法,它将 Ruby 作为一种脚本来使用,可以轻松地修改程序内容。 而 C 源代码 (.c) 是将 mruby 嵌入到您自己应用程序中最简单的做法。 字节码 (.mrb) 用法则类似 Java 程序,可以基于文件来安装程序,但是不分发源代码。 字节码 (C 语言方式) (.c) 对很多人来说可能是最复杂的一种使用 mruby 的方式,但是它也提供了在程序内嵌入 mruby 最有效的办法。(译者注:mruby 自带的 mrbgems 机制就是使用该方式嵌入 Ruby 代码的)