tutorial on using C + + to write Ruby program extensions _ruby topics

Source: Internet
Author: User
Tags arrays constructor extend require uppercase letter

One of the coolest features of Ruby is to extend it using the Application programming interface (API) defined by C + + +. Ruby provides the C header file Ruby.h, which comes with a number of features that you can use to create Ruby classes, modules, and more. In addition to the header file, Ruby offers several other high-level abstractions to extend Ruby based on the local ruby.h build, this article is about Ruby Interface for C + + Extensions or Rice.
Create a Ruby extension

Before doing any Ruby C API or Rice extensions, I'd like to make a clear introduction to the standard process for creating extensions:

    • You have one or more C + + source code that you can use to build shared libraries.
    • If you use Rice to create an extension, you need to link the code to LIBRUBY.A and Librice.a.
    • Copy the shared library to the same folder and use the folder as part of the RUBYLIB environment variable.
    • Common require-based loading is used in Interactive Ruby (IRB) Prompt/ruby scripts. If the shared library name is rubytest.so, simply type require ' rubytest ' to load the shared library.

Assuming that the header file Ruby.h is in/usr/lib/ruby/1.8/include, the Rice header file is in/usr/local/include/rice/include and the extension code is in the file rubytest.cpp. Listing 1 shows how to compile and load the code.
Listing 1. Compiling and loading Ruby extensions

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< c4/>bash# CP rubytest.so/opt/test
bash# export rubylib= $RUBYLIB:/opt/test bash#
IRB irb>
require ' Rubytest '
=> True

Hello World Program

Now you are ready to use Rice to create your own first Hello world program. You created a class using the Rice API named Test and a method named Hello, which displays the string "Hello, world!". When the Ruby interpreter loads the extension, the function init_<shared library name> is invoked. For the rubytest extension of Listing 1, this call means that rubytest.cpp has defined the function init_rubytest. Rice supports the use of API Define_class to create your own classes. Listing 2 shows the relevant code.
Listing 2. Create a class using the Rice API

#include "rice/class.hpp"
extern "C"
void Init_rubytest () { 
 Class tmp_ = Define_class ("Test");
}

When you compile and load the code in Listing 2 in IRB, you should get the output shown in Listing 3.
Listing 3. To test a class created using 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 ", ...]

Note that there are several predefined class methods to use, such as inspect. This occurs because the defined Test class is implicitly derived from the object class (each Ruby class derives from object; in fact, all content in Ruby, including numbers), is an object of the base class.

Now, add a method for the Test class. Listing 4 shows the relevant code.
Listing 4. Add a method for the Test class

void Hello () {
  std::cout << "Hello world!";
}
extern "C"
 void Init_rubytest () {
   Class test_ = Define_class ("Test")
     . Define_method ("Hello", & Hello);
}

Listing 4 uses the Define_method API to add methods to the Test class. Note that Define_class is a function that returns an object of type class, Define_method is a member function of the Module_impl class, which is the base class for classes. The following is a Ruby test to verify that all content is running well:

irb> require ' rubytest '
=> true
irb> Test.new.hello
Hello, world!
=> Nil

Pass a parameter from Ruby to C + + code

Now, the Hello World program is running normally, trying to pass parameters from Ruby to the Hello function and having the function display the same output as standard output (sdtout). The easiest way to do this is to add a string argument to the Hello function:

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

In a Ruby environment, here's how to call the Hello function:

Irb> a = test.new
<Test:0x0145e42112>
irb> A.hello "Hello World, Ruby"
Hello World in ruby
   => Nil

One of the best things about Rice is that you don't have to do anything special to convert Ruby strings to std::string.

Now try using a string array in the Hello function, and then examine how to pass the information from Ruby to C + + code. The easiest way to do this is to use the Array data type provided by Rice. Rice::array is defined in the header file rice/array.hpp, and Rice::array is similar to using the Standard Template Library (STL) container. You also define common STL style iterators as part of the Array interface. Listing 5 shows the Count routine, which uses Rice Array as an argument.
Listing 5. Show Ruby Arrays

#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
   }
 }

Now, here's the beauty of this solution: Suppose you have std::vector<std::string> as a array_print parameter. Here is the error that Ruby throws:

>> 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

However, with the Array_print routine shown here, Rice is responsible for performing conversions from the Ruby array to the C + + array type. Here is the sample output:

