美文网首页
ruby c扩展 - travel between ruby a

ruby c扩展 - travel between ruby a

作者: SecondRocker | 来源:发表于2023-12-22 19:19 被阅读0次

    什么是ruby c扩展?

    我们知道,我们调用的ruby方法,很多都是由c实现的:


    image.png

    上图所示,String类的定义,他的方法都是由c实现的;使用类似的方法我们也可以用c语言实现一些扩展功能,成为ruby的c extension。

    什么时候使用 c 扩展

    • 想以 C 的速度优化某个特别重要的方法
    • 想在 C 库和 Ruby 之间创建接口(一个好用的c库,我想在ruby里调用)

    三步实现一个c扩展

    1. ext_conf.rb
    require 'mkmf'
    create_makefile 'echo'
    
    1. echo.c
    
    #include <stdio.h>
    #include <ruby.h>
    
    VALUE do_it(VALUE self, VALUE str) {
      const char* c_string = StringValueCStr(str);
      printf("from ruby:%s\n",c_string);
      
      return Qnil;
    }
    
    void Init_echo() {
      VALUE echo = rb_define_class("Echo", rb_cObject);
      rb_define_singleton_method(echo, "do_it", do_it, 1);
    }
    
    1. 生成,调用
    ruby extconf.rb #create makefile
    make # 编译生成c扩展
    ruby -e "require './echo';Echo.do_it('abc')" # ruby调用
    # from ruby:abc
    

    详解

    require 'mkmf'
    create_makefile 'echo'
    

    以上执行后就会生成一个Makefile文件,指定了ruby的库文件,头文件位置,最终生成共享库文件echo.bundle

    // 引入ruby头文件,可以使用ruby定义的数据类型,方法
    #include <ruby.h>
    
    // void Init_echo 中echo必须与上面的库名一致
    // ruby在require 扩展时会调用这个方法
    void Init_echo() {
      // ruby的数据都是由 c语言的VALUE类型表示的
      // rb_cObject 就是ruby里的Object
      // rb_define_class 定义一个Echo类,以rb_cObject为基类
      VALUE echo = rb_define_class("Echo", rb_cObject);
      // 给echo类定义一个单例方法,c中的函数名为do_it,1代表有一个参数
      rb_define_singleton_method(echo, "do_it", do_it, 1);
    }
    
    // 给ruby方法对用的函数都要有返回值VALUE(ruby方法都有返回值)
    // VALUE self 方法调用发
    // VALUE str 传过来的一个参数
    VALUE do_it(VALUE self, VALUE str) {
      // StringValueCStr 把 ruby的string转化为c的char*
      const char* c_string = StringValueCStr(str);
      // 打印
      printf("from ruby:%s\n",c_string);
      // Qnil就是ruby的nil
      return Qnil;
    }
    

    至此,我们完成了从ruby到c的穿梭;其实重要的无外乎两点:

    1. ruby调用传入的参数转为c类型处理
    2. 传出的参数包装为VALUE(ruby数据类型)

    不固定参数 及 块调用

    // args 参数数组
    VALUE sum(VALUE self, VALUE args) {
      // 获取数组长度
      long len = RARRAY_LEN(args);
      long i;
      long sum = 0; // 合计值
      for (i = 0; i < len; i++) {
        // 挨个获取数组index的内容
        VALUE element = rb_ary_entry(args, i);
        // 转为 long相加
        sum = sum + FIX2LONG(element);
      }
      // 如果提供了block(block_given?)
      if (rb_block_given_p()) {
        // 调用block 传参,1:1个参数
        // 把sum * 2 转回ruby int
        rb_yield_values(1, LONG2FIX(sum*2));
      }
      // 返回 转回ruby int
      return LONG2FIX(sum);
    }
    
    void Init_sum() {
      // 给Objectding定义单例方法,参数-2 表示参数以数组方式传入
      rb_define_singleton_method(rb_cObject, "sum", sum, -2);
    }
    

    常用的一些方法

    https://docs.ruby-lang.org/en/3.2/extension_rdoc.html

    typed_data_struct

    TypedData 对象允许 C 扩展开发人员在对象中存储他们自己的 C 结构。与实例变量的值必须是有效的 Ruby 对象不同,任何东西都可以放置在这个结构中。

    需要注意的是,当我们将 TypedData 对象传递回 Ruby 时,它看起来就像任何其他 Ruby 对象一样。换句话说,我们仍然可以执行诸如访问实例变量和调用 TypedData 对象上的方法之类的操作。


    image.png image.png

    https://github.com/secondrocker/typeddata-benchmark

    基准测试结果


    image.png

    typeddata 最快,提升近一倍速度,ivar快点有限,ruby最慢

    相关文章

      网友评论

          本文标题:ruby c扩展 - travel between ruby a

          本文链接:https://www.haomeiwen.com/subject/wpbxbdtx.html