Wenn Sie AIDL-Unterstützung suchen, sehen Sie sich auch FMQ mit AIDL an.
Die RPC-Infrastruktur (Remote Procedure Call) von HIDL verwendet Binder-Mechanismen, was bedeutet, dass Aufrufe Overhead verursachen, Kernel-Operationen erfordern und möglicherweise Scheduler-Aktionen auslösen. In Fällen, in denen Daten zwischen Prozessen mit geringerem Overhead und ohne Kernel-Beteiligung übertragen werden müssen, wird jedoch das Fast Message Queue (FMQ)-System verwendet.
FMQ erstellt Nachrichtenwarteschlangen mit den gewünschten Eigenschaften. Ein MQDescriptorSync
oder MQDescriptorUnsync
Objekt kann über einen HIDL-RPC-Aufruf gesendet und vom empfangenden Prozess für den Zugriff auf die Nachrichtenwarteschlange verwendet werden.
Fast Message Queues werden nur in C++ und auf Geräten mit Android 8.0 und höher unterstützt.
MessageQueue-Typen
Android unterstützt zwei Warteschlangentypen (bekannt als Flavors ):
- Nicht synchronisierte Warteschlangen dürfen überlaufen und können viele Leser haben; Jeder Leser muss die Daten rechtzeitig lesen, sonst gehen sie verloren.
- Synchronisierte Warteschlangen dürfen nicht überlaufen und können nur einen Leser haben.
Beide Warteschlangentypen dürfen keinen Unterlauf aufweisen (das Lesen aus einer leeren Warteschlange schlägt fehl) und können nur einen Schreiber haben.
Unsynchronisiert
Eine nicht synchronisierte Warteschlange hat nur einen Schreiber, kann aber eine beliebige Anzahl von Lesern haben. Es gibt eine Schreibposition für die Warteschlange; jedoch behält jeder Leser seine eigene unabhängige Leseposition im Auge.
Schreibvorgänge in die Warteschlange sind immer erfolgreich (werden nicht auf Überlauf überprüft), solange sie nicht größer als die konfigurierte Warteschlangenkapazität sind (Schreibvorgänge, die größer als die Warteschlangenkapazität sind, schlagen sofort fehl). Da jeder Leser möglicherweise eine andere Leseposition hat, können Daten aus der Warteschlange fallen, wenn neue Schreibvorgänge den Platz benötigen, anstatt darauf zu warten, dass jeder Leser alle Daten liest.
Die Leser sind dafür verantwortlich, Daten abzurufen, bevor sie am Ende der Warteschlange landen. Ein Lesevorgang, der versucht, mehr Daten zu lesen, als verfügbar sind, schlägt entweder sofort fehl (falls nicht blockierend) oder wartet, bis genügend Daten verfügbar sind (falls blockierend). Ein Lesevorgang, der versucht, mehr Daten als die Warteschlangenkapazität zu lesen, schlägt immer sofort fehl.
Wenn ein Lesegerät nicht mit dem Schreibgerät mithalten kann und die von diesem Lesegerät geschriebene und noch nicht gelesene Datenmenge größer ist als die Warteschlangenkapazität, werden beim nächsten Lesevorgang keine Daten zurückgegeben. Stattdessen wird die Leseposition des Lesegeräts auf die letzte Schreibposition zurückgesetzt und dann ein Fehler zurückgegeben. Wenn die zum Lesen verfügbaren Daten nach einem Überlauf, aber vor dem nächsten Lesevorgang überprüft werden, werden mehr zum Lesen verfügbare Daten als die Warteschlangenkapazität angezeigt, was darauf hinweist, dass ein Überlauf aufgetreten ist. (Wenn die Warteschlange zwischen der Überprüfung der verfügbaren Daten und dem Versuch, diese Daten zu lesen, überläuft, ist der einzige Hinweis auf einen Überlauf, dass der Lesevorgang fehlschlägt.)
Leser einer nicht synchronisierten Warteschlange möchten wahrscheinlich die Lese- und Schreibzeiger der Warteschlange nicht zurücksetzen. Daher sollten Leser beim Erstellen der Warteschlange aus dem Deskriptor ein „false“-Argument für den „resetPointers“-Parameter verwenden.
Synchronisiert
Eine synchronisierte Warteschlange verfügt über einen Schreiber und einen Leser mit einer einzelnen Schreibposition und einer einzelnen Leseposition. Es ist nicht möglich, mehr Daten zu schreiben, als in der Warteschlange Platz ist, oder mehr Daten zu lesen, als die Warteschlange derzeit enthält. Abhängig davon, ob die blockierende oder nicht blockierende Schreib- oder Lesefunktion aufgerufen wird, schlagen Versuche, den verfügbaren Speicherplatz oder die verfügbaren Daten zu überschreiten, entweder sofort fehl oder werden blockiert, bis der gewünschte Vorgang abgeschlossen werden kann. Versuche, mehr Daten zu lesen oder zu schreiben, als die Warteschlangenkapazität übersteigt, schlagen immer sofort fehl.
Einrichten eines FMQ
Für eine Nachrichtenwarteschlange sind mehrere MessageQueue
Objekte erforderlich: eines zum Schreiben und eines oder mehrere zum Lesen. Es gibt keine explizite Konfiguration, welches Objekt zum Schreiben oder Lesen verwendet wird; Es obliegt dem Benutzer, sicherzustellen, dass kein Objekt sowohl zum Lesen als auch zum Schreiben verwendet wird, dass es höchstens einen Schreiber und bei synchronisierten Warteschlangen höchstens einen Leser gibt.
Erstellen des ersten MessageQueue-Objekts
Eine Nachrichtenwarteschlange wird mit einem einzigen Aufruf erstellt und konfiguriert:
#include <fmq/MessageQueue.h> using android::hardware::kSynchronizedReadWrite; using android::hardware::kUnsynchronizedWrite; using android::hardware::MQDescriptorSync; using android::hardware::MQDescriptorUnsync; using android::hardware::MessageQueue; .... // For a synchronized non-blocking FMQ mFmqSynchronized = new (std::nothrow) MessageQueue<uint16_t, kSynchronizedReadWrite> (kNumElementsInQueue); // For an unsynchronized FMQ that supports blocking mFmqUnsynchronizedBlocking = new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite> (kNumElementsInQueue, true /* enable blocking operations */);
- Der
MessageQueue<T, flavor>(numElements)
-Initialisierer erstellt und initialisiert ein Objekt, das die Funktionalität der Nachrichtenwarteschlange unterstützt. - Der
MessageQueue<T, flavor>(numElements, configureEventFlagWord)
-Initialisierer erstellt und initialisiert ein Objekt, das die Nachrichtenwarteschlangenfunktionalität mit Blockierung unterstützt. -
flavor
kann entwederkSynchronizedReadWrite
für eine synchronisierte Warteschlange oderkUnsynchronizedWrite
für eine nicht synchronisierte Warteschlange sein. -
uint16_t
(in diesem Beispiel) kann ein beliebiger HIDL-definierter Typ sein, der keine verschachtelten Puffer (keinestring
odervec
-Typen), Handles oder Schnittstellen umfasst. -
kNumElementsInQueue
gibt die Größe der Warteschlange in Anzahl der Einträge an; Es bestimmt die Größe des gemeinsam genutzten Speicherpuffers, der der Warteschlange zugewiesen wird.
Erstellen des zweiten MessageQueue-Objekts
Die zweite Seite der Nachrichtenwarteschlange wird mithilfe eines MQDescriptor
Objekts erstellt, das von der ersten Seite erhalten wurde. Das MQDescriptor
Objekt wird über einen HIDL- oder AIDL-RPC-Aufruf an den Prozess gesendet, der das zweite Ende der Nachrichtenwarteschlange enthält. Der MQDescriptor
enthält Informationen über die Warteschlange, einschließlich:
- Informationen zum Zuordnen des Puffers und des Schreibzeigers.
- Informationen zum Zuordnen des Lesezeigers (wenn die Warteschlange synchronisiert ist).
- Informationen zum Zuordnen des Ereignisflagworts (wenn die Warteschlange blockiert).
- Objekttyp (
<T, flavor>
), der den HIDL-definierten Typ von Warteschlangenelementen und den Warteschlangentyp (synchronisiert oder nicht synchronisiert) umfasst.
Das MQDescriptor
Objekt kann zum Erstellen eines MessageQueue
Objekts verwendet werden:
MessageQueue<T, flavor>::MessageQueue(const MQDescriptor<T, flavor>& Desc, bool resetPointers)
Der Parameter resetPointers
gibt an, ob die Lese- und Schreibpositionen beim Erstellen dieses MessageQueue
Objekts auf 0 zurückgesetzt werden sollen. In einer nicht synchronisierten Warteschlange wird die Leseposition (die für jedes MessageQueue
Objekt in nicht synchronisierten Warteschlangen lokal ist) während der Erstellung immer auf 0 gesetzt. Normalerweise wird der MQDescriptor
während der Erstellung des ersten Nachrichtenwarteschlangenobjekts initialisiert. Für zusätzliche Kontrolle über den gemeinsam genutzten Speicher können Sie den MQDescriptor
manuell einrichten ( MQDescriptor
ist in system/libhidl/base/include/hidl/MQDescriptor.h
definiert) und dann jedes MessageQueue
Objekt wie in diesem Abschnitt beschrieben erstellen.
Blockierende Warteschlangen und Ereignisflags
Standardmäßig unterstützen Warteschlangen das Blockieren von Lese-/Schreibvorgängen nicht. Es gibt zwei Arten blockierender Lese-/Schreibaufrufe:
- Kurzform mit drei Parametern (Datenzeiger, Anzahl der Elemente, Timeout). Unterstützt das Blockieren einzelner Lese-/Schreibvorgänge in einer einzelnen Warteschlange. Bei Verwendung dieser Form verarbeitet die Warteschlange das Ereignisflag und die Bitmasken intern und das erste Nachrichtenwarteschlangenobjekt muss mit dem zweiten Parameter
true
initialisiert werden. Beispiel:// For an unsynchronized FMQ that supports blocking mFmqUnsynchronizedBlocking = new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite> (kNumElementsInQueue, true /* enable blocking operations */);
- Langform mit sechs Parametern (einschließlich Ereignisflag und Bitmasken). Unterstützt die Verwendung eines gemeinsam genutzten
EventFlag
Objekts zwischen mehreren Warteschlangen und ermöglicht die Angabe der zu verwendenden Benachrichtigungsbitmasken. In diesem Fall müssen das Ereignisflag und die Bitmasken bei jedem Lese- und Schreibaufruf bereitgestellt werden.
Für die Langform kann das EventFlag
explizit in jedem readBlocking()
und writeBlocking()
Aufruf angegeben werden. Eine der Warteschlangen kann mit einem internen Ereignisflag initialisiert werden, das dann mit getEventFlagWord()
aus den MessageQueue
Objekten dieser Warteschlange extrahiert und zum Erstellen EventFlag
Objekten in jedem Prozess zur Verwendung mit anderen FMQs verwendet werden muss. Alternativ können die EventFlag
Objekte mit jedem geeigneten Shared Memory initialisiert werden.
Im Allgemeinen sollte jede Warteschlange nur eine der Optionen Nicht-Blockierung, Kurzform-Blockierung oder Langform-Blockierung verwenden. Es ist kein Fehler, sie zu mischen, aber um das gewünschte Ergebnis zu erzielen, ist eine sorgfältige Programmierung erforderlich.
Markieren Sie den Speicher als schreibgeschützt
Standardmäßig verfügt der gemeinsam genutzte Speicher über Lese- und Schreibberechtigungen. Bei nicht synchronisierten Warteschlangen ( kUnsynchronizedWrite
) möchte der Autor möglicherweise die Schreibberechtigungen für alle Leser entfernen, bevor er die MQDescriptorUnsync
Objekte ausgibt. Dadurch wird sichergestellt, dass die anderen Prozesse nicht in die Warteschlange schreiben können. Dies wird zum Schutz vor Fehlern oder schlechtem Verhalten in den Leseprozessen empfohlen. Wenn der Autor möchte, dass die Leser die Warteschlange jedes Mal zurücksetzen können, wenn sie MQDescriptorUnsync
zum Erstellen der Leseseite der Warteschlange verwenden, kann der Speicher nicht als schreibgeschützt markiert werden. Dies ist das Standardverhalten des „MessageQueue“-Konstruktors. Wenn es also bereits Benutzer dieser Warteschlange gibt, muss deren Code geändert werden, um die Warteschlange mit resetPointer=false
zu erstellen.
- Autor: Rufen Sie
ashmem_set_prot_region
mit einemMQDescriptor
Dateideskriptor und einer schreibgeschützten Region (PROT_READ
) auf:int res = ashmem_set_prot_region(mqDesc->handle->data[0], PROT_READ)
- Leser: Nachrichtenwarteschlange mit
resetPointer=false
erstellen (der Standardwert isttrue
):mFmq = new (std::nothrow) MessageQueue(mqDesc, false);
Verwenden der MessageQueue
Die öffentliche API des MessageQueue
Objekts ist:
size_t availableToWrite() // Space available (number of elements). size_t availableToRead() // Number of elements available. size_t getQuantumSize() // Size of type T in bytes. size_t getQuantumCount() // Number of items of type T that fit in the FMQ. bool isValid() // Whether the FMQ is configured correctly. const MQDescriptor<T, flavor>* getDesc() // Return info to send to other process. bool write(const T* data) // Write one T to FMQ; true if successful. bool write(const T* data, size_t count) // Write count T's; no partial writes. bool read(T* data); // read one T from FMQ; true if successful. bool read(T* data, size_t count); // Read count T's; no partial reads. bool writeBlocking(const T* data, size_t count, int64_t timeOutNanos = 0); bool readBlocking(T* data, size_t count, int64_t timeOutNanos = 0); // Allows multiple queues to share a single event flag word std::atomic<uint32_t>* getEventFlagWord(); bool writeBlocking(const T* data, size_t count, uint32_t readNotification, uint32_t writeNotification, int64_t timeOutNanos = 0, android::hardware::EventFlag* evFlag = nullptr); // Blocking write operation for count Ts. bool readBlocking(T* data, size_t count, uint32_t readNotification, uint32_t writeNotification, int64_t timeOutNanos = 0, android::hardware::EventFlag* evFlag = nullptr) // Blocking read operation for count Ts; //APIs to allow zero copy read/write operations bool beginWrite(size_t nMessages, MemTransaction* memTx) const; bool commitWrite(size_t nMessages); bool beginRead(size_t nMessages, MemTransaction* memTx) const; bool commitRead(size_t nMessages);
availableToWrite()
und availableToRead()
kann ermittelt werden, wie viele Daten in einem einzigen Vorgang übertragen werden können. In einer nicht synchronisierten Warteschlange:
-
availableToWrite()
gibt immer die Kapazität der Warteschlange zurück. - Jeder Leser hat seine eigene Leseposition und führt seine eigene Berechnung für
availableToRead()
durch. - Aus der Sicht eines langsamen Lesers darf die Warteschlange überlaufen; Dies kann dazu führen, dass
availableToRead()
einen Wert zurückgibt, der größer als die Größe der Warteschlange ist. Der erste Lesevorgang nach einem Überlauf schlägt fehl und führt dazu, dass die Leseposition für diesen Leser auf den aktuellen Schreibzeiger gesetzt wird, unabhängig davon, ob der Überlauf überavailableToRead()
gemeldet wurde oder nicht.
Die Methoden read()
und write()
geben true
zurück, wenn alle angeforderten Daten in die/aus der Warteschlange übertragen werden konnten (und wurden). Diese Methoden blockieren nicht; Sie sind entweder erfolgreich (und geben true
zurück) oder geben sofort einen Fehler ( false
) zurück.
Die Methoden readBlocking()
und writeBlocking()
warten, bis der angeforderte Vorgang abgeschlossen werden kann oder bis eine Zeitüberschreitung auftritt (ein timeOutNanos
Wert von 0 bedeutet, dass es nie zu einer Zeitüberschreitung kommt).
Sperrvorgänge werden über ein Ereignismerkerwort realisiert. Standardmäßig erstellt und verwendet jede Warteschlange ihr eigenes Flag-Wort, um die Kurzform von readBlocking()
und writeBlocking()
zu unterstützen. Es ist möglich, dass mehrere Warteschlangen ein einzelnes Wort gemeinsam nutzen, sodass ein Prozess auf Schreib- oder Lesevorgänge in einer der Warteschlangen warten kann. Ein Zeiger auf das Ereignisflagwort einer Warteschlange kann durch Aufrufen getEventFlagWord()
abgerufen werden. Dieser Zeiger (oder ein beliebiger Zeiger auf einen geeigneten gemeinsam genutzten Speicherort) kann verwendet werden, um ein EventFlag
Objekt zu erstellen, das in die Langform von readBlocking()
und übergeben wird writeBlocking()
für eine andere Warteschlange. Die Parameter readNotification
und writeNotification
geben an, welche Bits im Ereignisflag verwendet werden sollen, um Lese- und Schreibvorgänge in dieser Warteschlange zu signalisieren. readNotification
und writeNotification
sind 32-Bit-Bitmasken.
readBlocking()
wartet auf die writeNotification
Bits; Wenn dieser Parameter 0 ist, schlägt der Aufruf immer fehl. Wenn der readNotification
Wert 0 ist, schlägt der Aufruf nicht fehl, aber ein erfolgreicher Lesevorgang setzt keine Benachrichtigungsbits. In einer synchronisierten Warteschlange würde dies bedeuten, dass der entsprechende writeBlocking()
Aufruf niemals aufwacht, es sei denn, das Bit ist an anderer Stelle gesetzt. In einer nicht synchronisierten Warteschlange wartet writeBlocking()
nicht (es sollte weiterhin zum Setzen des Schreibbenachrichtigungsbits verwendet werden) und es ist angemessen, dass bei Lesevorgängen keine Benachrichtigungsbits gesetzt werden. Ebenso schlägt writeblocking()
fehl, wenn readNotification
0 ist und ein erfolgreicher Schreibvorgang die angegebenen writeNotification
Bits setzt.
Um in mehreren Warteschlangen gleichzeitig zu warten, verwenden Sie die wait()
Methode eines EventFlag
Objekts, um auf eine Bitmaske von Benachrichtigungen zu warten. Die Methode wait()
gibt ein Statuswort mit den Bits zurück, die das Aufwecken verursacht haben. Diese Informationen werden dann verwendet, um zu überprüfen, ob in der entsprechenden Warteschlange genügend Speicherplatz oder Daten für den gewünschten Schreib-/Lesevorgang vorhanden sind, und um einen nicht blockierenden write()
/ read()
auszuführen. Um eine Benachrichtigung nach dem Vorgang zu erhalten, verwenden Sie einen weiteren Aufruf der wake()
Methode von EventFlag
. Eine Definition der EventFlag
Abstraktion finden Sie unter system/libfmq/include/fmq/EventFlag.h
.
Keine Kopiervorgänge
Die read
/ write
/ readBlocking
/ writeBlocking()
APIs nehmen einen Zeiger auf einen Eingabe-/Ausgabepuffer als Argument und verwenden memcpy()
Aufrufe intern, um Daten zwischen demselben und dem FMQ-Ringpuffer zu kopieren. Um die Leistung zu verbessern, enthalten Android 8.0 und höher eine Reihe von APIs, die einen direkten Zeigerzugriff auf den Ringpuffer ermöglichen, sodass keine memcpy
Aufrufe erforderlich sind.
Verwenden Sie die folgenden öffentlichen APIs für Zero-Copy-FMQ-Vorgänge:
bool beginWrite(size_t nMessages, MemTransaction* memTx) const; bool commitWrite(size_t nMessages); bool beginRead(size_t nMessages, MemTransaction* memTx) const; bool commitRead(size_t nMessages);
- Die
beginWrite
Methode stellt Basiszeiger in den FMQ-Ringpuffer bereit. Nachdem die Daten geschrieben wurden, schreiben Sie sie mitcommitWrite()
fest. Die MethodenbeginRead
/commitRead
verhalten sich auf die gleiche Weise. - Die Methoden
beginRead
/Write
nehmen als Eingabe die Anzahl der zu lesenden/schreibenden Nachrichten und geben einen booleschen Wert zurück, der angibt, ob das Lesen/Schreiben möglich ist. Wenn das Lesen oder Schreiben möglich ist, wird diememTx
Struktur mit Basiszeigern gefüllt, die für den direkten Zeigerzugriff auf den gemeinsam genutzten Ringpufferspeicher verwendet werden können. - Die
MemRegion
Struktur enthält Details zu einem Speicherblock, einschließlich des Basiszeigers (Basisadresse des Speicherblocks) und der Länge in Bezug aufT
(Länge des Speicherblocks in Bezug auf den HIDL-definierten Typ der Nachrichtenwarteschlange). - Die
MemTransaction
Struktur enthält zweiMemRegion
Strukturen,first
undsecond
, da ein Lese- oder Schreibvorgang in den Ringpuffer möglicherweise einen Umlauf bis zum Anfang der Warteschlange erfordert. Dies würde bedeuten, dass zum Lesen/Schreiben von Daten in den FMQ-Ringpuffer zwei Basiszeiger erforderlich sind.
So erhalten Sie die Basisadresse und -länge aus einer MemRegion
Struktur:
T* getAddress(); // gets the base address size_t getLength(); // gets the length of the memory region in terms of T size_t getLengthInBytes(); // gets the length of the memory region in bytes
So erhalten Sie Verweise auf die erste und zweite MemRegion
s innerhalb eines MemTransaction
Objekts:
const MemRegion& getFirstRegion(); // get a reference to the first MemRegion const MemRegion& getSecondRegion(); // get a reference to the second MemRegion
Beispiel für einen Schreibvorgang in den FMQ mit Zero-Copy-APIs:
MessageQueueSync::MemTransaction tx; if (mQueue->beginRead(dataLen, &tx)) { auto first = tx.getFirstRegion(); auto second = tx.getSecondRegion(); foo(first.getAddress(), first.getLength()); // method that performs the data write foo(second.getAddress(), second.getLength()); // method that performs the data write if(commitWrite(dataLen) == false) { // report error } } else { // report error }
Die folgenden Hilfsmethoden sind ebenfalls Teil von MemTransaction
:
-
T* getSlot(size_t idx);
Gibt einen Zeiger auf die Slot-idx
innerhalb derMemRegions
zurück, die Teil diesesMemTransaction
Objekts sind. Wenn dasMemTransaction
Objekt die Speicherbereiche zum Lesen/Schreiben von N Elementen vom Typ T darstellt, liegt der gültige Bereich vonidx
zwischen 0 und N-1. -
bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
Schreiben SienMessages
Elemente vom Typ T in die vom Objekt beschriebenen Speicherbereiche, beginnend mit dem IndexstartIdx
. Diese Methode verwendetmemcpy()
und ist nicht für einen Zero-Copy-Vorgang gedacht. Wenn dasMemTransaction
Objekt Speicher zum Lesen/Schreiben von N Elementen vom Typ T darstellt, liegt der gültige Bereich vonidx
zwischen 0 und N-1. -
bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
Hilfsmethode zum LesennMessages
Elementen vom Typ T aus den Speicherbereichen, die vom Objekt beschrieben werden, beginnend mitstartIdx
. Diese Methode verwendetmemcpy()
und ist nicht für einen Zero-Copy-Vorgang gedacht.
Senden der Warteschlange über HIDL
Auf der erstellenden Seite:
- Erstellen Sie ein Nachrichtenwarteschlangenobjekt wie oben beschrieben.
- Überprüfen Sie mit
isValid()
ob das Objekt gültig ist. - Wenn Sie in mehreren Warteschlangen warten, indem Sie ein
EventFlag
in der Langform vonreadBlocking()
/writeBlocking()
übergeben, können Sie den Ereignis-Flag-Zeiger (mitgetEventFlagWord()
) aus einemMessageQueue
Objekt extrahieren, das zum Erstellen des Flags initialisiert wurde. und verwenden Sie dieses Flag, um das erforderlicheEventFlag
Objekt zu erstellen. - Verwenden Sie die
MessageQueue
MethodegetDesc()
um ein Deskriptorobjekt abzurufen. - Geben Sie der Methode in der
.hal
Datei einen Parameter vom Typfmq_sync
oder fmq_unsync
wobei T
ein geeigneter HIDL-definierter Typ ist. Verwenden Sie dies, um das vongetDesc()
zurückgegebene Objekt an den empfangenden Prozess zu senden.
Auf der Empfangsseite:
- Verwenden Sie das Deskriptorobjekt, um ein
MessageQueue
Objekt zu erstellen. Stellen Sie sicher, dass Sie dieselbe Warteschlangenvariante und denselben Datentyp verwenden, da die Vorlage sonst nicht kompiliert werden kann. - Wenn Sie ein Ereignisflag extrahiert haben, extrahieren Sie das Flag aus dem entsprechenden
MessageQueue
Objekt im Empfangsprozess. - Verwenden Sie das
MessageQueue
Objekt, um Daten zu übertragen.