Utiliser le programme C de Ruby

FFI Abréviation de Foreign function Interface, qui est une fonction permettant d'utiliser des fonctions écrites dans un autre langage de programmation. Dans Ruby, le gem ffi et la bibliothèque standard Fiddle emballée dans Ruby fournissent des fonctionnalités équivalentes.

Cette fois, j'aimerais utiliser le programme C de Ruby en utilisant le ffi fourni qui est un joyau.

Passer par valeur

Habituons-nous d'abord à ffi. Le code d'appel est ci-dessous.

lib.c


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

Nous avons défini ʻadd` qui prend deux arguments de type int et renvoie le résultat de leur addition.

Pour appeler cet ajout depuis Ruby, il doit être converti au format de bibliothèque partagée (* .so). Utilisez la commande suivante pour convertir.

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

Si vous passez l'option -shared au compilateur gcc, il crachera le fichier objet en tant que bibliothèque partagée. Le fichier à appeler depuis Ruby est 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)

Chargez la bibliothèque partagée que vous venez de générer avec la méthode ffi_lib.

Ensuite, avec ʻattach_function, mappez la fonction définie dans le programme C afin qu'elle puisse être manipulée depuis Ruby. Donnez le nom de la méthode au premier argument, le type d'argument de la fonction définie dans C au deuxième argument et le type de valeur de retour` au troisième argument. Le nom de la méthode doit être le même que la fonction définie dans le programme C.

Exécutez ce fichier Ruby.

$ ruby add.rb
3

J'ai pu appeler avec succès le programme C via FFI.

Passer à l'aide d'un pointeur

Le passage par référence est un mécanisme de partage de la valeur d'une variable en passant l'adresse mémoire de la variable. La valeur n'est pas copiée, donc si vous réécrivez les informations sur cette variable à la destination de référence, la valeur de la variable conservée d'origine changera également.

[Une addition] ~~ Le passage par référence est un mécanisme pour partager la valeur d'une variable en passant l'adresse mémoire de la variable. La valeur n'est pas copiée, donc si vous réécrivez les informations sur cette variable à la destination de référence, la valeur de la variable conservée d'origine changera également. ~~ La compréhension de l'auteur du passage par référence était incorrecte, et @shiracamus et @ c-yan l'ont souligné. Pour la définition du mot passer par référence, veuillez vous référer à la section commentaires de cet article pour les conseils de vous deux. (Merci de l'avoir signalé!)

Exemple

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

    //Passer par référence ici
    // [Postscript]Ceci n'est pas passé par référence, mais par valeur de référence(Voir les commentaires pour plus de détails)
    q = p;

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

Si vous faites cela, vous verrez que la valeur de * p a changé après l'attribution de * q.

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

Ce que je veux faire, c'est définir une fonction qui renvoie une valeur d'adresse (pointeur) dans le programme C et obtenir la valeur de Ruby en faisant référence à cette adresse.

Le programme C créé est le suivant.

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

Pour le débogage, «calc» est appelé dans «main». Je veux renvoyer la valeur d'adresse à Ruby, donc calc renvoie un pointeur de type int.

Le résultat de l'exécution est le suivant. Cela semble fonctionner comme prévu.

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

Convertissez ce programme C en une bibliothèque partagée en tant que ʻadd_sansyo.so` et Ruby le chargera.

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)

[Dépôt] de ffi (https://github.com/ffi/ffi/wiki/Pointers) et rubydoc J'y fais allusion.

Pour le mappage effectué par ʻattach_function, le nom est également Zubari et la valeur de retour est spécifiée sous la forme: pointer. ʻLa valeur de retour de la méthode AddFFI.sansyo est la classe FFI :: Pointer, donc la méthode d'instance appelée read est utilisée pour obtenir la valeur de cette adresse.

Une fois exécuté, ce sera comme suit, et vous pouvez comprendre que le résultat de sortie de la fonction peut être obtenu comme prévu en touchant l'adresse référencée dans le programme C de Ruby.

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

Résumé

En utilisant ffi de Rubygems, j'ai pu appeler une fonction écrite dans un programme C de Ruby en utilisant une valeur ou un pointeur. J'aime personnellement l'idée d'optimiser un programme en combinant des fonctionnalités linguistiques.

Recommended Posts

Utiliser le programme C de Ruby
[Flutter] Comment utiliser C / C ++ depuis Dart?
Programme de calcul des salaires Ruby
Programme de calcul du score Ruby
De Java à Ruby !!
Utilisez TensorFlow de JRuby
Programme de référence numérique Ruby
Essayez d'utiliser Cocoa de Ruby
Utilisez des variables ruby en javascript.
Extension Ruby C et volatile
Commande Rbenv pour utiliser Ruby
Ruby: Comment utiliser les cookies
[Ruby] Recevoir les entrées de la console
CHATBOT (Dialogflow) utilisé depuis Ruby
Utilisez Ruby avec Google Colab
J'ai essayé de générer une source de programme en langage C à partir de cURL
Écrire des méthodes Ruby en utilisant C (Partie 1)
Comment utiliser Ruby on Rails
[Ruby] Échapper à plusieurs boucles [Nest]
Utiliser Chrome Headless de Selenium / Java
Introduction à Ruby (à partir d'autres langues)
Utiliser les fonctions définies par l'utilisateur de la base de données de JPQL
Comment utiliser la méthode Ruby inject
Appeler les fonctions du langage C depuis Swift
[Ruby / Refactoring] Du traitement itératif Ruby tel que Java et C au traitement itératif de type Ruby