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

Author:zumikua Updated at:2015-01-30 16:25:47 UTC

mRuby拥有自己的包管理系统,mrbgems。与gem不同的是,mrbgems静态的,是在编译mruby时发挥作用的,而rubygems则是在ruby编译好以后可以进行动态的扩展。

mrbgems

首先我们来看一下官方给出的样例mrbgem

#include <mruby.h>
#include <stdio.h>

static mrb\_value
mrb\_c\_method(mrb\_state \*mrb, mrb\_value self)
{
  puts("A C Extension");
  return self;
}

void
mrb\_c\_extension\_example\_gem\_init(mrb\_state\* mrb) {
  struct RClass \*class\_cextension = mrb\_define\_module(mrb, "CExtension");
  mrb\_define\_class\_method(mrb, class\_cextension, "c\_method", mrb\_c\_method, MRB\_ARGS\_NONE());
}

void
mrb\_c\_extension\_example\_gem\_final(mrb\_state\* mrb) {
  // finalizer
}

来源于examples\mrbgems\cextensionexample\src\example.c

mrbgem要求你定义mrb\_YOURGEMNAME\_gem\_init(mrb\_state\* mrb)函数以及mrb\_YOURGEMNAME\_gem\_final(mrb\_state\* mrb)函数,分别会在加载与结束的时候被调用。当然即使没有什么内容一个空函数也是不能少的。其中函数的内容就和我们在上一节中所使用的差不多。

下面,我们来看看mrbgem.rake

MRuby::Gem::Specification.new('c\_extension\_example') do |spec|
  spec.license = 'MIT'
  spec.author  = 'mruby developers'
end

其中cextensionexample是你的gem的名字。license是许可证,author是作者,感觉说的好像有点多……具体自己参考这里吧,其对gem的路径格式也有定义。

创建完以后,在mruby的根目录下的build\_config.rb中加一行conf.gem 'mrbgems/mruby-c-extension'当然要记得指向你自己的gem地址,我就不多说了。

最后make一下,编译过程中没错误的话就可以用了。在将其嵌入到程序之前,你可以使用mirb先测试一下。

使用C struct

有时候我们可能会需要使用Ruby变量来储存一些数据,比如C的struct。在一个Ruby对象中储存struct有两种方法,一种是用对象本身包装成一个C Struct。我个人认为这个方法更加麻烦一点,而且可扩展性也不算太高……不能让一个对象拥有两个struct之类……另一种方法是为该对象定义一个实例变量(instance variable),用这个实例变量包装struct。

用实例本身包装

#include "mruby.h"
#include "mruby/compile.h"
#include "mruby/data.h"
#include "mruby/class.h"
#include "mruby/variable.h"
#include <stdlib.h>
#include <stdio.h>
struct Foo {
  int a;
};
static void foo\_free(mrb\_state\* mrb,void\* p){
  free(p);
  printf("Freed\n");
}
static struct mrb\_data\_type foo\_data\_type = {
  "foo", foo\_free
};
mrb\_value new\_foo(mrb\_state\* mrb,mrb\_value self){
  struct Foo\* f;
  f = (struct Foo\*)DATA\_PTR(self);
  if(f){
    mrb\_free(mrb,f);
  }
  DATA\_TYPE(self) = &foo\_data\_type;
  DATA\_PTR(self) = NULL;
  f  = (struct Foo\*)mrb\_malloc(mrb,sizeof(struct Foo));
  f->a = 0;
  DATA\_PTR(self) = f;
  return self;
}
mrb\_value set\_foo\_a(mrb\_state\* mrb,mrb\_value self){
  int v=0;
  mrb\_get\_args(mrb,"i",&v);
  struct Foo\* f;
  f = DATA\_GET\_PTR(mrb,self,&foo\_data\_type,struct Foo);
  f->a = v;
  return mrb\_nil\_value();
}
mrb\_value get\_foo\_a(mrb\_state\* mrb,mrb\_value self){
  struct Foo\* f;
  f = DATA\_GET\_PTR(mrb,self,&foo\_data\_type,struct Foo);
  return mrb\_fixnum\_value(f->a);
}

