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.
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 the
return 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.
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!)
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
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