lundi 19 janvier 2009

Victoire! Je me suis débarrassé de cet ignoble objet global et j'ai en même temps appris l'existence du type v8::External, qui est un enrobage autour d'un void *.

J'ai tout d'abord commencé par définir un typedef pour éviter une verbosité excessive:


typedef TestCollection<std::string, int> Collection;

Commençons d'abord par le main:

int main()
{
// La collection
Collection testCollection;
v8::HandleScope handleScope;
// On créé l'objet global
v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
// On créé la fonction print
global->Set("print", v8::FunctionTemplate::New(print));
// On créé le template de la collection
v8::Local<v8::ObjectTemplate> collectionTemplate = v8::ObjectTemplate::New();
// On créé les deux méthodes du template
collectionTemplate->Set("getObjectById", v8::FunctionTemplate::New(getObjectById));
collectionTemplate->Set("setObject", v8::FunctionTemplate::New(setObject));
// On informe le template qu'il aura un champ interne
// (si on ne le fait pas, on aura un débordement de tableau plus tard)
collectionTemplate->SetInternalFieldCount(1);
// On créé le contexte
v8::Handle<v8::Context> context = v8::Context::New(0, global);
// On créé le script
JSScript script("script.js", context);
// On instancie la collection à partir du template
v8::Local<v8::Object> collection = collectionTemplate->NewInstance();
// On assigne un pointeur sur testCollection à la collection pour qu'on puisse y accéder plus tard
collection->SetInternalField(0, v8::External::New(&testCollection));
// On assigne la collection comme appartenant à l'objet global
context->Global()->Set(v8::String::New("collection"), collection);
// On exécute le script
script.run();
}

On remarque d'abord l'objet global et la fonction print(). Puis, le template de la collection. Or, ça s'arrête là. J'ai en effet réalisé que l'on n'était pas obligé d'utiliser le template comme objet. On pourrait voir un template comme une classe: on le créé, on définit des méthodes, puis on instancie à l'aide de NewInstance().
Ah, NewInstance(). Cette méthode m'a donnée du fil à retordre. Originellement, elle causait une erreur de segmentation, jusqu'à ce que je réalise qu'on devait obligatoirement créer un Context et un ContextScope avant. Un objet ne peut en effet exister que dans un contexte: on ne peut créer un objet "dans le vide".
Une fois l'objet instancié à l'aide de NewInstance(), on peut lui assigner divers méthodes ou attributs à l'aide de Set, ou des données internes (similaires en utilisation à des attributs privés) à l'aide de ce qu'on appelle un "internal field". Un champ interne est un attribut qui ne sera pas accessible par le script. On peut donc s'en servir pour stocker diverses informations. Ici, je m'en sert pour stocker un External, qui contiendra un pointeur sur la collection. Finalement, on déclare collection comme un objet appartenant à l'objet global. Remarquez la syntaxe différente: on assigne pas des objets comme on assigne des templates. L'effet est le même, par contre.

Mais comment utiliser ce pointeur? Voici la fonction setObject():

v8::Handle<v8::Value> setObject(const v8::Arguments &args)
{
if(args.Length() == 2)
{
// args.Holder() retourne l'objet possédant la fonction: dans ce cas précis, l'objet de collection
// On va donc chercher le External dans l'objet à l'aide de GetInternalField()
// L'appel à la méthode Cast est nécessaire, car GetInternalField() retourne un Object
// alors que nous voulons un External
v8::Local<v8::External> external = v8::Local<v8::External>::Cast(args.Holder()->GetInternalField(0));
// Puis, on prend le pointeur contenu dans le External.
// Celui-ci sera un void *, alors un cast est nécessaire
Collection * collection = static_cast<Collection *>(external->Value());
// Le reste est identique
v8::HandleScope handleScope;
v8::String::Utf8Value key(args[0]);
v8::Local<v8::Value> value(args[1]);
collection->setObject(*key, value->Int32Value());
}
return v8::Undefined();
}

getObjectById() est similaire. L'important ce situe dans les premières lignes du if: le reste est inchangé (ou presque: on utilise maintenant le pointeur sur l'objet).

Voilà!

Prochaine étape: faire en sorte que getObjectById() retourne des objets JS complexes plutôt que des entiers. Cela me permettra d'expérimenter les accesseurs.

Comme toujours, j'attends vos commentaires.

Ah, oui, j'en profite pour laisser un lien sur des benchmarks des divers moteurs JavaScript sur le marché:
JavaScript Performance Rundown.
Comme vous pouvez le voir, IE ne joue pas dans la même ligue que le reste.

0 commentaires:

Enregistrer un commentaire