void mrb\_mruby\_c\_extension\_gem\_init(mrb\_state\* mrb) {
  struct RClass\* module\_test = mrb\_define\_module(mrb,"Test");
  struct RClass\* class\_foo = mrb\_define\_class\_under(mrb,module\_test,"Foo",mrb->object\_class);
  MRB\_SET\_INSTANCE\_TT(class\_foo, MRB\_TT\_DATA);
  mrb\_define\_method(mrb,class\_foo,"initialize",new\_foo,MRB\_ARGS\_NONE());
  mrb\_define\_method(mrb,class\_foo,"a",get\_foo\_a,MRB\_ARGS\_NONE());
  mrb\_define\_method(mrb,class\_foo,"a=",set\_foo\_a,MRB\_ARGS\_REQ(1));
}
void mrb\_mruby\_c\_extension\_gem\_final(mrb\_state\* mrb) {
  /\*Do nothing\*/
}

这部分的代码主要参考mruby-time这个gem的代码。首先来看一下new\_foo

首先应该指出的一点就是这里的initialize是一个实例方法,所以说传给new\_fooselfFoo.new生成的是实例自身而不是Foo这个Class类的实例常量

Ruby中内建的类型有一种DATA原型,该原型有一个DATA指针用来储存数据内容,所以说我们可以通过使用该DATA指针来储存struct。不过在此之前,先通过MRB\_SET\_INSTANCE\_TT(class\_foo, MRB\_TT\_DATA)将Foo类的所有实例设为DATA原型。

然后我们就可以通过DATA\_PTR(self)获取实例的DATA指针。注意:DATA\_PTR(self)宏实际上等于((struct RData \*)mrb\_ptr(self))->data,并不是一个字面值常量,所以对其进行赋值是可行的。

在取得DATA指针以后要对其进行释放,然后再将其指向自己创建的新内存中,这部分的代码全部都在new\_foo函数中。关于mrb\_free,目前从源码来看其与free没有区别,但是不保证以后会有什么修改,所以我个人还是倾向Ruby的东西用mrb\_free,自己定义的东西用free。

关于mrb\_data\_type,在这里Matz本人阐述了其作用,主要是Debug,以及定义了一个Data要如何被释放掉。正如代码所示,该struct由一段任意的字符串作为名字以及一个函数指针作为释放时使用的函数组成。

当然,为了让mRuby了解到该实例的类型,也要通过DATA\_TYPE(self) = &foo\_data\_type指定一下实例的类型。

编译后,在mirb下使用如下代码测试

> s = Test::Foo.new
 => #<Test::Foo:0x6d3d30>
> s.a
 => 0
> s.a = 10
 => 10
> s.a
 => 10
> s = 20
 => 20
> GC.start
Freed
 => nil

用实例变量包装

#include "mruby.h"
#include "mruby/compile.h"
#include "mruby/data.h"
#include "mruby/class.h"
#include "mruby/variable.h"
#include <stdlib.h>
#include <stdio.h>
struct Foo {
  int a;
};
static void foo\_free(mrb\_state\* mrb,void\* p){
  free(p);
  printf("Freed\n");
}
static struct mrb\_data\_type foo\_data\_type = {
  "foo", foo\_free
};
mrb\_value new\_foo(mrb\_state\* mrb,mrb\_value self){
  struct Foo\* f;
  f  = (struct Foo\*)mrb\_malloc(mrb,sizeof(struct Foo));
  f->a = 0;
  mrb\_iv\_set(mrb,self,mrb\_intern\_cstr(mrb,"@data"),
  mrb\_obj\_value(Data\_Wrap\_Struct(mrb,mrb->object\_class,&foo\_data\_type,(void \*)f)));
  return self;
}
mrb\_value set\_foo\_a(mrb\_state\* mrb,mrb\_value self){
  int v=0;
  mrb\_get\_args(mrb,"i",&v);
  struct Foo\* f;
  Data\_Get\_Struct(mrb,mrb\_iv\_get(mrb,self,mrb\_intern\_cstr(mrb,"@data")),&foo\_data\_type,f);
  f = (struct Foo \*)f;
  f->a = v;
  return mrb\_nil\_value();
}
mrb\_value get\_foo\_a(mrb\_state\* mrb,mrb\_value self){
  struct Foo\* f;
  Data\_Get\_Struct(mrb,mrb\_iv\_get(mrb,self,mrb\_intern\_cstr(mrb,"@data")),&foo\_data\_type,f);
  f = (struct Foo \*)f;
  return mrb\_fixnum\_value(f->a);
}

