本科rice 的cs+_使用Rice在C ++中构建Ruby扩展

本科rice 的cs+

Ruby最酷的功能之一就是可以使用C/C++定义的应用程序编程接口(API)对其进行扩展。 Ruby提供了C头ruby.h,它附带了用于创建Ruby类,模块等的大量函数。 除了Ruby提供的标头外,还可以使用其他一些高级抽象来扩展基于本机ruby.h构建的Ruby。本文研究的对象是用于C++扩展的Ruby接口,即Rice。 。

创建一个Ruby扩展

在进入任何Ruby的C API或Rice扩展之前,我想清楚地描述创建扩展的标准过程:

  1. 您有一个或多个C/C++源,并在其中使用了一个共享库。
  2. 如果使用Rice创建扩展,则需要将代码链接到libruby.a和librice.a。
  3. 将共享库复制到某个文件夹,并将该文件夹作为RUBYLIB环境变量的一部分。
  4. 在Interactive Ruby(irb)提示/Ruby脚本中使用通常的基于require的加载。 如果共享库名为rubytest.so,则只需键入require 'rubytest'加载共享库。

假设头文件ruby.h位于/usr/lib/ruby/1.8/include中,莱斯头文件位于/ usr / local / include / rice / include中,扩展代码位于文件rubytest.cpp中。 清单1显示了如何编译和加载代码。

清单1.编译和加载Ruby扩展
bash# g++ -c rubytest.cpp –g –Wall -I/usr/lib/ruby/1.8/include  \
    -I/usr/local/include/rice/include
bash# g++ -shared –o rubytest.so rubytest.o -L/usr/lib/ruby/1.8/lib \
    -L/usr/local/lib/rice/lib  -lruby –lrice –ldl  -lpthread
bash# cp rubytest.so /opt/test
bash# export RUBYLIB=$RUBYLIB:/opt/test
bash# irb
irb> require 'rubytest'
=> true

Hello World程序

现在,您准备使用Rice创建您的第一个Hello World程序。 您可以使用Rice API创建一个名为Test的类,该类的方法hello显示字符串“ Hello,World!”。 当Ruby解释器加载扩展时,它将调用函数Init_<shared library name> 。 对于清单1中rubytest扩展,此调用意味着rubytest.cpp具有定义的Init_rubytest函数。 Rice使您可以使用API define_class创建自己的类。 清单2显示了代码。

清单2.使用Rice API创建一个类
#include "rice/Class.hpp"
extern "C"
void Init_rubytest( ) { 
  Class tmp_ = define_class("Test");
}

在irb中编译并加载清单2中的代码时,应该获得清单3中的输出。

清单3.测试使用Rice创建的类
irb> require ‘rubytest’
=> true
irb> a = Test.new
=> #<Test:0x1084a3928>
irb> a.methods
=> ["inspect", "tap", "clone", "public_methods", "__send__", 
      "instance_variable_defined?", "equal?", "freeze", …]

请注意,可以使用几种预定义的类方法,例如inspect 。 发生这种情况是因为您定义的Test类是从Object类隐式派生的(每个Ruby类都是从Object派生的;实际上,Ruby中的所有Object (包括数字)都是一个以Object为基类的Object )。

现在,向Test类添加一个方法。 清单4显示了代码。

清单4.向Test类添加方法
void hello() {
   std::cout << "Hello World!";
}
extern "C"
 void Init_rubytest() {
      Class test_ = define_class("Test")
         .define_method("hello", &hello);
}

清单4使用define_method API向Test类添加一个方法。 注意define_class是一个返回Class类型对象的函数; define_methodModule_Impl类的成员函数, Module_ImplClass的基类。 这是Ruby测试,用于验证一切是否正常:

irb> require ‘rubytest’
=> true
irb> Test.new.hello
Hello, World!
=> nil

将参数从Ruby传递到C / C ++代码

现在,您的Hello World程序已启动,尝试将Ruby中的参数传递给hello函数,并使该函数与标准输出(sdtout)显示相同。 最简单的方法是在hello函数中添加一个字符串参数:

void hello(std::string args) {
   std::cout << args << std::endl;
}
extern "C"
 void Init_rubytest() {
      Class test_ = define_class("Test")
         .define_method("hello", &hello);
}

在Ruby世界中,这是调用hello函数的方式:

irb> a = Test.new
<Test:0x0145e42112>
irb> a.hello "Hello World in Ruby"
Hello World in Ruby
=> nil

使用Rice最好的事情是,您无需执行任何特定操作即可将Ruby字符串转换为std::string

