このページでは

C

カスタムキューの実装

このトピックでは、Qt Quick Ultralite にカスタムキューを実装する方法を説明します。

概要

Qt Quick Ultraliteでイベントを処理する際、キューは重要な役割を果たします。その最も顕著な例の1つは、入力処理におけるキューの使用です。EventQueue を使用することで、Qt Quick Ultralite にイベントを伝搬させ、Qt Quick Ultralite の実行サイクル中の適切なタイミングで処理されるようにすることができます。

デフォルトのキューの実装

EventQueue のデフォルトのキュー実装は、platform\common\baremetal\doublequeue.cpp にあるDoubleQueue です。これは、書き込み者が1人である限り割り込み安全です。複数のライタが存在する場合、EventQueue::postEvent() は、別のEventQueue::postEvent() によって割り込まれてはならない。さらに、DoubleQueue でのスレッドセーフは保証されないため、使用も制限される。

std::thread およびstd::mutex をサポートする組み込みプラットフォームでは、デフォルトのキューはミューテックスを使用することでスレッドセーフとなっている。

デフォルトのキュー実装を使用するには、platform\common\baremetal\doublequeue.cpp をプロジェクトに追加してください。

注意: デフォルトのキューを使用したい場合や、OS が OS 固有のキューを提供していない場合は、この実装をスキップしてすぐに次のトピックに進むことができます:タッチ入力の処理

OSキューの実装

ほとんどのOSは、割り込み安全性やスレッド安全性といった付加的な利点を提供する独自のキューを提供しています。また、複数のタスクを使用するアプリケーションで必要とされる、適切なタスク間通信を可能にします。OSのキューを使用することで、DoubleQueue の制限により通常は使用できないような場合でも、EventQueue を使用できるようになります。

このトピックでは、例えばオペレーティング・システム が提供するキューを使用するためにMessageQueueInterface APIEventQueue Qt Quick Ultraliteのプラットフォーム実装では、カスタムキューを使用することができます。

カスタムキューの適応

カスタムキューを実装するために使用されるキューの抽象化はMessageQueueInterface API と呼ばれます。ヘッダを含めることでアクセスできます。 platform\messagequeue.hヘッダを含めることでアクセスできます。このAPIは、抽象関数とデフォルトの実装を持つ関数を提供します。これらの関数は再実装する必要があります。詳しくはMessageQueueInterface クラスのドキュメントを参照のこと。

注: ここに示すコード・スニペットは、Qt Quick Ultraliteインストール・ディレクトリ(platform\boards\qt\example-baremetal\examplequeue.cpp)にある実装例からのものです。デモのために、キューのバックエンドとして単純な循環バッファを使用しています。