>> t = test.new
=> #<test:0x100494688>
>> t.array_print ["Hello", "World", "Ruby"]
Array has hello
array has world
Array has ruby
=> Nil

Now, try the reverse process of passing an array of C + + to the Ruby environment. Note that in Ruby, array elements are not necessarily the same type. Listing 6 shows the relevant code.
Listing 6. Passing arrays from C + + to 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_;
 }

Listing 6 clearly shows that you can create a different type of Ruby array in C + +. Here is the test code in Ruby:

>> x = T.return_array
=> [1, 2.3, "hello"]
>> x[0].class
=> fixnum
>> x[1]. Class
=> Float
>> x[2].class
=> String

What if I don't have the flexibility to change the C + + parameter list?

More commonly, with this flexibility, you will find that the Ruby interface is designed to convert data to C + + functions, and the signature of that function cannot be changed. For example, consider the case where you need to pass an array of strings from Ruby to C + +. The C + + function signature looks like this:

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

In fact, what you're looking for here is some kind of from_ruby function, which the Ruby array uses and converts it to std::vector<std::string>. This is what Rice provides, From_ruby functions with the following signatures:

Template <typename t>
T From_ruby (Object);

For each Ruby data type that needs to be converted to C + + types, the From_ruby routine needs to be described in detail for the template. For example, if you pass a Ruby array to the above handler function, listing 7 shows how the From_ruby function should be defined.
Listing 7. Converts a ruby array to 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;
  }

Note that you do not need to explicitly call the From_ruby function. When passing a string array as a function argument from a Ruby environment, From_ruby converts it to std::vector<std::string>. The code in Listing 7 is not perfect, but you've seen that the arrays in Ruby have different types. Instead, you invoked ((String) *ai). STR () to obtain std::string from rice::string. (Str is a way to rice::string: view string.hpp For more information about it.) If you are dealing with the most common scenario, listing 8 shows the relevant code.
Listing 8. Converts a ruby array to std::vector<std::string> (general case)

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;
  }

Because each element of the Ruby array is still a Ruby object of type String, you can assume that Rice has defined the From_ruby method, converts this type to std::string, and does not require additional action. If this is not the case, you will need to provide a From_ruby method for this transformation. The following is the From_ruby method of To_from_ruby.ipp in Rice resources:

template<>
inline std::string from_ruby<std::string> (Rice::object x) {return
 rice::string (x) . STR ();
}

Test this code in a Ruby environment. First pass an array of all the strings, as shown in Listing 9.
Listing 9. Verifying From_ruby Features

>> t = test.new
=> #<test:0x10e71c5c8>
>> t.print_array ["AA", "BB"]
aa bb
=> Nil
>> t.print_array ["AA", "BB", "
typeerror:wrong" argument type Fixnum (expected String) from
 (IR b): 4:in ' Print_array ' from
 (IRB): 4

As expected, the first call to the Print_array runs normally. Because there is no From_ruby method to convert Fixnum to std::string, the second call causes the Ruby interpreter to throw typeerror. There are several ways to fix this error: for example, during a Ruby call, only strings are passed as part of the array (such as t.print_array["AA", "BB", 111.to_s]), or in C + + code, the object.to_s is invoked. The To_s method is part of the Rice::object interface, which returns rice::string, and it also has a predefined str method that returns std::string. Listing 10 uses the C + + method.
Listing 10. Populating a string vector with 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;
  }

In general, the code in Listing 10 is more important because you need to handle the custom string representation of a user-defined class.

Use C + + to create a complete class with variables

You've learned how to create Ruby classes and related functions within C + + code. For more general classes, you need a method that defines the instance variables and provides a initialize method. To set and get the value of a Ruby object instance variable, you can use the Rice::object::iv_set and Rice::object::iv_get methods. Listing 11 shows the relevant code.
Listing 11. Defining the Initialize method in C + +

void Init (Object self) {
   self.iv_set ("@intvar", 121);
   Self.iv_set ("@stringvar", String ("testing"));
Class ctest = Define_class ("Test").
             Define_method ("Initialize", &init);

When you declare a C + + function as a Ruby class method using the Define_method API, you can optionally declare the first parameter of a C + + function as Object, and Ruby populates the object with a reference to the invocation instance. Then, on Object, set the instance variable with Iv_set. The following is the appearance of the interface in a Ruby environment:

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

Similarly, to return an instance variable, the returned function needs to receive object in Ruby and invoke Iv_get on it. Listing 12 shows the associated code fragment.
Listing 12. Retrieving values from Ruby objects

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);