现在,尝试在hello函数中使用字符串数组,然后检查如何将信息从Ruby传递到C++代码。 最简单的方法是使用Rice提供的Array数据类型。 在Rice / Array.hpp标头中定义的使用Rice::Array类似于使用标准模板库(STL)容器。 常用的STL样式迭代器等定义为Array接口的一部分。 清单5显示了count例程,该例程以Rice Array作为参数。

清单5.显示一个Ruby数组
#include "rice/Array.hpp"

void Array_Print (Array a)   {
      Array::iterator aI = a.begin();
      Array::iterator aE = a.end();
      while (aI != aE) {
        std::cout << "Array has " << *aI << std::endl;
        ++aI;
      }
  }

现在,这里是解决方案的Array_Print :假设您有一个std::vector<std::string>作为Array_Print参数。 这是Ruby引发的错误:

>> t = Test.new
=> #<Test:0x100494688>
>> t.Array_Print ["g", "ggh1", "hh1"]
ArgumentError: Unable to convert Array to std::vector<std::string, 
    std::allocator<std::string> >
	from (irb):3:in `hello'
	from (irb):3

但是,借助此处显示的Array_Print例程,Rice负责从Ruby数组到C++ Array类型的转换。 这是一个示例运行:

>> t = Test.new
=> #<Test:0x100494688>
>>  t.Array_Print ["hello", "world", "ruby"]
Array has hello
Array has world
Array has ruby
=> nil

现在以另一种方式尝试一下,将数组从C++传递到Ruby世界。 请注意,在Ruby中,数组元素可能不一定是同一类型。 清单6显示了代码。

清单6.将数组从C ++传递给Ruby
#include "rice/String.hpp"
#include "rice/Array.hpp"
using namespace rice; 

Array return_array (Array a)  {
      Array tmp_;
      tmp_.push(1);
      tmp_.push(2.3);
      tmp_.push(String("hello"));
      return tmp_;
 }

清单6清楚地表明,可以在C++创建具有不同类型的Ruby数组。 这是Ruby中的测试代码:

>> x = t.return_array
=> [1, 2.3, "hello"]
>> x[0].class
=> Fixnum
>> x[1].class
=> Float
>> x[2].class
=> String

如果我没有灵活性来更改C ++参数列表怎么办?

更为常见的是,您会发现Ruby接口旨在将数据转换为您无法更改其签名的C++函数。 例如,考虑需要将字符串数组从Ruby传递到C++C++函数签名如下所示:

void print_array(std::vector<std::string> args)

实际上,您在这里寻找的是某种from_ruby函数,该函数接收一个Ruby数组并将其转换为std::vector<std::string> 。 这正是莱斯提供的—具有以下签名的from_ruby函数:

template <typename T>
T from_ruby(Object );

对于需要转换为C++类型的每种Ruby数据类型,都需要对from_ruby例程进行模板专用from_ruby 。 例如,如果将Ruby数组传递给上面显示的process函数, 清单7显示了如何定义from_ruby函数。

清单7.将ruby数组转换为std :: vector <std :: string>
template<>
std::vector<std::string> from_ruby< std::vector<std::string> > (Object o)   {
    Array a(o);
    std::vector<std::string> v;
    for(Array::iterator aI = a.begin(); aI != a.end(); ++aI)
        v.push_back(((String)*aI).str());
    return v;
    }

请注意,无需显式调用from_ruby函数。 当string数组从Ruby世界作为函数参数传递时, from_ruby将其转换为std::vector<std::string> 。 但是,清单7中的代码并不完美,并且您已经看到Ruby中的数组可以具有不同的类型。 相反,您已调用((String)*aI).str()Rice::String获得std::string 。 ( strRice::String的方法:有关更多详细信息,请检查String.hpp。)如果要处理最普通的情况, 清单8显示了代码的外观。

清单8.将ruby数组转换为std :: vector <std :: string>(一般情况)
template<>
std::vector<std::string> from_ruby< std::vector<std::string> > (Object o)   {
    Array a(o);
    std::vector<std::string> v;
    for(Array::iterator aI = a.begin(); aI != a.end(); ++aI)
        v.push_back(from_ruby<std::string> (*aI));
    return v;
    }

因为Ruby数组的每个元素也是String类型的Ruby对象,并且您押注Rice定义了一个from_ruby方法,用于将该类型转换为std::string ,所以无需执行其他操作。 如果不是这种情况,则需要提供from_ruby方法进行转换。 以下是莱斯资源中to_from_ruby.ipp中的from_ruby方法:

template<>
inline std::string from_ruby<std::string>(Rice::Object x) {
  return Rice::String(x).str();
}

在Ruby世界中测试此代码。 首先传递所有字符串的数组,如清单9所示

清单9.验证from_ruby功能
>> t = Test.new
=> #<Test:0x10e71c5c8>
>> t.print_array ["aa", "bb"]
aa bb
=> nil
>> t.print_array ["aa", "bb", 111]
TypeError: wrong argument type Fixnum (expected String)
	from (irb):4:in `print_array'
	from (irb):4

