Use C ++ to compile Ruby program extension tutorials.

Source: Internet
Author: User

Use C ++ to compile Ruby program extension tutorials.

One of Ruby's coolest features is to extend it using the application programming interface (API) defined in C/C ++. Ruby provides the C header file ruby. h, which is accompanied by many features that can be used to create Ruby classes, modules, and more. In addition to header files, Ruby also provides several other high-level abstractions to expand ruby built based on local Ruby. h. This article will introduce Ruby Interface for C ++ Extensions or Rice.
Create Ruby Extension

Before performing any Ruby c api or Rice extension, I 'd like to clearly introduce the standard process for creating the extension:

  • You have one or more C/C ++ source code that can be used to build a shared library.
  • If you use Rice to create an extension, you need to link the code to libruby. a and librice..
  • Copy the Shared Library to the same folder and use the folder as part of the RUBYLIB environment variable.
  • Use common require-based loading in Interactive Ruby (irb) prompt/ruby scripts. If the shared library name is rubytest. so, you only need to type require 'rubytest' to load the shared library.

Assume that the header file is ruby. h is located in/usr/lib/ruby/1.8/include, the Rice header file is located in/usr/local/include/rice/include, and the extension code is located in the file rubytest. cpp. Listing 1 shows how to compile and Load 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
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 first Hello World Program. You created a class using the Rice API named Test and the method named hello, and used it to display the string "Hello, World! ". When the Ruby interpreter loads an extension, the Init _ <shared library name> function is called. For the rubytest extension in Listing 1, this call means that rubytest. cpp has defined the Init_rubytest function. Rice allows you to use API define_class to create your own class. Listing 2 shows the relevant code.
List 2. Use the Rice API to create a class

#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. Test the 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 available, such as inspect. This occurs because the defined Test class is implicitly derived from the Object class (Each Ruby class is derived from the Object; in fact, all content in Ruby (including numbers) all objects whose base classes are objects ).

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 Use the define_method API to add a method to the Test class. Note: define_class is a function that returns an object of the Class type. define_method is a member function of the Module_Impl Class, which is the base Class of the Class. 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

Passing parameters from Ruby to C/C ++ code

Now that the Hello World program is running properly, try to pass the parameter from Ruby to the hello function and display the function with the same output as the standard output (sdtout. The simplest way is to add a string parameter 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 the Ruby environment, the following describes how to call the hello function:

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

One of the best ways to use Rice is to convert Ruby strings to std: string without any special operations.

Now, try to use a string array in the hello function, and then check how to pass information from Ruby to C ++ code. The simplest way is to use the Array data type provided by Rice. In the header file "rice/Array. hpp", define "Rice: Array". The method of using "Rice: Array" is similar to using the Standard Template Library (STL) container. It also defines common STL style iterators and other content as part of the Array interface. Listing 5 shows the count routine, which uses the Rice Array as the parameter.
Listing 5. Displaying 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 is the charm of this solution: assume that you have std: vector <std: string> as the Array_Print parameter. The following is an error thrown by 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

However, using the Array_Print routine shown here, Rice is responsible for the conversion from the Ruby Array to the C ++ Array type. The following is an example 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 opposite process and pass the C ++ array to the Ruby environment. Note that in Ruby, array elements are not necessarily of 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 Ruby arrays of different types in C ++. The following 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 often, with this flexibility, you will find that the Ruby interface is designed to convert data to a C ++ function, and the signature of this function cannot be changed. For example, you need to pass the string array from Ruby to C ++. The C ++ function signature is as follows:

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

Actually, you are looking for a from_ruby function here. The Ruby array uses this function and converts it to std: vector <std: string>. This is exactly what Rice provides. It has the from_ruby function with the following signature:

template <typename T>
T from_ruby(Object );

For each Ruby data type that needs to be converted to C ++, The from_ruby routine needs to be described in detail for the template. For example, if you pass the Ruby array to the preceding handler, listing 7 shows how to define the from_ruby function.
Listing 7. Convert the 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 a string array is passed as a function parameter from the Ruby environment, from_ruby converts it to std: vector <std: string>. The code in listing 7 is not perfect, but you have seen that arrays in Ruby have different types. Instead, you call (String) * aI). str () to get std: String from Rice: string. (Str is a method of Rice: String: View String. hpp for more information .) If you are dealing with the most common cases, listing 8 shows the relevant code.
Listing 8. Convert the ruby array to std: vector <std: string> (General)

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

Since each element of the Ruby array is still a Ruby object of the String type, we can assume that Rice has defined the from_ruby method to convert this type to std: string, no other operations are required. If this is not the case, you need to provide the from_ruby method for this conversion. The from_ruby method of to_from_ruby.ipp in Rice Resources is as follows:

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

