Use C program from Ruby

FFI Abbreviation for Foreign function Interface, which is a function for using functions written in another programming language. In Ruby, the gem ffi and the standard library Fiddle packed in Ruby provide the same functionality.

This time, I would like to use the C program from Ruby using the provided ffi which is a gem.

Pass by value

Let's get used to ffi first. The calling code is below.

lib.c


int add(int a, int b) {
    return a + b;
}

I have defined ʻadd` which takes two arguments of type int and returns the result of their addition.

In order to call this add from Ruby, it needs to be converted to the shared library (* .so) format. Use the following command to convert.

$ gcc -shared lib.c -o libadd.so

If you pass the -shared option to the gcc compiler, it will spit out the object file as a shared library. The file to call from Ruby is libadd.so.

add.rb


require 'ffi'

module AddFFI
  extend FFI::Library
  ffi_lib 'libadd.so'

  attach_function :add, [:int, :int], :int
end

puts AddFFI.add(1, 2)

Load the shared library you just generated with the ffi_lib method.

Then, with ʻattach_function, map the function defined in the C program so that it can be handled from Ruby. Give the method name to the first argument, the argument type of the function defined inC to the second argument, and thereturn value type` to the third argument. The method name must be the same as the function defined in the C program.

Run this Ruby file.

$ ruby add.rb
3

I was able to successfully call the C program via FFI.

Pass using a pointer

Passing by reference is a mechanism to share the value of a variable by passing the memory address of the variable. The value is not copied, so if you rewrite the information about that variable in the reference destination, the value of the originally held variable will also change.

[Addition] ~~ Passing by reference is a mechanism to share the value of a variable by passing the memory address of the variable. The value is not copied, so if you rewrite the information about that variable in the reference destination, the value of the originally held variable will also change. ~~ The author's understanding of passing by reference was incorrect, and @shiracamus and @ c-yan pointed out. For the definition of the word pass by reference, please refer to the comments section of this article for the guidance from the two of you. (Thank you for pointing out!)

Example

sansyo.c


#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    int *p, *q;
    p = (int *)malloc(sizeof(int));
    *p = 1;
    printf("%d\n", *p);

    //Pass by reference here
    // [Postscript]This is not passed by reference, but by value of reference(See comments for details)
    q = p;

    *q = 2;
    printf("%d\n", *p);
}

If you do this, you'll see that the value of * p has changed after being assigned to * q.

$ gcc -o sansyo sansyo.c
$ ./sansyo
1
2

What I want to do is to define a function that returns an address value (pointer) in a C program, and get the value from Ruby by referring to that address.

The created C program is as follows.

add_sansyo.c


#include <stdio.h>
#include <stdlib.h>

int *calc(int a, int b) {
    int *p;
    p = (int *)malloc(sizeof(int));
    *p = a + b;
    printf("sansyo function address = %p\n", p);
    return p;
}

int main(int argc, char *argv[]) {
    int *q = calc(1, 2);
    printf("calc(1, 2)              = %d\n", *q);
    printf("calc(1, 2)'s address    = %p\n", q);
}

I call calc in main for debugging. Since I want to return the address value to Ruby, calc returns an int type pointer.

The execution result is as follows. It seems to be working as intended.

$ gcc -o add_sansyo add_sansyo.c
$ ./add_sansyo
sansyo function address = 0x7f94d54026b0
calc(1, 2)              = 3
calc(1, 2)'s address    = 0x7f94d54026b0

Convert this C program to a shared library as ʻadd_sansyo.so` and Ruby will load it.

sansyo.rb


require 'ffi'

module AddFFI
  extend FFI::Library
  ffi_lib "add_sansyo.so"

  attach_function :sansyo, [:int, :int], :pointer
end

result = AddFFI.sansyo(1, 2)
puts result
puts result.read(:int)

ffi's Repository and rubydoc I am referring to it.

For the mapping performed by ʻattach_function, the name is also Zubari, and the return value is specified in the form of: pointer. ʻThe return value of the AddFFI.sansyo method is the FFI :: Pointer class, so the instance method called read is used to get the value of that address.

When executed, it will be as follows, and you can understand that the output result of the function can be obtained as intended by touching the address referenced in the C program from Ruby.

$ ruby sansyo.rb
sansyo function address = 0x7fa1aad31860
#<FFI::Pointer address=0x00007fa1aad31860>
3

Summary

Using ffi of Rubygems, I was able to call a function written in a C program from Ruby using a value or a pointer. I personally like the idea of optimizing a program by combining language features.

Recommended Posts

Use C program from Ruby
Use Face API from Ruby
[Flutter] How to use C / C ++ from Dart?
Ruby payroll program
Ruby score calculation program
From Java to Ruby !!
Use TensorFlow from JRuby
Ruby Numeric Reference Program
[Ruby] Leap year judgment program
Try using Cocoa from Ruby
Use ruby variables in javascript.
Ruby C extension and volatile
Rbenv command to use Ruby
Ruby: How to use cookies
[Ruby] Receive input from console
CHATBOT (Dialogflow) used from Ruby
Use Ruby with Google Colab
I tried to generate a C language program source from cURL
Ruby: I made a FizzBuzz program!
Write Ruby methods using C (Part 1)
How to use Ruby on Rails
Let's use Amazon Textract with Ruby
[Ruby] Escape from multiple loops [Nest]
[Ruby] How to use any? Method
From introduction to use of ActiveHash
Use Chrome Headless from Selenium / Java
Introduction to Ruby (from other languages)
Use database user-defined functions from JPQL
How to use Ruby inject method
Call a C function from Swift
[Ruby / Refactoring] From Ruby iterative processing like Java and C language to Ruby-like iterative processing