動作する実装を得るためには、MessageQueueInterface API の以下の関数を実装する必要があります:

  • コンストラクタ - 実装のコンストラクタは、キューが保持できるアイテムの最大数を表す整数を少なくとも取らなければならない。また、MessageQueueInterface::MessageQueueInterface() を呼び出さなければなりません。

    以下は、実装のコンストラクタの例です:

    MyMessageQueue(const uint32_t &capacity, const uint32_t &messageSize)
        : MessageQueueInterface()
        , mQueue(NULL)
        , mOverrunFlag(false)
    {
        void *memory = qul_malloc(sizeof(Private::CircularBuffer));
        mQueue = new (memory) Private::CircularBuffer(capacity, messageSize);
    }
  • MessageQueueInterface::discardSupported() とMessageQueueInterface::overwriteSupported() - これらの関数は、キューの実装が破棄、上書き、またはその両方をサポートしているかどうかを示します。これらの関数は、true またはfalse のいずれかを返さなければならない。

    注意: EventQueue が動作するためには、これらの関数のどちらかがtrue を返さなければなりません。

  • MessageQueueInterface::enqueueOrDiscard() - この関数は、与えられたメッセージをキューの最後尾にプッシュします。キューが満杯の場合、メッセージは破棄され、オーバーラン状態が設定されなければならない。メッセージがキューにプッシュされた場合はMessageQueueStatus::Success を返し、メッセージが破棄された場合はMessageQueueStatus::MessageDiscarded を返さなければならない。

    注: message の引数の内容は、オリジナルが削除されるかもしれないので、キューにコピーされなければならない。

    実装が破棄をサポートしていない場合、この関数はMessageQueueStatus::DiscardingNotSupported を返さなければならない。

    以下は、この関数の実装例です:

    MessageQueueStatus enqueueOrDiscard(const void *message) QUL_DECL_OVERRIDE
    {
        if (mQueue->isFull()) {
            // Discard message
            mOverrunFlag = true;
            return MessageQueueStatus::MessageDiscarded;
        }
    
        mQueue->pushBack(message);
        return MessageQueueStatus::Success;
    }
  • MessageQueueInterface::enqueueOrOverwrite() - この関数は、与えられたメッセージをキューの最後にプッシュします。キューが満杯の場合、キュー内の最も古いメッセージを与えられたメッセージで上書きし、オーバーラン状態を設定しなければなりません。この関数は、メッセージが正常にプッシュされた場合はMessageQueueStatus::Success を返し、古いメッセージが与えられたメッセージで上書きされた場合はMessageQueueStatus::MessageOverwritten を返さなければなりません。

    注意: enqueueOrDiscard()と同様、オリジナルが削除される可能性があるため、message の内容はキューにコピーされるべきである。

    実装が上書きをサポートしていない場合、この関数は決して呼び出されないはずですが、MessageQueueStatus::OverwritingNotSupported を返さなければなりません。

    EventQueue は、イベント・タイプがポインタの場合、上書きをサポートしません。この機能が必要な場合、実装は上書きされるポインタに対して適切なメモリ処理を行わなければなりません。

    以下は、上書きがサポートされていない実装におけるMessageQueueInterface::enqueueOrOverwrite() の実装例である:

    MessageQueueStatus enqueueOrOverwrite(const void *message) QUL_DECL_OVERRIDE
    {
        return MessageQueueStatus::OverwriteNotSupported;
    }
  • MessageQueueInterface::receive() - 指定されたタイムアウト内にキューからメッセージを取り出し、それを返す。タイムアウトはミリ秒単位で指定する。タイムアウトの値が 0 の場合、関数はまったく待ちません。一方、タイムアウトの値が負 の場合、関数はメッセージを無期限に待ちます。

    キューからのメッセージの取り出しに成功した場合、message 引数には取り出したメッセー ジが格納されていなければならず(つまり、取り出したメッセージの内容はmessage が指すアドレスにコピーされていなければなりません)、関数はMessageQueueStatus::Success を返さなければなりません。キューが空であった場合、代わりにMessageQueueStatus::EmptyQueueMessageQueueStatus::Timeout を返さなければならない。

    以下はMessageQueueInterface::receive() の実装例である:

    MessageQueueStatus receive(void *message, int32_t timeout = 0) QUL_DECL_OVERRIDE
    {
        (void) timeout; // This example does not implement timeout handling.
    
        if (mQueue->isEmpty())
            return MessageQueueStatus::EmptyQueue;
    
        mQueue->popFront(message);
        return MessageQueueStatus::Success;
    }
  • MessageQueueInterface::isEmpty() - キューが空であればtrue を返し、そうでなければfalse を返す。
  • MessageQueueInterface::isOverrun() およびMessageQueueInterface::clearOverrun() - これらの関数は、キューのオーバーラン状態を返したり変更したりします。
    bool isOverrun() const QUL_DECL_OVERRIDE { return mOverrunFlag; }
    
    void clearOverrun() QUL_DECL_OVERRIDE { mOverrunFlag = false; }

メッセージのキューイングと受信の割り込みセーフバージョンを実装するための関数もあります。これらのメソッドには、Qt Quick Ultralite が提供する対応するメソッドを呼び出すデフォルトの実装があります。しかし、割り込み安全性を確保するために、以下の関数を再実装することを推奨します:

これで、カスタム・キューを使用したMessageQueueInterface API の実装が動作するはずである。EventQueue は、MessageQueue の便利なAPIを使用して、キューの実装とインターフェイスする。しかし、MessageQueue は、MessageQueueInterface を実装しているという以外には、カスタム実装について何も知らない。その代わりに、MessageQueue は、requestQueue() 関数を呼び出して、使用する適切なキューを取得します。この関数はcapacitymessageSize の引数を取り、それぞれキューが保持できるアイテムの数と、キューが使用するメッセージのサイズを示します。この関数は、キュー実装のインスタンスへのポインタを返すか、実装が要求された容量やメッセージサイズをサポートしていない場合はNULLポインタを返さなければなりません。

requestQueue() の実装例:

MessageQueueInterface *requestQueue(size_t queueCapacity, size_t messageSize)
{
    void *queue = qul_malloc(sizeof(MyMessageQueue));

    if (queue == NULL) {
        return NULL;
    }

    MessageQueueInterface *interface = new (queue) MyMessageQueue(queueCapacity, messageSize);
    return interface;
}

キューが不要になった場合、MessageQueue は、キュー資源の削除と割り当て解除を処理するdeleteQueue() を呼び出します。

deleteQueue() の実装例:

void deleteQueue(MessageQueueInterface *queue)
{
    MyMessageQueue *mq = static_cast<MyMessageQueue *>(queue);
    mq->~MyMessageQueue();
    qul_free(mq);
}

MessageQueue の実装例: 与えられたメッセージ・サイズが実装のサイズ制限を超えた場合に対処できるように、許容される最大メッセージ・サイズを知っておく必要があります。この情報はmaximumQueueMessageSize() を実装することで提供できる。この関数は、サポートされる最大サイズをバイト単位で返さなければなりません。また、キューが任意のメッセージサイズをサポートしている場合は、SIZE_MAXSIZE_MAX が返された場合、実装は、そのサイズに関係なく、メッセージを処理する責任を負います。

以下の例では、maximumQueueMessageSize() を実装し、SIZE_MAX を返している。

size_t maximumQueueMessageSize()
{
    return LONG_MAX;
}


詳細はこちらをご覧ください。