Convert C + + class to Ruby type

To date, you have packaged free functions (not class methods) as Ruby class methods. You have passed a reference to a Ruby object by declaring the C function with the first argument object. This is useful, but this approach is not easy to use when wrapping a C + + class as a Ruby object. To wrap a C + + class, you still need to use the Define_class method, unless you now use the C + + class type to "templating" it. The code in Listing 13 wraps the C + + class as a Ruby type.
Listing 13. Wrapping a C + + class as a Ruby type

Class Cpptype {public
  :
   void print (String args) {
    std::cout << args.str () << Endl;
   }
};
Class rb_ctest =
    define_class<cpptype> ("Test")
     . Define_method ("Print", &cpptype::p rint);

Note that, as mentioned earlier, the Define_class has been templated. Although this method is not suitable for all of these. The following is a record of the Ruby interpreter when you attempt to instantiate the object of type Test:

>> t = test.new
typeerror:allocator undefined for Test to
 (IRB): 3:in ' new ' from
 (IRB): 3

What the hell just happened? You need to explicitly bind the constructor to the Ruby type. (This is one of Rice's quirks.) Rice gives you the Define_constructor method to associate a C + + type constructor. You also need to include the header file CONSTRUCTOR.HPP. Note that you must do this even if you do not have an explicit constructor in your code. Listing 14 provides the sample code.
Listing 14. Associating a C + + constructor with a Ruby type

#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::p rint);

You can also associate a constructor with a parameter list that uses the Define_constructor method. Rice does this by adding a parameter type to the list of templates. For example, if Cpptype has a constructor that receives integers, you must call Define_constructor as Define_constructor (Constructor<cpptype, int> ()). Here's a warning: Ruby types don't have multiple constructors. So, if you have C + + types with multiple constructors and associate them with Define_constructor, from the point of view of the Ruby environment, you can, as the last define_constructor of the source code defines, Initializes a type that has (or has no) parameters. Listing 15 explains all the things that have just been discussed.
Listing 15. Associating a constructor with a parameter

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: :p rint);

Here is a record from the Ruby environment. Note that the last associated constructor is the constructor that Ruby understands:

>> t = test.new 2
typeerror:wrong argument type fixnum (expected Array) from
 (IRB): 2:in ' Initialize '
 fr Om (IRB): 2:in ' new ' from
 (IRB): 2
>> t = test.new [1, 2]
2
=> #<test:0x10d52cf48>

Define a new Ruby type as part of a module

Defining a new Ruby module from C + + can be attributed to invoking Define_module. To define a class that is only part of the module, use Define_class_under instead of the commonly used Define_class method. The first parameter of the Define_class_under is the module object. In Listing 14, if you plan to define Cpptype as part of a Ruby module named types, listing 16 shows how to do this.
Listing 16. Declaring a type as part of a module

#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::p rint);

Here's how to use the same declaration in Ruby:

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

Note that in Ruby, the module name and class name must begin with an uppercase letter. If you name the module types instead of Types,rice, there will be no error.

Creating Ruby structures with C + + code

You use the struct constructor in Ruby to quickly create a sample Ruby class. Listing 17 shows a way to create a new class of type Newclass with three variables named a, AB, and AAB.
Listing 17. Create a new class using 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
>& Gt 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

To make the equivalent encoding in Listing 17 in C + +, you need to use the define_struct () API declared in the header file RICE/STRUCT.HPP. This API returns RICE::STRUCT. You associate the Ruby class created by this struct with the module to which the class belongs. This is the purpose of the Initialize method. Use the Define_member function call to define individual class members. Note that you have created a new Ruby type, but you have not associated any C + + type or function with it. Here's how to create a class named Newclass:

#include "rice/struct.hpp" ...
Module RB1 = Define_module ("Types");
Define_struct ().
    Define_member ("a").
    Define_member ("AB").
    Define_member ("AaB").
    Initialize (RB1, "Newclass");

Conclusion

This article describes some background knowledge: creating Ruby objects with C + + code, associating C-style functions as Ruby object methods, converting data types between Ruby and C + +, creating instance variables, and wrapping C + + classes as ruby types. You can do all of this with the Ruby.h header file and Libruby, but you need to write a lot of boilerplate code to end all of the operations. Rice makes the work easier. Here, I wish you to use C + + for the Ruby environment to write new extensions happy! world!

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.