Pour pallier certain manques de ces langages il est possible de rendre scriptable une application, via une api vers un langage interprété.
Ici on s’intéresse au problème suivant: on veut permettre à l'utilisateur d'inclure certaines commande pour rendre dynamique un document texte qu'il éditerai depuis une application écrite en c++.
J'ai cherché diverses solutions et c'est finalement Ruby qui a retenus mon attention, notamment à cause d'erb, le langage de template intégré à Ruby.
Prenons un exemple simple, un petit code ruby utilisant erb pour imprimer un petit hello world:
#!/usr/bin/ruby require 'erb' my_template = "Hello World\nToday is <%# this is just a comment %> <%= MyModule.MyFunc()%>\n" module MyModule def self.MyFunc() Time.now.strftime('%A') end end my_erb = ERB.new(my_template) puts my_erb.result() |
Mais maintenant, addmettons que l'on veuille faire la même chose en c++, cad au moins pouvoir créer une fonction qui puisse employer les données de nos variables c++ pour les employer dans ruby.
Pour ce faire on va passer par l'API c de ruby, pour ceux qui ne connaissent pas un petit lien sur le tuto !
Le premier défis consiste à obtenir les fichiers de développement ruby, sous ubuntu 15.04 il suffit d'installer ruby-all-dev:
sudo apt-get install ruby-all-dev |
Si comme moi vous souhaitez aussi employer pkg-config il faut installer aussi ruby-pkg-config
sudo apt-get install ruby-pkg-config |
Voilà, nous sommes prêts à utiliser ruby à l'intérieur de c ou c++. Donc, ça donne quoi le script d'en dessus en c++ ?
#include <iostream> #include <ruby.h> #include <cstdio> #include <ctime> using namespace std; const string erb_line = "Hello World\nToday is <%# this is just a comment %><%= MyModule.MyFunc()%>\n"; VALUE MyFunc(int argc, VALUE* argv, VALUE self) { time_t rawtime; tm* timeinfo; char buffer [80]; time(&rawtime); timeinfo = localtime(&rawtime); strftime(buffer,80,"%A",timeinfo); string timeString(buffer); return rb_str_new2(timeString.c_str()); } int main(int argc, char ** argv){ /* construct the VM */ ruby_init(); /* Ruby goes here */ ruby_init_loadpath(); rb_require("erb"); // Define a new module VALUE mymodule = rb_define_module("MyModule"); //add a function to the module rb_define_module_function(mymodule, "MyFunc", (VALUE (*)(...)) MyFunc, -1); //get a reference to ERB ID sym_erbclass = rb_intern("ERB"); VALUE erbclass = rb_const_get(rb_cObject, sym_erbclass); //list of arguments to initialise erb (1 arg) VALUE erbargv[1]; erbargv[0] = rb_str_new2(erb_line.c_str()); VALUE myerb = rb_class_new_instance(1, erbargv, erbclass); //prepare the result VALUE resultargv[0]; VALUE result = rb_funcallv(myerb, rb_intern("result"), 0, resultargv); cout << string(RSTRING_PTR(result), RSTRING_LEN(result)); /* destruct the VM */ return ruby_cleanup(0); } |
Un peu plus long n'est-ce pas ? Mais ce n'est pas tant sorcier que cela, c'est juste pénible à chercher dans la doc quand on est dans un cas de figure que les gens rencontrent apparement peu et donc ne documentent guère plus.
Il existe néanmoins des subtilité induite par c++, qui est par exemple plus fortement typé que c, ce qui explique qu'il faille caster de manière judicieuse le pointeur d'une fonction c++ que l'on veut enregistrer dans ruby.
La compilation ne pose pas de problème majeur, le plus dur et de localiser les headers dont dépend <ruby.h> pour les inclures dans le path (ci dessous la ligne que j'ai employé sur ubuntu 15.04):
g++ --std=c++11 main.cpp -o essai -I/usr/include/ruby-2.1.0 -I/usr/include/x86_64-linux-gnu/ruby-2.1.0/ -lruby-2.1 |
Vous me direz, tant d'efforts pour pas grand chose ? Je vous rapelle que le but était de pouvoir exploiter les données contenues dans une application c++, hors vous remarquerez que MyFunc peut tout à fait servir d'accesseur ! Ainsi nous disposons de toutes les cartes pour créer des applications exploitant erb.
La prochaine fois je vous montrerai comment cross-compiler une telle application pour Windows depuis Linux !
Aucun commentaire:
Enregistrer un commentaire