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:

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

Commençons d'abord par le main:
  1. int main()  
  2. {  
  3.     // La collection  
  4.     Collection testCollection;  
  5.     v8::HandleScope handleScope;  
  6.     // On créé l'objet global  
  7.     v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();  
  8.     // On créé la fonction print  
  9.     global->Set("print", v8::FunctionTemplate::New(print));  
  10.     // On créé le template de la collection  
  11.     v8::Local<v8::ObjectTemplate> collectionTemplate = v8::ObjectTemplate::New();  
  12.     // On créé les deux méthodes du template  
  13.     collectionTemplate->Set("getObjectById", v8::FunctionTemplate::New(getObjectById));  
  14.     collectionTemplate->Set("setObject", v8::FunctionTemplate::New(setObject));  
  15.     // On informe le template qu'il aura un champ interne   
  16.     // (si on ne le fait pas, on aura un débordement de tableau plus tard)  
  17.     collectionTemplate->SetInternalFieldCount(1);  
  18.     // On créé le contexte  
  19.     v8::Handle<v8::Context> context = v8::Context::New(0, global);   
  20.     // On créé le script  
  21.     JSScript script("script.js", context);   
  22.     // On instancie la collection à partir du template  
  23.     v8::Local<v8::Object> collection = collectionTemplate->NewInstance();   
  24.     // On assigne un pointeur sur testCollection à la collection pour qu'on puisse y accéder plus tard  
  25.     collection->SetInternalField(0, v8::External::New(&testCollection));        
  26.     // On assigne la collection comme appartenant à l'objet global  
  27.     context->Global()->Set(v8::String::New("collection"), collection);  
  28.     // On exécute le script  
  29.     script.run();  
  30. }  

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

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