mars
2011
Introduction
J’utilise régulièrement ruby-taglib. Malheureusement, certains bugs empêchent son utilisation avec ruby 1.9. Après avoir tenté, avec plus ou moins de succès, de corriger ces bugs, j’ai décidé de réécrire cette bibliothèque. La version originale est basée sur ruby/DL, bibliothèque que je n’ai jamais utilisée. Je profite de cette réécriture pour proposer une série d’articles sur la manière d’adapter une bibliothèque C.
Préparation
Je développe pour l’instant sous ubuntu 10.04 (je terminerai sûrement ces articles sous la version 10.10). J’ai installé taglib, ruby et rdoc soit les paquets debian suivant : libtagc0, libtagc0-dev, ruby, ruby-dev, rdoc.
Hiérarchie
La commande tree présente l’arborescence de mon répertoire de travail.
.
├── extconf.rb
├── lib
│ └── taglib2.rb
└── taglib2.c
Le fichier extconf.rb, écrit en ruby, permettra de préparer les sources en vue de compiler la bibliothèque. Le fichier lib/taglib2.rb, lui aussi écrit en ruby, est le fichier lu par l’interpréteur ruby lors de l’appel à require « taglib2″ qui chargera notre future bibliothèque.Le fichier taglib2.c, écrit en C, contiendra les méthodes qui permettent d’interfacer taglib.
Ruby
Dans un premier temps, j’utiliserai seulement le langage ruby. J’écris donc les premières lignes de code dans le fichier lib/taglib2.rb.
module TagLib
VERSION=[0, 0, 1]
end
Une constante nommée VERSION est définie comme un tableau contenant 3 entiers. D’ores et déjà, je peux tester mon code grâce à irb, un interpréteur ruby en ligne de commande. La bibliothèque n’étant pas encore installée, j’utilise un chemin relatif pour y accéder.
> require "./lib/taglib2"
=> true
> TagLib::VERSION
=> [0, 0, 1]
Interface ruby/C
Exceptions
Nous nous intéressons enfin au fichier taglib2.c. Nous définissons, tout d’abord, les différents types d’exceptions qui peuvent être levés par la bibliothèque.
#include <ruby.h>
#include <taglib/tag_c.h>
VALUE mTagLib;
VALUE eBadPath, eBadFile, eBadTag, eBadAudioProperties;
void
Init_taglib2()
{
mTagLib=rb_define_module("TagLib");
eBadPath=rb_define_class_under(mTagLib, "BadPath", rb_eException);
eBadFile=rb_define_class_under(mTagLib, "BadFile", rb_eException);
eBadTag=rb_define_class_under(mTagLib, "BadTag", rb_eException);
eBadAudioProperties=rb_define_class_under(mTagLib, "BadAudioProperties", rb_eException);
}
Les fichiers nécessaires au développement de la bibliothèque sont tout d’abord inclus puis nous déclarons des variables de type VALUE. Ce type est utilisé pour toute variable représentant un objet du langage ruby. Les noms de variables représentant des exceptions sont généralement préfixés par la lettre e. Pour les modules, la lettre m est utilisée, pour les classes, la lettre c.
Il s’agit d’une convention et non d’une règle. Finalement, on retrouve la fonction Init_taglib2 qui (re)définit le module TagLib ainsi que 4 classes descendant de la classe ruby Exception représentée en C par la variable rb_eException.
Le code suivant, écrit en ruby, est équivalent à la définition donnée en C de l’exception TagLib::BadPath
class BadPath < Exception
end
end
Compilation
Afin de compiler notre bibliothèque, nous devons créer un fichier Makefile. La bibliothèque mkmf nous facilite le travail.
1 require 'mkmf'
2
3 have_header('ruby.h') || exit 1
4 have_header('taglib/tag_c.h') || exit(1)
5
6 create_makefile("taglib2")
$ ruby extconf.rb
checking for ruby.h... yes
checking for taglib/tag_c.h... yes
creating Makefile
Dans le fichier extconf.rb, la présence des fichiers « header » de ruby et de taglib sont vérifiées ligne 3 et 4. La ligne 6 permet de créer le fichier Makefile du projet taglib2. Lors du chargement d’une interface ruby/C, la fontion appelée est de la forme Init_NomDuProjet,c’est pourquoi l’unique fonction du fichier taglib2.c s’appelle Init_taglib2. Il s’agit de l’équivalent de la fonction main d’un programme C traditionnel.
Après avoir créer le fichier Makefile, compilons.
$ make
Un bibliothèque dynamique taglib2.so vient de faire son apparition. Ruby permet de charger directement ce type de bibliothèque. Nous ajoutons donc l’instruction (require « taglib2.so ») de chargement dans le fichier lib/taglib2.rb.
require "taglib2.so"
module TagLib
VERSION=[0, 0, 1]
end
Test
Nous testons notre code sous irb.
> $:.unshift('.')
> $:.unshift('lib')
> require "taglib2"
=> true
> TagLib.constants
=> ["BadFile", "BadPath", "BadAudioProperties", "VERSION", "BadTag"]
> TagLib::BadPath.new
=> #<TagLib::BadPath: TagLib::BadPath>
J’ajoute tout d’abord le répertoire courant et le répertoire lib dans les chemins vérifiés lors du chargement d’un fichier afin que les appels à require « taglib2″ et à require « taglib2.so » n’échouent pas. Cette astuce ne sera plus nécessaire une fois la bibliothèque installée. On vérifie que les constantes définies dans les fichiers sources lib/taglib2.rb et taglib2.c sont accessibles.
Conclusion
Lors de ce premier billet, j’ai mis en place les différents éléments (bibliothèques, fichiers) qui seront nécessaires à l’interface ruby de taglib. Nous avons, déjà, réussi à obtenir un code fonctionnel. Il nous reste à porter les fonctions proposées par taglib afin de pourvoir accéder, en ruby, aux tags de fichiers musicaux.
À la prochaine.
Je ne suis pas un spécialiste du langage C, si vous avez des suggestions à faire sur mon code
commentez ce billet.
Addendum
Je rajoute la liste des billets faisant suite à celui-ci :
Je ne me suis pas encore penché sur le cas window…
Super mais cela serait interessant d’avoir aussi la même bib pour une version windows Ruby ….
Correction d’un oubli.