Test this code in the Ruby environment. First, pass the array of all strings, as shown in listing 9.
Listing 9. Verify the from_ruby Function

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

As expected, print_array is called for the first time to run normally. Since there is no from_ruby method to convert Fixnum to std: string, A TypeError will be thrown by the Ruby interpreter during the second call. There are several ways to fix this error: for example, during a Ruby call, only strings are part of the array (such as t. print_array ["aa", "bb", 111. to_s]), or call the Object in the C ++ code. to_s. The to_s method is part of the Rice: Object interface, which returns Rice: String, and a predefined str method that returns std: string. Listing 10 uses the C ++ method.
Listing 10. Filling string vectors 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;
  }

Generally, the code in listing 10 is more important because you need to process the custom string representation of the User-Defined class.

Use C ++ to create a complete class with variables

You have learned how to create Ruby classes and related functions in C ++ code. For more common classes, a method is required to define instance variables and an initialize method is provided. To set and obtain the values of Ruby Object instance variables, 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 using the define_method API to declare a C ++ function as a Ruby class method, you can choose to declare the first parameter of the C ++ function as an Object, in addition, Ruby uses the reference of the called instance to fill the Object. Then, call iv_set on the Object to set the instance variables. The following shows the interface appearance in the Ruby environment:

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

Similarly, to return instance variables, the returned function must receive the Object that references the Object in Ruby and call iv_get for it. Listing 12 shows related code snippets.
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

So far, you have packaged free functions (non-class methods) as Ruby class methods. You have passed the reference to the Ruby Object by using the first parameter Object to declare the C function. This method is useful, but it is not useful when packaging C ++ classes as Ruby objects. To wrap the C ++ class, you still need to use the define_class method, unless you have templated it using the C ++ class type ". The code in listing 13 wraps the C ++ class into the Ruby type.
Listing 13. Packaging C ++ classes as Ruby types

class cppType {
  public:
   void print(String args) {
    std::cout << args.str() << endl;
   }
};
Class rb_cTest =
    define_class<cppType>("Test")
     .define_method("print", &cppType::print);

Note: As mentioned above, define_class is templated. Although this method is not suitable for all of these classes. The following is a record of the Ruby interpreter when you try to instantiate an object of the Test Type:

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

What happened just now? You need to bind the constructor to the Ruby type explicitly. (This is one of the weirdness of Rice .) Rice provides the define_constructor method to associate C ++ constructors. You also need to include the header file Constructor. hpp. Note that you must do this even if there is no explicit constructor in your code. Listing 14 provides sample code.
Listing 14. Associate the C ++ constructor with the 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::print);

You can also associate the constructor with the parameter list using the define_constructor method. Rice adds parameter types to the template list. For example, if cppType has a Constructor that receives integers, you must call define_constructor as define_constructor (Constructor <cppType, int>. Here is a warning: there are no constructors of the Ruby type. Therefore, if you have a C ++ type with multiple constructors and use define_constructor to associate them, from the Ruby environment perspective, you can initialize a type with (or without) parameters, as defined by the last define_constructor in the source code. Listing 15 explains all the content just discussed.
Listing 15. Associate constructors with Parameters

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

The following is a record from the Ruby environment. Note that the associated constructor is a constructor that Ruby understands:

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

Define the new Ruby type as part of the module

Defining a new Ruby module in C ++ can be attributed to calling define_module. To define a class that is only part of the module, use the define_class_under instead of the commonly used define_class method. The first parameter of define_class_under is the module object. According to listing 14, if you plan to define cppType as part of the Ruby module named types, listing 16 shows how to perform this operation.
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::print);

The following describes how to use the same declaration in Ruby:

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

Note: In Ruby, the module name and class name must start with an uppercase letter. If you name the module types instead of Types, Rice will not go wrong.

Use C ++ code to create a Ruby Structure

You can use the struct constructor in Ruby to quickly create a sample Ruby class. Listing 17 shows how to use three variables named a, AB, and aab to create a new class of NewClass.
Listing 17. Using Ruby Struct to create a new class

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

To perform equivalent encoding for 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 struct with the module to which the class belongs. This is the purpose of the initialize method. Use the define_member function to call and define class members. Note that you have created a new Ruby type. Unfortunately, you have not associated any C ++ type or function with it. The method for creating a class named NewClass is as follows:

#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 introduces some background knowledge: Create Ruby objects using C ++ code, associate C-style functions as Ruby object methods, and convert data types between Ruby and C ++, create instance variables and package the C ++ class as the Ruby type. You can use the ruby. h header file and libruby to perform all these operations, but you need to write a lot of sample code to complete all the operations. Rice makes the work easier. Here, I wish you a pleasant time writing new extensions for the Ruby environment using C ++! 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.