citizen428.blog()

Try to learn something about everything

Ruby: Going to the C Side

Yesterday afternoon I decided it’s about time I finally learn how to write Ruby C extensions, so I went ahead and rewrote one of our RubyLearning.org exercises in C. Here’s the exercise description:

Write a class called Person, that has balance as an instance variable and
the following public method: show_balance.

I shall create the Person object as follows:

p = Person.new(40000)
puts p.show_balance # calling the method

Here’s the program in C, with relevant function signatures included as comments:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<span class='line'><span class="cp">#include &lt;ruby.h&gt;</span>
</span><span class='line'>
</span><span class='line'><span class="k">static</span> <span class="n">VALUE</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">VALUE</span> <span class="n">self</span><span class="p">,</span> <span class="n">VALUE</span> <span class="n">amount</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="c1">// VALUE rb_iv_set(VALUE obj, char *name, VALUE value)</span>
</span><span class='line'>  <span class="n">rb_iv_set</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="s">&quot;@balance&quot;</span><span class="p">,</span> <span class="n">amount</span><span class="p">);</span>
</span><span class='line'>  <span class="k">return</span> <span class="n">self</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="k">static</span> <span class="n">VALUE</span> <span class="nf">show_balance</span><span class="p">(</span><span class="n">VALUE</span> <span class="n">self</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="c1">// VALUE rb_iv_get(VALUE obj, char *name)</span>
</span><span class='line'>  <span class="k">return</span> <span class="n">rb_iv_get</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="s">&quot;@balance&quot;</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="kt">void</span> <span class="nf">Init_person</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="c1">// VALUE rb_define_class(char *name, VALUE superclass)</span>
</span><span class='line'>  <span class="n">VALUE</span> <span class="n">cPerson</span> <span class="o">=</span> <span class="n">rb_define_class</span><span class="p">(</span><span class="s">&quot;Person&quot;</span><span class="p">,</span> <span class="n">rb_cObject</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>  <span class="c1">// void rb_define_method(VALUE classmod, char *name, VALUE(*func)(), int argc)</span>
</span><span class='line'>  <span class="n">rb_define_method</span><span class="p">(</span><span class="n">cPerson</span><span class="p">,</span> <span class="s">&quot;initialize&quot;</span><span class="p">,</span> <span class="n">initialize</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
</span><span class='line'>  <span class="n">rb_define_method</span><span class="p">(</span><span class="n">cPerson</span><span class="p">,</span> <span class="s">&quot;show_balance&quot;</span><span class="p">,</span> <span class="n">show_balance</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span>

I think the code is very self-explanatory, almost like writing Ruby in C. As you can see we define a class called “Person” in the “Init_person” function (which is called by Ruby when it loads our module), where we add the functions as instance methods with “rb_define_method”. To compile this, we need an extconf.rb, which contains the following:

1
2
3
4

1
2
3
4
<span class='line'><span class="n">require</span> <span class="err">&#39;</span><span class="n">mkmf</span><span class="err">&#39;</span>
</span><span class='line'>
</span><span class='line'><span class="n">extension_name</span> <span class="o">=</span> <span class="err">&#39;</span><span class="n">person</span><span class="err">&#39;</span>
</span><span class='line'><span class="n">create_makefile</span><span class="p">(</span><span class="n">extension_name</span><span class="p">)</span>
</span>

Now we can build and run it:

1
2
3
4
5
6
7
8
9

1
2
3
4
5
6
7
8
9
<span class='line'>→ ruby extconf.rb
</span><span class='line'>creating Makefile
</span><span class='line'>→ make
</span><span class='line'>gcc -I. <span class="o">[</span>output snipped<span class="o">]</span>
</span><span class='line'>→ irb
</span><span class='line'>&gt;&gt; <span class="nv">$:</span>&lt;&lt;<span class="s1">&#39;.&#39;</span> <span class="c">#=&gt; [..., &#39;.&#39;]</span>
</span><span class='line'>&gt;&gt; require <span class="s1">&#39;person&#39;</span> <span class="c">#=&gt; true</span>
</span><span class='line'>&gt;&gt; <span class="nv">p</span> <span class="o">=</span> Person.new<span class="o">(</span>3000<span class="o">)</span> <span class="c">#=&gt; #&lt;Person:0x00000100a01940 @balance=3000&gt;</span>
</span><span class='line'>&gt;&gt; p.show_balance <span class="c">#=&gt; 3000</span>
</span>

As you can see everything works as if we had defined the class in Ruby. Let’s add another method to deposit some money, which gives us a chance to demonstrate “rb_funcall”:

1
2
3
4
5
6

1
2
3
4
5
6
<span class='line'><span class="k">static</span> <span class="n">VALUE</span> <span class="nf">deposit</span><span class="p">(</span><span class="n">VALUE</span> <span class="n">self</span><span class="p">,</span> <span class="n">VALUE</span> <span class="n">amount</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="c1">// VALUE rb_funcall(VALUE recv, ID id, int argc, ... )</span>
</span><span class='line'>  <span class="n">VALUE</span> <span class="n">result</span> <span class="o">=</span> <span class="n">rb_funcall</span><span class="p">(</span><span class="n">rb_iv_get</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="s">&quot;@balance&quot;</span><span class="p">),</span> <span class="n">rb_intern</span><span class="p">(</span><span class="s">&quot;+&quot;</span><span class="p">),</span> <span class="mi">1</span><span class="p">,</span> <span class="n">amount</span><span class="p">);</span>
</span><span class='line'>  <span class="n">rb_iv_set</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="s">&quot;@balance&quot;</span><span class="p">,</span> <span class="n">result</span><span class="p">);</span>
</span><span class='line'>  <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span>

We also need to add this function as a method in “Init_person”:

1

1
<span class='line'> <span class="n">rb_define_method</span><span class="p">(</span><span class="n">cPerson</span><span class="p">,</span> <span class="s">&quot;deposit&quot;</span><span class="p">,</span> <span class="n">deposit</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
</span>

Does it work?

1

1
<span class='line'><span class="o">&gt;&gt;</span> <span class="nb">p</span><span class="o">.</span><span class="n">deposit</span><span class="p">(</span><span class="mi">400</span><span class="p">)</span> <span class="c1">#=&gt; 3400</span>
</span>

It does and we’ve successfully created a Ruby class from C. Probably not the type of class you’d really implement this way, but good enough as an example.

Recommended reading:

Comments

Copyright © 2016 - Michael Kohl - Powered by Octopress