|
Application Kit dostarcza systemu przekazywania komunikatów, który pozwala Twojej aplikacji wysyłać i odbierać komunikaty z innych aplikacji (włącznie z serwerami aplikacjami zdefiniowanymi przez Be) i z innych wątków w Twojej własnej aplikacji.Najważniejszymi klasami komunikacyjnymi są:
- BMessage reprezentuje komunikat.
- BLooper uruchamia pętlę, która odbiera nadchodzące komunikaty i rozpracowuje który BHandler powinien je obsłużyć.
- BHandler określa funkcje przechwytujące, które są wywoływane do obsługiwania nadchodzących komunikatów.
- BMessenger reprezentuje punkt przeznaczenia komunikatu (połączenie BLooper'a i BHandler'a) i to czy on jest lokalny czy zdalny. Obiekt ten jest najprzydatniejszy do wysyłania komunikatów do innych aplikacji - nie potrzebujesz go do wywołań lokalnych.
Innymi klasami komunikacyjnymi są:
- BMessageQueue jest kolejką FIFO, która przechowuje komunikaty nadchodzące do BLooper'a.
- BMessageFilter jest urządzeniem, któe może sprawdzać i (potencjalnie) odrzucać lub przekierowywać nadchodzące komunikaty.
- BInvoker jest wygodną klasą, która pozwala Ci traktować komunikat i jego adresata (BHandler, który będzie obsługiwał ten komunikat) jak pojedynczy obiekt.
- BMessageRunner pozwala Ci wysyłać ten sam komunikat wielokrotnie w regularnych odstępach.
Reszta tego rozdziału spogląda na...
- Istotne cechy czterech klas podstawowych. ("Cechy klas podstawowych")
- Jak BLooper decyduje, który BHandler powinien obsłużyć nadchodzący komunikat. ("Od looper'a do handler'a")
- Różne metody wysyłania komunikatów i otrzymywania odpowiedzi. ("Wysyłanie komunikatu").
opisując jak klasy są dopasowane do siebie w systemie rozgłaszania komunikatów z uwypukleniem tego co możesz zrobić w swojej aplikacji aby wziąć w tym udział.
Łącznie patrząc, te cztery podstawowe klasy komunikacji obejmują ogromny fragment API. Na szczęście, podstawowa część tego API jest dość mała; właśnie na to będziemy tutaj spoglądali.
W klasie BMessage, jest jedna podstawowa dana członkowska i dwie podstawowe funkcje:
- Dana członkowska what jest arbitralną wartością uint32, która opisuje (symbolicznie) to czym jest komunikat. Możesz ustawiać i sprawdzać what bezpośrednio - nie musisz używać funkcji aby dostać się do niej. Wartość what jest nazywana stałą polecenia (command constant) obiektu. BeOS definiuje kilka liczb stałych poleceń (takich jak B_QUIT_REQUESTED i B_MOUSE_DOWN) ale Ty również będziesz tworzył swoje własne stałe. Pamiętaj, że wartość stałej jest bez znaczenia - jest to tylko kod, który identyfikuje "zamiar" komunikatu (i tylko to jest znaczące jeżeli odbiorca rozpoznaje stałą).
- Tymi dwoma podstawowymi funkcjami są AddData() i FindData(). Te funkcje dodają dane do komunikatu posyłanego przez Ciebie i odbieranego odzyskują je z komunikatu przez Ciebie właśnie otrzymanego. BMessage może przechowywać wiele danych; każda pozycja danych (lub "pole") jest identyfikowane przez nazwę, typ i indeks. Dla przykładu, możesz zapytać komunikat o trzecią wartość boolowską nazwaną "IsEnabled", którą on zawiera. Ogólnie, używasz raczej funkcji ukierunkowanych na typ, takich jak Add/FindString() i Add/FindInt32() niż Add/FindData(). Pytanie które właśnie postawiliśmy właciwie wyglądałoby jak to:
/* Argumentami są: name, index, value (zwracane przez referencję) */
bool returnValue;
aMessage.FindBool("IsEnabled", 2, &returnValue);Podsumowując, BMessage zawiera (1) stałą polecenia i (2) zbiór pól danych. Każdy BMessage który jest używany w systemie komunikacji musi mieć stałą polecenia ale nie każdy obiekt potrzebuje mieć pola danych. (Inne części BeOS'a używają obiekty BMessage tylko dla ich danych. Obiekt BClipboard na przykład, ignoruje stałą polecenia BMessage'a.)
![]()
Kiedy omawiamy generowane przez system obiekty BMessage, odnosimy się do tych obiektów przez ich stałą polecenia. Przykładowo, "B_MOUSE_DOWN" znaczy "BMessage, który ma B_MOUSE_DOWN jako jego stałą polecenia". Zauważ, że BMessage nie wie jak wysłać się samemu. Jednak jak zobaczymy później, wie on jak odpowiedzieć swojemu nadawcy, że dotarł on do rąk odbiorcy.
Rolą BLooper'a jest jest otrzymywanie komunikatu i rozpracowanie tego, co z nim zrobić. Są cztery części tego zadania, ucieleśnionego w tych funkcjach:
- Każdy BLooper zapoczątkowuje wątek w którym uruchamia pętlę komunikatów. Jest tak w tym wątku, którego obiekt otrzymuje komunikaty. Ale musisz uruchomić BLooper'a aby zaczął on działać; robisz to przez wywołanie funkcji Run(). Kiedy już skończysz z tym obiektem - czyli kiedy go już dłużej nie potrzebujesz do odbierania komunikatów - wywołaj Quit().
- Chociaż nigdy nie wywołujesz jej bezpośrednio, DispatchMessage() to wnętrzności pętli komunikatów. Wszystkie komunikaty, które looper otrzymuje ukazują się w indywidualnych wywołaniach DispatchMessage(). Ta funkcja decyduje, gdzie następnie powinien pójść komunikat, która to przeważnie decyduje o kwestii, czy (1) komunikat powinien zostać obsłużony przez zdefiniowaną w systemie funkcję przechwytującą, czy (2) przekazany do funkcji MessageReceived() BHandler'a (o której będziemy mówili za chwilę). Trzema innymi ważnymi aspektami DispatchMessage() są...
- Działa on w watku komunikatu BLooper'a (lub pętli komunikatu); jest to oddzielny wątek któremu obiekt daje początek specjalnie do odbierania nadchodzacych komunikatów.
- Pojedyncze wywołania DispatchMessage() są synchroniczne w odniesieniu do pętli. Innymi słowy, kiedy komunikat się ukazuje, wywoływana jest funkcja DispatchMessage() i działa aż do zakończenia zanim może być przetworzony następny komunikat. (Komunikaty, które ukazują się podczas gdy DispatchMessage() jest zajęta nie są gubione - są one kolejkowane (zbierane) w obiekcie BMessageQueue.)
- Jest ona w pełni implementowana przez BLooper (i zestaw klas pochodnych od BLooper'a). Rzadko powinieneś potrzebować ją przeimplementować (override) w twojej aplikacji.
- Funkcja PostMessage() dostarcza komunikat do BLooper'a. Innymi słowy, wywołuje ona funkcję DispatchMessage() w wątku komunikatu looper'a. Wywołuj PostMessage() bezpośrednio w swoim kodzie. Na przykład, tworzymy tutaj BMessage i wysyłamy go do Twojego obiektu BApplication (BApplication dziedziczy z BLooper'a):
/* Ta forma konstruktora BMessage ustawia stałą polecenia. */
BMessage *myMessage = new BMessage(YOUR_CONSTANT_HERE);
be_app->PostMessage(myMessage)
Klasy BApplication i BWindow dziedziczą z BLooper'a.
Obiekty BHandler są wywoływane przy obsłudze komunikatów, które odbiera BLooper. BHandler zależy od dwóch zasadniczych funkcji:
- MessageReceived() jest funkcją która wywołuje BLooper'a do wysyłąnia nadchodzących komunikatów do BHandler'a (BMessage jest przekazywany tylko jako argument funkcji). Jest to funkcja przechwytująca, którą implementuje podklasa BHandler do obsługi różnych typów komunikatów, których ona oczekuje do odebioru. Większość implementacji sprawdza stałą polecenia komunikatu i pochodzi stamtąd. Typowy zarys wygląda tak jak ten:
void MyHandler::MessageReceived(BMessage *message)
{
/* Sprawdza stałą polecenia. */
switch ( message->what ) {
case YOUR_CONSTANT_HERE:
/* Wywołuje funkcję, która obsługuje to sortowanie komunikatów. */
HandlerFunctionA();
break;
case ANOTHER_CONSTANT_HERE:
/* jak wyżej */
HandlerFunctionB();
break;
default:
/* Komunikaty których Twoja klasa nie rozpoznaje muszą być
* przekazane do implementacji klasy podstawowej. */
baseClass::MessageReceived(message);
break;
}
}
- Inną niezbędną funkcją BHandler'a jest zdefiniowana przez BLooper: BLooper::AddHandler(). Ta funkcja dodaje obiekt (argument) BHandler do (invoked-upon) listy handlerów-kandydatów BLooper'a (jego łańcuch handlerów). Jeśli BHandler chce obsłużyć komunikaty, które są odebrane przez BLooper, musi on najpierw być dodany do łańcucha handlerów BLooper'a.
BLooper dziedziczy z BHandler, i automatycznie dodaje siebie do swojego własnego łańcucha handlerów. To znaczy, że BLooper może obsługiwać komunikaty które on odbiera - jest to domyślne zachowanie dla większości komunikatów. Będziemy badać to zagadnienie o wiele dalej w tym rozdziale.
Innymi klasami które dziedziczą z BHandler'a są BView i BShelf (obie w Interface Kit).
Najważniejszą cechą BMessenger'a jest to, że może on wysyłać komunikaty do aplikacji zdalnej. Aby to zrobić, podejmuje on dwa kroki, które wskazują najwazniejsze cechy klasy:
- Ty identyfikujesz aplikację do której chcesz wysłać komunikat ("adresata") w konstruktorze BMessenger'a. Applikacja jest identyfkowana przez jej sygnaturę aplikacji (łańcuch MIME).
- Funkcja SendMessage() wysyła komunikat do adresata.
BMessenger'y mogą również być użyte do adresowania lokalnej pary looper/handler.
BLooper pobiera komunikat ze swojej kolejki komunikatów i wewnątrz swojej DispatchMessage() funkcji wysyła komunikat przez wywołanie funkcji BHandler'a. Ale (1) który BHandler i (2) która funkcja?
Najpierw, odpowiedzmy na pytanie "który BHandler". Do określenia który BHandler powinien obsłużyć nadchodzący komunikat, BLooper postępuje według tych kroków:1. Czy adresat BMessage określa BHandler'a? Obie funkcje BLooper::PostMessage() i BMessenger::SendMessage() dostarczja dodatkowych argumentów, które pozwola CI określić adresata handler'a którego chcesz mieć do obsłużenia komunikatu (możesz również ustawić adresata w konstruktorze BMessenger'a). Jeśli BHandler jest określony, BMessage będzie się pojawiał funkcji MessageReceived() tego obiektu (lub będzie on wywoływał funkcję przechwytującą mapowania komunikatu, jak jest wyjaśnione poniżej).
2. Czy BLooper wyznaczył preferowanego handler'a? Poprzez swoją funkcję SetPreferredHandler(), BLooper może wyznaczyć jeden z obiektów w swoim łańcuchu handlerów jako swój preferowany handler i przekazać wszystkie komunikaty do tego obiektu.
3. BLooper obsługuje BMessage sam. Jeśli nie ma wyznaczonego docelowego handler'a lub nie jest wyznaczony preferowany handler, BLooper obsługuje komunikat sam - innymi słowy, komunikat jest przekazywany do BLooper'a własną funkcją MessageReceived() (lub funkcją mapowania komunikatu).
Powinniśmy wspomnieć tutaj, że obie klasy BApplication i BWindow dobrze zestawiają ten kawałek procesu decyzyjnego. Jednak wtrącają się one tylko stosowania komunikatów systemowych (opisanych poniżej). Komunikaty które definijesz sam (tj. stałe poleceń które są określone w Twojej aplikacji) będą zawsze śledzić komunikat wysyłany ścieżką opisaną tutaj.
![]()
Jeśli popatrzysz na protokół DispatchMessage(), zauważysz, że ma on BMessage i BHandler jako argumenty. Innymi słowy, opisywana tutaj decyzja, "który handler" jest w rzeczywistości wykonywana zanim zostanie wywołana funkcja DispatchMessage(). Ogólnie, jest to implementacja szczegółu którym nie musisz się martwić. Jeśli chcesz to myśl, że tą decyzję podejmuje DispatchMessage() - my nic nie zrobiliśmy, żeby wyprowadzić Cię z tego błędnego pojęcia - idź dalej i myśl tak.
Jak napisano powyższej, BLooper przekazuje BMessage do BHandler'a przez by wywołanie końcowej funkcji MessageReceived(). Jest to prawdą dla wszystkich komunikatów, które tworzysz sam, ale nie jest to prawdziwe dla pewnych komunikatów, które definiuje i wysyła system. Te wygenerowane przez system komunikaty (lub komunikaty systemowe) - szczególnie te, które zgłaszają zdarzenia użytkownika takie jak B_MOUSE_DOWN lub B_APP_ACTIVATED - wywołują określone funkcje przechwytujące.Dla przykłądu, gdy użytkownik naciśnie klawisz, komunikat B_KEY_DOWN jest wysyłany do aktywnego obiektu BWindow. Z wnętrza jego funkcji DispatchMessage(), BWindow wywołuje funkcję MouseDown() obiektu BView która aktualnie posiada ognisko klawiatury. (Kiedy BView uzyskał ognisko zdarzeń klawiatury, jego okno wyznacza go aby był preferowanym handler'em.)
Więc pytanie z "której funkcji" jest zupełnie proste: Jeżeli BMessage jest komunikatem systemowym, to jest mapowany do funkcji przechwytującej, wywoływana jest funkcja przechwytująca. Jeśli nie jest ona mapowana do przechwytującej, jest wywoływana funkcja MessageReceived() BHandler'a.
A full list of system messages and the hook functions that they're mapped to is given in the Dodatku Komunikatów Systemowych (System Messages Appendix). Zauważ, że nie wszystkie komunikaty systemowe są mapowane, by funkcje przechwytujące; część z nich pokazano w MessageReceived().
Popatrzmy na MessageReceived() znowu. Zostało to podkreślone w skrawku kodu pokazanym wcześniej, że typowa implementacjaMessageReceived() powinna zawierać wywołanie podstawowej wersji funkcji klasy:
void MyHandler::MessageReceived(BMessage *message)
{
switch ( message->what ) {
/* Stałe poleceń, które Ty obsługujesz przebiegają tutaj. */
default:
/* Komunikaty, których Twoja klasa nie rozpoznaje muszą być
* przekazane do implementacji klasy podstawowej. */
baseClass::MessageReceived(message);
break;
}
}To nie jest tylko dobry pomysł - to jest zasadnicza część systemu komunikowania się. Przesyłanie komunikatów do klasy podstawowej załatwia dwie rzeczy: pozwala ono (1) na przekazywanie komunikatów w górę hierarchii klas i (2) przekazywanie ich wzdłuż łańcucha handler'a (w tej kolejności).
Przekazywanie w górę hierarchii klas jest najczęściej bezpośrednie - nie jest ono inne dla funkcji MessageReceived() aniżeli dla jakiejś innej funkcji. Ale co zdarzyło się na szczycie hierarchii - w samej klasie BHandler - dodano małą zmarszczenie. Implementacja funkcji MessageReceived() BHandler'a szuka następnego handler'a w łańcuchu handler'a BLooper'a i wywołuje funkcję MessageReceived() tego obiektu.
Są dwie funkcje, które wysyłają komunikaty do różnych odbiorców:
- BLooper::PostMessage() może być użyta jeśli adresat (BLooper, na którym jest wywołana funkcja PostMessage()) przebywa w tej samej aplikacji co nadawca komunikatu.
- BMessenger::SendMessage() pozwala Ci wysłać komunikaty do zdalnych aplikacji. Obiekt BMessenger działa jako pośrednik dla aplikacji zdalnej. (SendMessage() może być również użyta do wysłania komunikatu do lokalnegoBLooper'a, z powodów o których będziemy dyskutować później.)
Możesz wysłać komunikat jeśli odbiorca BLooper'a jest w Twojej aplikacji:
myLooper->PostMessage(new BMessage(DO_SOMETHING), targetHandler); Jak pokazano tutaj, możesz określić handler który chcesz mieć do obsługiwania posyłanego komunikatu. Jedynym wymaganiem jest to, że BHandler musi należeć do BLooper'a.
Jeśli argument handler'a jest równy NULL, komunikat jest obsługiwany przez looper'a. preferowany handler
myLooper->PostMessage(new BMessage(DO_SOMETHING), NULL); Przez użycie domyślnego handler'a, pozwalasz looper'owi rozstrzygać o tym, kto powinien obsłużyć komunikat.
![]()
Twórca komunikatu BMessage zachowuje prawo własności i jest odpowiedzialny za usunięcie go jeśli nie jest on już dłużej potrzebny.
Jeśli chcesz wysłąć komunikat do kolejnej aplikacji, możesz użyć funkcji SendMessage() BMessenger'a. Najpierw konstruujesz obiekt BMessenger, który identyfikuje zdalną aplikację przez sygnaturę...
BMessenger messenger("application/x-some-app"); ...a następnie wywołujesz SendMessage():
messenger.SendMessage(new BMessage(DO_SOMETHING));
![]()
Twórca BMessage'a zachowuje prawo własności i jest odpowiedzialny za jego usunięcie gdy nie jest on już dłużej potrzebny.
Każdy BMessage , który wysyłasz identyfukuje aplikację z której był on wysłany. Odbiorca komunikatu może odpowiedzieć na komunikat Odbiorca komunikatu może odpowiedzieć na komunikat, czy Ty ( nadawca) oczekujesz na odpowiedź czy nie. Domyślnie, komunikaty odpowiedzi są obsługiwane przez Twój obiekt BApplication. Jeśli chcesz aby komunikaty odpowiedzi były obsługiwane przez jakiś inny BHandler, określ obiekt jako końcowy argument wywołania PostMesssage() lub SendMessage():
myLooper->PostMessage(new BMessage(DO_SOMETHING), targetHandler, replyHandler);
/* i */
myMessenger.SendMessage(&message, replyHandler);Odpowiedź jest wysyłana asynchronicznie w odniesieniu do funkcji PostMessage()/SendMessage().
SendMessage() (tylko) pozwala Ci zapytać się o komunikat odpowiedzi, który jest przekazywany spowrotem synchronicznie w swoim wywołaniu SendMessage():
BMessage reply;
myMessenger.SendMessage(&message, &reply);SendMessage() nie zwraca wartości dopóki nie otrzyma odpowiedzi. Jeśli odbiorca nie odpowie wystarczająco szybko, jest tworzony i zwracany komunikat domyślny.
Funkcja SendReply() BMessage'a ma taką sama składnię jak SendMessage(), więc jest możliwe pytanie o synchroniczną odpowiedź na komunikat, który sam jest odpowiedzią,
BMessage message(READY);
BMessage reply;
theMessage->SendReply(&message, &reply);
if ( reply->what != B_NO_REPLY ) {
. . .
}lub wyznaczenie BHandler'a dla odpowiedzi na odpowiedź:
theMessage->SendReply(&message, someHandler); W ten sposób, dwie aplikacje mogą utrzymywać toczącą się wymianę komunikatów.
Aby zostac powiadomionym o nadchodzącym komunikacie, BHandler musi "należeć" do BLooper; a ten musi zostać dodany do listy odpowiednich handler'ów BLooper'a. Lista może zawierać dowolną liczbę obiektów, ale w danej chwili BHandler może należeć tylko do jednego BLooper'a.Handler'y, które należą do tego samego BLooper'a mogą być powiązane w listę łączoną (ang. linked list). Jeśli obiekt nie może odpowiedzieć na komunikat, system przekazuje komunikat do następnego handler'a.
Funkccja AddHandler() BLooper'a ustawia przypisanie looper-handler; SetNextHandler() BHandler'a ustawia połączenie handler-handler.
Klasa BMessageFilter pozwala tworzyć funkcje filtrujące, które sprawdzają i przekierowują (lub odrzucają) nadchodzące komunikaty przed ich przetworzeniem przez BLooper. Filtry komunikatów mogą być również zastosowane do pojedynczych obiektów typu BHandler.
Zarówno źródło jak i adresat komunikatu muszą zgodzić się na jego format - stała polecenia, nazwy i typy pól danych. Muszą oni również uzgodnić szczegóły wymiany - kiedy komunikat może zostać wysłany, czy wymaga on odpowiedzi, jaki powinien być format odpowiedzi, co to znaczy, jeśli oczekiwany element danych jest pominięty i tak dalej.Nie jest to żaden problem dla komunikatów, które są używane tylko wewnątrz aplikacji; projektant aplikacji może utrzymywać ślady szczegółów developer can keep track of the details. Jednak, protokoły muszą być opublikowane dla komunikatów, których używają do komunikowania się aplikacje. Jesteś nakłaniany do opublikowania specyfikacji dla wszystkich komunikatów, które twoja aplikacja jest skłonna przyjąć z zewnętrznych źródeł i dla tych wszystkich, które może ona zapakować aby doręczyć je do innych aplikacji.
|
Be
Book,
...w ślicznym HTML...
dla BeOS wydanie 5
Copyright © 2000 Be, Inc. Wszelkie prawa zastrzeżone.