Viadeo Twitter Google Bookmarks ! Facebook Digg del.icio.us MySpace Yahoo MyWeb Blinklist Netvouz Reddit Simpy StumbleUpon Bookmarks Windows Live Favorites 
Logo Documentation Qt ·  Page d'accueil  ·  Toutes les classes  ·  Toutes les fonctions  ·  Vues d'ensemble  · 

Custom Script Class Example

Files:

The Custom Script Class example shows how to use QScriptClass and QScriptClassPropertyIterator to implement a custom script class.

The script class we are going to implement is called ByteArray. It provides a wrapper around the QByteArray class in Qt, with a simplified API. Why do we need such a class? Well, neither the ECMAScript Array class or String class is appropriate to use when working with arrays of bytes. Our ByteArray class will have the right semantics; objects will use only the amount of memory that is really needed (a byte is stored as a byte, not as a floating-point number or a Unicode character) and can be passed directly to C++ slots taking QByteArray arguments (no costly conversion necessary).

ByteArray Class In Use

When the ByteArray class has been made available to the scripting environment, ByteArray objects can be constructed like so:

 var ba = new ByteArray();    // constructs an empty ByteArray
 var ba2 = new ByteArray(10); // constructs a ByteArray of length 10 (all bytes initialized to 0)

ByteArray objects behave similar to normal Array objects. Every ByteArray object has a length property, that holds the length of the array. If a new value is assigned to the length property, the array is resized. If the array is enlarged, the new bytes are initialized to 0. (This is a difference from normal Array objects; ByteArray objects are always dense arrays.) Use normal array operations to read or write bytes in the array. The following code sets all the bytes of an array to a certain value:

 for (var i = 0; i < ba.length; ++i)
     ba[i] = 123;

When assigning a value to an array element, the value is truncated to eight bits:

 ba[0] = 257;
 print(ba[0]);  // 1

Like normal Array objects, if the array index is greater than the current length of the array, the array is resized accordingly:

 var ba3 = new ByteArray();
 print(ba3.length); // 0
 ba[0] = 64;
 print(ba3.length); // 1

Property names that aren't valid array indexes are treated like normal object properties (again, the same is the case for normal Array objects); in other words, it's perfectly fine to do something like this:

 ba["foo"] = "Hello";

The above assignment won't affect the contents of the array, but will rather assign a value to the object property named "foo".

ByteArray objects have a set of methods: chop(), equals(), left(), mid(), toBase64() and so on. These map directly onto the corresponding methods in QByteArray.

 var ba64 = ba.toBase64();
 print(ba64.toLatin1String());

ByteArray Class Implementation

To implement the ByteArray script class in C++, we create a subclass of QScriptClass, called ByteArrayClass, and reimplement the virtual functions from QScriptClass. We also provide a Qt Script constructor function suitable for being added to a QScriptEngine's environment.

The ByteArrayClass constructor prepares the script class:

 ByteArrayClass::ByteArrayClass(QScriptEngine *engine)
     : QObject(engine), QScriptClass(engine)
 {
     qScriptRegisterMetaType<QByteArray>(engine, toScriptValue, fromScriptValue);

     length = engine->toStringHandle(QLatin1String("length"));

     proto = engine->newQObject(new ByteArrayPrototype(this),
                                QScriptEngine::QtOwnership,
                                QScriptEngine::SkipMethodsInEnumeration
                                | QScriptEngine::ExcludeSuperClassMethods
                                | QScriptEngine::ExcludeSuperClassProperties);
     QScriptValue global = engine->globalObject();
     proto.setPrototype(global.property("Object").property("prototype"));

     ctor = engine->newFunction(construct, proto);
     ctor.setData(qScriptValueFromValue(engine, this));
 }

First, the constructor registers a pair of conversion functions, so that C++ QByteArray objects and Qt Script ByteArray objects can move seamlessly between the C++ side and the script side. For example, if a ByteArray object is passed to a C++ slot that takes a QByteArray argument, the actual QByteArray that the ByteArray object wraps will be passed correctly.

Second, we store a handle to the string "length", so that we can quickly compare a given property name to "length" later on.

Third, we initialize the standard ByteArray prototype, to be returned by our prototype() reimplementation later on. (The implementation of the prototype is discussed later.)

Fourth, we initialize a constructor function for ByteArray, to be returned by the constructor() function. We set the internal data of the constructor to be a pointer to this ByteArrayClass object, so that the constructor, when it is invoked, can extract the pointer and use it to create a new ByteArray object.

 QScriptValue ByteArrayClass::newInstance(int size)
 {
     engine()->reportAdditionalMemoryCost(size);
     return newInstance(QByteArray(size, /*ch=*/0));
 }
 QScriptValue ByteArrayClass::newInstance(const QByteArray &ba)
 {
     QScriptValue data = engine()->newVariant(qVariantFromValue(ba));
     return engine()->newObject(this, data);
 }