如预期的那样,第一次调用print_array进行得很好。 因为没有from_ruby方法将Fixnum转换为std::string ,所以第二次调用导致Ruby解释器抛出TypeError 。 有多种方法可以修复此错误,例如,在Ruby调用期间,仅将字符串作为数组的一部分(例如t.print_array["aa", "bb", 111.to_s] )或在C++内部t.print_array["aa", "bb", 111.to_s]代码,调用Object.to_sto_s方法是Rice::Object接口的一部分,并返回Rice::String ,它具有预定义的str方法,该方法返回std::string清单10使用C++方法。

清单10.使用Object.to_s填充字符串向量
template<>
std::vector<std::string> from_ruby< std::vector<std::string> > (Object o)   {
    Array a(o);
    std::vector<std::string> v;
    for(Array::iterator aI = a.begin(); aI != a.end(); ++aI)
        v.push_back(aI->to_s().str());
    return v;
    }

通常, 清单10中的代码将涉及更多,因为您需要处理用户定义的类的自定义字符串表示形式。

使用C ++创建带有变量的完整类

您已经了解了如何在C++代码中创建Ruby类和相关函数。 对于更通用的类,您需要一种定义实例变量以及提供initialize方法的方法。 要设置和获取Ruby对象的实例变量的值,请分别使用Rice::Object::iv_setRice::Object::iv_get方法。 清单11显示了代码。

清单11.在C ++中定义一个initialize方法
void init(Object self) {
      self.iv_set("@intvar", 121);
      self.iv_set("@stringvar", String("testing"));
 }
Class cTest = define_class("Test").
                          define_method("initialize", &init);

当使用define_method API将C++函数声明为Ruby类方法时,可以选择将C++函数的第一个参数声明为Object ,并且Ruby会使用对调用实例的引用来填充此Object 。 然后,在Object上调用iv_set来设置实例变量。 接口在Ruby世界中的外观如下:

>> require 'rubytest'
=> true
>> t = Test.new
=> #<Test:0x1010fe400 @stringvar="testing", @intvar=121>

同样,要返回实例变量,返回函数需要采用一个Object ,该对象引用Ruby中的对象并在其上调用iv_get清单12显示了一个片段。

清单12.从Ruby对象检索值
void init(Object self) {
      self.iv_set("@intvar", 121);
      self.iv_set("@stringvar", String("testing"));
 }
int getvalue(Object self) { 
    return self.iv_get("@intvar");
}
Class cTest = define_class("Test").
                          define_method("initialize", &init).
                          define_method("getint", &getvalue);

将C ++类转换为Ruby类型

到目前为止,您已经将自由函数(即非类方法)包装为Ruby类方法。 通过使用第一个参数Object声明C函数,您已经传递了对Ruby对象的引用。 这种方法有效,但是不足以将C++类包装为Ruby对象。 要包装C++类,您仍然使用define_class方法,除了现在用C++类类型“模板化”它。 清单13中的代码将C++类包装为Ruby类型。

清单13.将C ++类包装为Ruby类型
class cppType {
    public:
      void print(String args) {
        std::cout << args.str() << endl;
      }
};
Class rb_cTest =
        define_class<cppType>("Test")
         .define_method("print", &cppType::print);

请注意,如所讨论的, define_class是模板化的。 但是,这堂课并不是所有人都很好。 当您尝试实例化Test类型的对象时,这是Ruby解释器的日志:

