octobre
2010
Cet article fait suite au premier. Il s’intéresse à la classe principale qui permettra de manipuler les tags de fichiers audio.
Addendum
Afin que notre bibliothèque soit correctement liée à taglib lors de la compilation, il faut ajouter la ligne have_library(‘tag_c’) || exit(1) au fichier extconf.rb, juste avant d’invoquer la création du Makefile.
Créer un objet appartenant à la classe TagLib::File
Ajouter une classe File
Après avoir déclaré une nouvelle variable de type VALUE (cFile), je définis une nouvelle classe nommée File appartenant au module TagLib. Elle hérite de la classe Objet (classe mère de toutes les classes en ruby).
#include <ruby.h>
#include <taglib/tag_c.h>
VALUE mTagLib;
VALUE eBadPath, eBadFile, eBadTag, eBadAudioProperties;
VALUE cFile;
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);
cFile=rb_define_class_under(mTagLib, "File", rb_cObject);
}
Après avoir recompilé notre bibliothèque, je peux créer un objet de type TagLib::File, cette classe ayant héritée de la méthode Objet.new.
$ ruby extconf.rb
$ make
$ irb
> require './lib/taglib2.rb'
> TagLib::File.superclass
=> Object
> TagLib::File.new
=> #<TagLib::File:0xb75145e8>
Redéfinir la méthode new
Dans la fonction Init_taglib2, je redéfinis la méthode de classe new (et non une méthode d’instance). C’est pourquoi j’utilise rb_define_singleton_method et non rb_define_method. Cette fonction prend quatre arguments : l’object pour lequel la méthode va ếtre (re)défini, le nom de la méthode, la fonction écrite en C à appeler, le nombre d’aguments passés à la fonction.
Voici la fonction file_new qui redéfinit TagLib::File.new.
{
TagLib_File *file;
TagLib_Tag *tag;
const TagLib_AudioProperties *audio;
} tgFileData;
VALUE
file_new(VALUE self, VALUE path)
{
TagLib_File *tgFile;
tgFileData *data;
VALUE rbData;
VALUE argv[1];
tgFile=taglib_file_new(StringValuePtr(path));
if (tgFile==NULL)
rb_raise(eBadPath, "Bad path");
data=ALLOC(tgFileData);
data->file=tgFile;
data->tag=NULL;
data->audio=NULL;
rbData=Data_Wrap_Struct(cFile, 0, free_tgFileData, data);
argv[0]=path;
rb_obj_call_init(rbData, 1, argv);
return rbData;
}
La méthode new est appelée avec un seul argument qui est une chaîne de caractère ruby représentant un chemin vers un fichier audio. Cependant, la function file_new possède un argument supplémentaire : le premier qui représente l’objet auquel s’applique la méthode.
La fonction taglib_file_new permet de créer une structure qui représente un fichier audio pour la bibliothèque taglib. Elle requière, comme argument, un pointeur sur une chaîne de caractère C obtenu grâce à la macro StringValuePtr.
La variable data est une structure qui représentera un objet de type TagLib::File au niveau du langage C. Elle est composée de trois champs représentant le fichier audio (file), le tag associé au fichier (tag) et les propriétés audio du fichier (audio). Après avoir alloué la mémoire nécéssaire (grâce à la macro ALLOC) et initialiser ses différents champs, cette structure est transformée en object ruby grâce à l’aide de la macro Data_Wrap_Struct. Cet object rbData appartient à la classe TagLib::File (représenté par la variable cFile). Lorsque l’objet est détruit, la fonction free_tgFileData est appelée.
Enfin, l’objet est initialisé (rb_obj_call_init).
Écrire la méthode initialize
La méthode new appelle la méthode initialize qui, comme son nom l’indique, s’occupe d’initialiser l’objet, en particulier ses variables d’instance.
Cette dernière méthode est déclarée dans la fonction Init_taglib2.
Ici, la méthode initialize se borne à valoriser la variable d’instance @path.
file_init(VALUE self, VALUE path)
{
rb_iv_set(self, "@path", path);
return self;
}
Libérer la mémoire
Lorsque le ramasse-miettes de ruby détruit un object de type TagLib::File, il utilise la fonction indiquée lors de l’appel à la macro Data_Wrap_Struct, pour libérer la mémoire allouée à cet objet.
free_tgFileData(tgFileData *d)
{
taglib_file_free(d->file);
free(d);
}
Code
Voici l’ensemble du code :
require 'mkmf'
have_header('ruby.h') || exit(1)
have_header('taglib/tag_c.h') || exit(1)
have_library('tag_c') || exit(1)
create_makefile("taglib2")
require "taglib2.so"
module TagLib
VERSION=[0, 0, 1]
end
#include <ruby.h>
#include <taglib/tag_c.h>
typedef struct
{
TagLib_File *file;
TagLib_Tag *tag;
const TagLib_AudioProperties *audio;
} tgFileData;
VALUE mTagLib;
VALUE eBadPath, eBadFile, eBadTag, eBadAudioProperties;
VALUE cFile;
static void
free_tgFileData(tgFileData *d)
{
taglib_file_free(d->file);
free(d);
}
VALUE
file_init(VALUE self, VALUE path)
{
rb_iv_set(self, "@path", path);
return self;
}
VALUE
file_new(VALUE self, VALUE path)
{
TagLib_File *tgFile;
tgFileData *data;
VALUE rbData;
VALUE argv[1];
tgFile=taglib_file_new(StringValuePtr(path));
if (tgFile==NULL)
rb_raise(eBadPath, "Bad path");
data=ALLOC(tgFileData);
data->file=tgFile;
data->tag=NULL;
data->audio=NULL;
rbData=Data_Wrap_Struct(cFile, 0, free_tgFileData, data);
argv[0]=path;
rb_obj_call_init(rbData, 1, argv);
return rbData;
}
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);
cFile=rb_define_class_under(mTagLib, "File", rb_cObject);
rb_define_singleton_method(cFile, "new", file_new, 1);
rb_define_method(cFile, "initialize", file_init, 1);
}
Tester le code
Après une recompilation, on vérifie que la méthode TagLib::File.new est correctement appelée depuis l’interpréteur irb.
$ irb
> require './lib/taglib2.rb'
=> true
> t=TagLib::File.new('Musique/01 - Blue Bird.mp3')
=> #<TagLib::File:0xb74c29a0>
> t.instance_variable_get("@path")
=> "Musique/01 - Blue Bird.mp3"
Conclusion
Lors de ce deuxième billet, nous avons interfacé une structure C avec un objet ruby, représentant un fichier audio. Dans le prochain billet, nous écrirons les méthodes qui permettent d’accéder au tag de ce fichier.