void mrb\_mruby\_c\_extension\_gem\_init(mrb\_state\* mrb) {
  struct RClass\* module\_test = mrb\_define\_module(mrb,"Test");
  struct RClass\* class\_foo = mrb\_define\_class\_under(mrb,module\_test,"Foo",mrb->object\_class);
  mrb\_define\_method(mrb,class\_foo,"initialize",new\_foo,MRB\_ARGS\_NONE());
  mrb\_define\_method(mrb,class\_foo,"a",get\_foo\_a,MRB\_ARGS\_NONE());
  mrb\_define\_method(mrb,class\_foo,"a=",set\_foo\_a,MRB\_ARGS\_REQ(1));
}
void mrb\_mruby\_c\_extension\_gem\_final(mrb\_state\* mrb) {
  /\*Do nothing\*/
}

在这里,我们定义了一个实例变量@data,用其存储struct数据。该变量通过Data\_Wrap\_Struct(mrb,mrb->object\_class,&foo\_data\_type,(void \*)f)中的mrb->object\_class被指定为了Object的实例。

我们使用了两个宏,Data\_Wrap\_Struct以及Data\_Get\_Struct,前者返回一个指定类的Data原型的实例,另一个则是从指定的对象中取得数据指针,它与DATAGETPTR是一模一样的,而与DATA\_PTR相比多了一个对类型的检测。

__Data\_Get\_Struct已经废弃,请使用DATA\_GET\_PTR替代__

mrb\_intern\_cstr会返回一个可供Ruby使用的标识符,其参数应该不用我多说。

mrb\_iv\_setmrb\_iv\_get的声明如下:

void mrb\_vm\_iv\_set(mrb\_state\*, mrb\_sym, mrb\_value);

mrb\_value mrb\_iv\_get(mrb\_state \*mrb, mrb\_value obj, mrb\_sym sym);

应该比较容易理解吧?

其他好像也没什么需要具体讲解的内容了,直接编译试试效果吧。

> s = Test::Foo.new
 => #<Test::Foo:0x903d30 @data=#<Object:0x903d18>>
> s.instance\_variables
 => [:@data]
> s.instance\_eval{@data}.class
 => Object
> s.instance\_eval{@data}.methods - Object.methods
 => []
> s.a = 10
 => 10
> s
 => #<Test::Foo:0x903d30 @data=#<Object:0x903d18>>
> s.a
 => 10
> s.instance\_eval{@data = 0}
 => 0
> s.a
(mirb):1: wrong argument type Fixnum (expected Data) (TypeError)
> s.a=102
(mirb):1: wrong argument type Fixnum (expected Data) (TypeError)
> s = 3
 => 3
> GC.start
Freed
 => nil

可以看出GC的确在好好工作。顺便如果不想让@data被修改的话可以把@data命名为data,这样它就不会被访问到了。

总结

这两种方法虽然实现方式不同,但是原理上是差不多的,都是通过DATA原型提供的DATA指针来存储数据,将struct用Ruby对象包装起来使用。不同的只是包装用的Ruby对象是什么、属于哪个类的实例而已而已。顺带一提,如果你想的话,你也可以把Data\_Wrap\_Struct中的mrb->object\_class改成其他的类,甚至是Foo类自身都可以。

ipv6访问开启

Author:zumikua Updated at:2015-12-05 18:24:30 UTC