The newInstance() function isn't part of the QScriptClass API; its purpose is to offer a convenient way to construct a ByteArray object from an existing QByteArray. We store the QByteArray as the internal data of the new object, and return the new object. QScriptEngine::newObject() will call the prototype() function of our class, ensuring that the prototype of the new object will be the standard ByteArray prototype.

QScriptEngine::reportAdditionalMemoryCost() is called to inform the script engine of the memory occupied by the QByteArray. This gives the garbage collector a hint that it should perhaps trigger more frequently, possibly freeing up memory associated with large ByteArray objects that are no longer in use.

 QScriptValue ByteArrayClass::construct(QScriptContext *ctx, QScriptEngine *)
 {
     ByteArrayClass *cls = qscriptvalue_cast<ByteArrayClass*>(ctx->callee().data());
     if (!cls)
         return QScriptValue();
     QScriptValue arg = ctx->argument(0);
     if (arg.instanceOf(ctx->callee()))
         return cls->newInstance(qscriptvalue_cast<QByteArray>(arg));
     int size = arg.toInt32();
     return cls->newInstance(size);
 }

construct() is the native function that will act as a constructor for ByteArray in scripts. We extract the pointer to the class, then call a newInstance() overload that takes an initial size as argument, and return the new script object.

 QScriptClass::QueryFlags ByteArrayClass::queryProperty(const QScriptValue &object,
                                                        const QScriptString &name,
                                                        QueryFlags flags, uint *id)
 {
     QByteArray *ba = qscriptvalue_cast<QByteArray*>(object.data());
     if (!ba)
         return 0;
     if (name == length) {
         return flags;
     } else {
         bool isArrayIndex;
         qint32 pos = name.toArrayIndex(&isArrayIndex);
         if (!isArrayIndex)
             return 0;
         *id = pos;
         if ((flags & HandlesReadAccess) && (pos >= ba->size()))
             flags &= ~HandlesReadAccess;
         return flags;
     }
 }

queryProperty() is the function that Qt Script will call whenever someone tries to access a property of a ByteArray object. We first get a pointer to the underlying QByteArray. We check if the property being accessed is the special length property; if so, we return, indicating that we will handle every kind of access to this property (e.g. both read and write). Otherwise, we attempt to convert the property name to an array index. If this fails, we return, indicating that we don't want to handle this property. Otherwise, we have a valid array index, and store it in the id argument, so that we don't have to recompute it in e.g. property() or setProperty(). If the index is greater than or equal to the QByteArray's size, we indicate that we don't want to handle read access (but we still want to handle writes, if requested).

 QScriptValue ByteArrayClass::property(const QScriptValue &object,
                                       const QScriptString &name, uint id)
 {
     QByteArray *ba = qscriptvalue_cast<QByteArray*>(object.data());
     if (!ba)
         return QScriptValue();
     if (name == length) {
         return ba->length();
     } else {
         qint32 pos = id;
         if ((pos < 0) || (pos >= ba->size()))
             return QScriptValue();
         return uint(ba->at(pos)) & 255;
     }
     return QScriptValue();
 }

In the property() reimplementation, we do similar checks as in queryProperty() to find out which property is being requested, and then return the value of that property.

 void ByteArrayClass::setProperty(QScriptValue &object,
                                  const QScriptString &name,
                                  uint id, const QScriptValue &value)
 {
     QByteArray *ba = qscriptvalue_cast<QByteArray*>(object.data());
     if (!ba)
         return;
     if (name == length) {
         resize(*ba, value.toInt32());
     } else {
         qint32 pos = id;
         if (pos < 0)
             return;
         if (ba->size() <= pos)
             resize(*ba, pos + 1);
         (*ba)[pos] = char(value.toInt32());
     }
 }

The setProperty() reimplementation has a structure that is similar to property(). If the length property is being set, we resize the underlying QByteArray to the given length. Otherwise, we grab the array index that was calculated in the queryProperty() function, enlarge the array if necessary, and write the given value to the array.

 void ByteArrayClass::resize(QByteArray &ba, int newSize)
 {
     int oldSize = ba.size();
     ba.resize(newSize);
     if (newSize > oldSize)
         engine()->reportAdditionalMemoryCost(newSize - oldSize);
 }

The resize() function is a helper function that resizes the QByteArray to a new size, and, if the new size is greater than the old, reports the additional memory cost to the script engine.

 QScriptValue::PropertyFlags ByteArrayClass::propertyFlags(
     const QScriptValue &/*object*/, const QScriptString &name, uint /*id*/)
 {
     if (name == length) {
         return QScriptValue::Undeletable
             | QScriptValue::SkipInEnumeration;
     }
     return QScriptValue::Undeletable;
 }

The propertyFlags() reimplementation specifies that the length property can't be deleted, and that it is not enumerable. Array elements can't be deleted.

 QScriptClassPropertyIterator *ByteArrayClass::newIterator(const QScriptValue &object)
 {
     return new ByteArrayClassPropertyIterator(object);
 }

We want the array elements to show up when a ByteArray object is used in for-in statements and together with QScriptValueIterator. Therefore, we reimplement the newIterator() function and have it return a new iterator for a given ByteArray.