>> t = Test.new
TypeError: allocator undefined for Test
	from (irb):3:in `new'
	from (irb):3

刚才发生了什么? 好吧,您需要将构造函数显式绑定到Ruby类型。 (这是Rice的一些怪癖之一。)Rice为您提供了define_constructor方法来关联C++类型的构造函数。 您还需要包括标题Constructor.hpp。 请注意,即使您的代码中没有显式构造函数,也必须这样做。 清单14提供了示例代码。

清单14.将C ++构造函数与Ruby类型相关联
#include "rice/Constructor.hpp"
#include "rice/String.hpp"
class cppType {
    public:
    void print(String args) {
        std::cout << args.str() << endl;
      }
    };

Class rb_cTest =
        define_class<cppType>("Test")
         .define_constructor(Constructor<cppType>())
        .define_method("print", &cppType::print);

也可以使用define_constructor方法将构造函数与参数列表相关联。 Rice的方法是将参数类型添加到模板列表中。 例如,如果cppType具有接受整数的构造函数,则必须将define_constructor调用为define_constructor(Constructor<cppType, int>()) 。 需要注意的是:Ruby类型没有多个构造函数。 因此,如果您的C++类型具有多个构造函数,并且都使用define_constructor它们关联起来,那么在Ruby世界中,您可以使用源代码中最后一个define_constructor定义的参数(或不使用参数)实例化该类型。 清单15解释了刚才讨论的所有内容。

清单15.将构造函数与参数关联
class cppType {
    public:
      cppType(int m) {
        std::cout << m << std::endl;
      }
      cppType(Array a) {
        std::cout << a.size() << std::endl;
      }
      void print(String args) {
        std::cout << args.str() << endl;
      }
    };
Class rb_cTest =
        define_class<cppType>("Test")
         .define_constructor(Constructor<cppType, int>())
         .define_constructor(Constructor<cppType, Array>())
         .define_method("print", &cppType::print);

这是Ruby世界的日志。 请注意,最后关联的构造函数是Ruby可以理解的构造函数:

>> t = Test.new 2
TypeError: wrong argument type Fixnum (expected Array)
	from (irb):2:in `initialize'
	from (irb):2:in `new'
	from (irb):2
>> t = Test.new [1, 2]
2
=> #<Test:0x10d52cf48>

定义新的Ruby类型作为模块的一部分

C++定义新的Ruby模块归结为对define_module的调用。 要定义仅在所述模块中可用的类,请使用define_class_under而不是通常的define_class方法。 define_class_under的第一个参数是模块对象。 在清单14中 ,如果要将cppType定义为名为types的Ruby模块的一部分, 清单16显示了如何实现。

清单16.将类型声明为模块的一部分
#include "rice/Constructor.hpp"
#include "rice/String.hpp"
class cppType {
    public:
    void print(String args) {
        std::cout << args.str() << endl;
      }
    };

Module rb_cModule = define_module("Types");
Class rb_cTest =
        define_class_under<cppType>(rb_cModule, "Test")
         .define_constructor(Constructor<cppType>())
        .define_method("print", &cppType::print);

这是您在Ruby中的用法:

>> include Types
=> Object
>> y = Types::Test.new [1, 1, 1]
3
=> #<Types::Test:0x1058efbd8>

请注意,在Ruby中,模块名称和类名称必须以大写字母开头。 如果您将模块类型而不是类型命名,Rice不会出错。

使用C ++代码创建Ruby结构

您可以在Ruby中使用struct构造来快速创建样板Ruby类。 清单17显示了创建具有三个名为aabaab变量的NewClass类型的新类的典型Ruby方法。

清单17.使用Ruby Struct创建一个新类
>> NewClass = Struct.new(:a, :ab, :aab)
=> NewClass
>> NewClass.class
=> Class
>> a = NewClass.new
=> #<struct NewClass a=nil, ab=nil, aab=nil>
>> a.a = 1
=> 1
>> a.ab = "test"
=> "test"
>> a.aab = 2.33
=> 2.33
>> a
=> #<struct NewClass a=1, ab="test", aab=2.33>
>> a.a.class
=> Fixnum
>> a.ab.class
=> String
>> a.aab.class
=> Float

要用C++编写清单17中的等效C++ ,您需要使用在标头rice / Struct.hpp中声明的define_struct( ) API。 该API返回Rice::Struct 。 您将关联此struct创建的Ruby类以及该类将成为其一部分的模块。 这就是initialize方法的用途。 单独的类成员是使用define_member函数调用定义的。 请注意,除了尚未将任何C++类型或函数与之关联之外,您已经创建了一个新的Ruby类型。 这是创建名为NewClass的类的代码:

#include "rice/Struct.hpp"
…
Module rb1 = define_module("Types");
define_struct().
        define_member("a").
        define_member("ab").
        define_member("aab").
        initialize(rb1, "NewClass");

结论

本文涵盖了相当多的基础知识-使用C++代码创建Ruby对象,将C样式函数作为Ruby对象方法进行关联,在Ruby和C++之间转换数据类型,创建实例变量,以及将C++类包装为Ruby类型。 可以使用ruby.h标头和libruby来完成所有这些工作,但是您必须做很多样板代码才能使所有末端工作。 赖斯使所有工作变得更加容易。 在此祝您在Ruby世界中用C++编写新扩展充满乐趣!


翻译自: https://www.ibm.com/developerworks/opensource/library/os-extendruby/index.html

本科rice 的cs+

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值