好长时间没去digitalocean后台看看了,今次去看了一下,然后不小心手贱开启了ipv6支持…… 秉持着送佛送到西的原则我决定顺便开启本站的ipv6支持。 一开始我以为直接访问ipv6地址就能访问到海绵那边去(就像访问IPV4地址一样,具体为什么是海绵那边nginx的配置文件一直都特么很奇妙我不想看) 但是事实证明我实在是太特么年轻了…… 因为现在在家是没有ipv6环境的,所以我用了网上的一些ipv6测试网站,首先试着ping了一下IP,提示destination unreachable。这是才知道原来服务器也是需要设置一下的。于是去DO的tutorial页面搜了一下,找到了ipv6的设置教程。(DO的community建设的真的很不错),重启以后,能ping了。 然后我去dnspod加了条AAAA record,想着这下总能成功了吧。然后事实又无情地给了我一巴掌。connection refused。 推测是nginx的问题,比如没bind ipv6的port什么的,于是去网上搜了一下nginx的ipv6配置。 按照网上的说明把listen修改好以后,重启nginx提示我配置文件错误,无Ipv6支持(大概是这个意思),当时我特么就心一沉,因为我记得在nginx配置ipv6的那个页面好像说ipv6支持是要编译进nginx的…… 这里就要说一下我后台的情况了,passenger(ruby)+spawn_fcgi(php)+passenger带的nginx。 妈蛋你现在让我重编译nginx不是坑人么……passenger可没提供重装的选项…… 去passenger的文档上瞅了几眼,卧槽passenger进化的好快,都已经有从私有源安装的模式了,让我这种还在用gem安装的人顿时有了一种迷失感。 不过仔细想想从源安装的话不能编译还是没什么用,还是老老实实编译吧。 文档上说如果是升级passenger的nginx的话是不需要卸载nginx的,只要**保证安装时提供的configure参数一致**就行了。 谁特么没事会去重新编译nginx玩么,肯定是为了添加新模块好吗!编译参数一样有什么意义!!! 走投无路的我只好选择“不管三七二十一我今天就是要直接覆盖试一试反正DO上还有backup”这条路线。 在重编译之前我还特地把passenger用gem给升级了一下,希望能够看到个recompile的选项什么的,然后这件事又成了我犯下的另外一个错误…… 更新以后,哇塞新版的passenger好潮啊支持辣么多语言好棒好棒,还有依赖检查好棒好棒,诶呀这个是什么您的内存只有400+不够编译想让passenger编译通过最少需要1G内存…… **你大爷的!** 然后错误提示的下面还提供了如何添加swap空间的介绍但是我是VPS诶你确认这样能行么? 于是继续去DO的tutorial上搜swap,还真有增加swap空间的办法…… 按照DO上的步骤执行以后,再顺便把nginx的源码下下来,我终于特么能够开始编译了。 编译过程倒是挺顺利的,顺带一提好像并不需要该listen后的地址直接写个80 nginx会自动bind IPv6地址,我的nginx版本是1.9.3。 **其实并不行,还是需要用listen [::]:80;** 然后我终于特么能通过ipv6的那个站点的检测啦!只限于前两个检测点。 第三个检测点提示我我的DNS Server怎么样怎么样……于是我去测了一下DNSPod的NS服务器地址……果然没有AAAA记录! 淦你这样还算是个NS服务器么! 开始我又不想从DNSPod迁出去,于是开始考虑其他的方法……比如把ipv6子域名的ns记录给改到do的dns服务器上去……(DO的DNS服务器有AAAA记录)但是仍然通不过验证。 于是又经过了一段折腾,我忽然间意识到了,特么DO的NS服务器地址不还是需要从DNSPOD上去查么…… **我选择死亡。** 于是就是这样了,如果你目前只有ipv6的环境而你又很想浏览我这个万年不更新前端丑到没朋友只有左下角的那个女仆能看但是她也是个面瘫的博客而你的DNS服务器也不能从IPV4的NS服务器上获取到IP的话……请修改host。 本站的ipv6地址为2604:a880:1:20::57:d001 请自行在host文件内添加 ipv6.zumikua.in 2604:a880:1:20::57:d001 顺便为了不让nginx把ipv6.zumikua.in给跳转到zumikua.in也是超麻烦的,最后把配置文件写成了这样 ```` if ($host != 'zumikua.in'){ set $my_var A; } if ($host != 'ipv6.zumikua.in'){ set $my_var "${my_var}B"; } if ($my_var = AB) { rewrite ^/(.*)$ http://zumikua.in/$1 permanent; } ```` if不能嵌套,if后面还必须有个空格,条件表达式没有与和或……你特么不是语言就不要装成一门语言的样子好么…… 啊顺带一提在写这篇博文的时候我忽然发现了chrome的中文分词功能,链接在此 [http://simple-is-better.com/news/319]( http://simple-is-better.com/news/319) i18n做的比大部分本土公司还好……真是令人汗颜。
Main