ByteArray Iterator Implementation

 bool ByteArrayClassPropertyIterator::hasNext() const
 {
     QByteArray *ba = qscriptvalue_cast<QByteArray*>(object().data());
     return m_index < ba->size();
 }

 void ByteArrayClassPropertyIterator::next()
 {
     m_last = m_index;
     ++m_index;
 }

 bool ByteArrayClassPropertyIterator::hasPrevious() const
 {
     return (m_index > 0);
 }

 void ByteArrayClassPropertyIterator::previous()
 {
     --m_index;
     m_last = m_index;
 }

 void ByteArrayClassPropertyIterator::toFront()
 {
     m_index = 0;
     m_last = -1;
 }

 void ByteArrayClassPropertyIterator::toBack()
 {
     QByteArray *ba = qscriptvalue_cast<QByteArray*>(object().data());
     m_index = ba->size();
     m_last = -1;
 }

 QScriptString ByteArrayClassPropertyIterator::name() const
 {
     return object().engine()->toStringHandle(QString::number(m_last));
 }

 uint ByteArrayClassPropertyIterator::id() const
 {
     return m_last;
 }

The ByteArrayClassPropertyIterator class is simple. It maintains an index into the underlying QByteArray, and checks and updates the index in hasNext(), next() and so on.

ByteArray Prototype Implementation

The prototype class, ByteArrayPrototype, implements the ByteArray functions as slots.

 class ByteArrayPrototype : public QObject, public QScriptable
 {
 Q_OBJECT
 public:
     ByteArrayPrototype(QObject *parent = 0);
     ~ByteArrayPrototype();

 public slots:
     void chop(int n);
     bool equals(const QByteArray &other);
     QByteArray left(int len) const;
     QByteArray mid(int pos, int len = -1) const;
     QScriptValue remove(int pos, int len);
     QByteArray right(int len) const;
     QByteArray simplified() const;
     QByteArray toBase64() const;
     QByteArray toLower() const;
     QByteArray toUpper() const;
     QByteArray trimmed() const;
     void truncate(int pos);
     QString toLatin1String() const;
     QScriptValue valueOf() const;

 private:
     QByteArray *thisByteArray() const;
 };

There is a small helper function, thisByteArray(), that returns a pointer to the QByteArray being operated upon:

 QByteArray *ByteArrayPrototype::thisByteArray() const
 {
     return qscriptvalue_cast<QByteArray*>(thisObject().data());
 }

The slots simply forward the calls to the QByteArray. Examples:

 QByteArray ByteArrayPrototype::mid(int pos, int len) const
 {
     return thisByteArray()->mid(pos, len);
 }

 QScriptValue ByteArrayPrototype::remove(int pos, int len)
 {
     thisByteArray()->remove(pos, len);
     return thisObject();
 }

The remove() function is noteworthy; if we look at QByteArray::remove(), we see that it should return a reference to the QByteArray itself (i.e. not a copy). To get the same behavior in scripts, we return the script object (thisObject()).

Publicité

Best Of

Actualités les plus lues

Semaine
Mois
Année
  1. « Quelque chose ne va vraiment pas avec les développeurs "modernes" », un développeur à "l'ancienne" critique la multiplication des bibliothèques 93
  2. Apercevoir la troisième dimension ou l'utilisation multithreadée d'OpenGL dans Qt, un article des Qt Quarterly traduit par Guillaume Belz 0
  3. Les développeurs ignorent-ils trop les failles découvertes dans leur code ? Prenez-vous en compte les remarques des autres ? 17
  4. Pourquoi les programmeurs sont-ils moins payés que les gestionnaires de programmes ? Manquent-ils de pouvoir de négociation ? 40
  5. Quelles nouveautés de C++11 Visual C++ doit-il rapidement intégrer ? Donnez-nous votre avis 10
  6. Adieu qmake, bienvenue qbs : Qt Building Suite, un outil déclaratif et extensible pour la compilation de projets Qt 17
  7. 2017 : un quinquennat pour une nouvelle version du C++ ? Possible, selon Herb Sutter 6
Page suivante

Le Qt Labs au hasard

Logo

Génération de contenu dans des threads

Les Qt Labs sont les laboratoires des développeurs de Qt, où ils peuvent partager des impressions sur le framework, son utilisation, ce que pourrait être son futur. Lire l'article.

Communauté

Ressources

Liens utiles

Contact

  • Vous souhaitez rejoindre la rédaction ou proposer un tutoriel, une traduction, une question... ? Postez dans le forum Contribuez ou contactez-nous par MP ou par email (voir en bas de page).

Qt dans le magazine

Cette page est une traduction d'une page de la documentation de Qt, écrite par Nokia Corporation and/or its subsidiary(-ies). Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia. Qt 4.7
Copyright © 2012 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon, vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.
Vous avez déniché une erreur ? Un bug ? Une redirection cassée ? Ou tout autre problème, quel qu'il soit ? Ou bien vous désirez participer à ce projet de traduction ? N'hésitez pas à nous contacter ou par MP !
 
 
 
 
Partenaires

Hébergement Web