Programowanie komponentowe Henryk Budzisz wersja 1.0 Koszalin 2006 Wprowadzenie Beans COM NET XML CORBA © H.Budzisz Program wykładów Wprowadzenie Technologia JavaBeans Technologia CORBA Technologie COM/DCOM/COM+ Technologia .NET Technologia XML Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 2 Wprowadzenie Koncepcje Komponent programowy Technologie Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 3 „Klejenie” aplikacji Programowanie komponentowe (PK) jest procesem tworzenie aplikacji (systemu informatycznego) poprzez „sklejanie” jej z gotowych, odpowiednio przygotowanych, wymiennych komponentów programowych. Budowanie aplikacji z komponentów jest często porównywane do budowania (żargonowo klejenia) układu (urządzenia) elektronicznego z gotowych, seryjnych komponentów elektronicznych (scalaków, tranzystorów, przełączników). Zarówno w jednym jak i w drugim przypadku, komponenty muszą być przystosowane do tego „klejenia” (wykonane według określonych standardów). Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 4 Zastępowalność Podstawowym celem praktycznym PK, jest stworzenie warunków do łatwej zastępowalności komponentów. Systemy informatyczne podlegają ciągłym zmianom. Jest to proces kosztowny i często naruszający niezawodność oprogramowania. Możliwość zastąpienia komponentu przez jego nową wersję lub zupełnie inną implementację, bez potrzeby rozmontowywania całego systemu, jest niezwykle atrakcyjna z praktycznego punktu widzenia. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 5 Programowanie komponentowe a programowanie obiektowe Komponent musi odpowiadać specyfikacjom określonym dla danej technologii. Sposób implementacji komponentu nie jest narzucany. Ponieważ idee programowania komponentowego i programowania obiektowego w dużym stopniu pokrywają się, komponenty są zwykle definiowane poprzez klasy. Klasy definiujące komponenty muszą spełniać wymagania określone w specyfikacji, np. zapewnić serializację, mechanizmy łączenia i komunikacji, samoinstalację itp. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 6 Komponent programowy Komponent jest wyizolowaną (autonomiczną) jednostką programową, która zgodnie z przyjętymi standardami: zawiera dane i metody realizujące operacje na tych danych – niezależnie od otoczenia (hermetyzacja) udostępnia zestaw oferowanych usług poprzez interfejs programowy. zapewnia komunikację z innymi komponentami. zapewnia „utrwalanie” i odtwarzanie stanu wewnętrznego. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 7 Technologie JavaBeans – wprowadzona do języka Java już od pierwszej wersji. CORBA (ang. Common Object Request Broker Architecture ) COM (ang. Component Object Model) – opracowana przez Microsoft; następnie rozszerzona do DCOM i COM+ .NET – lansowana przez Microsoft jako następca COM/DCOM/COM+ XML – nowy zawodnik w grze komponentowej Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 8 Technologie sieciowe Wraz z rozwojem programowania sieciowego i rozproszonego wspomniane technologie zostały zostały rozszerzone: EJB – Enterprice Java Beans firmy Sun CCM – CORBA Component Model DCOM/COM+ - rozszerzenie technologii COM Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 9 „Mój klej jest lepszy od twojego kleju” Jaką technologię „klejenia” wybrać? Patrząc z odległości 1000 m, efekt „klejenia” w każdej technologii jest podobny. Podstawowe koncepcje są wspólne. Z bliska jednak dostrzegamy, że w każdej technologii stosuje się zdecydowanie różne techniki i style programowania dla osiągnięcia celu. Każda technologia komponentowa ma swoich zwolenników. Na razie nie widać przegranych. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 10 Technologia JavaBeans Wprowadzenie Budowa komponentu Bean Zdarzenia Refleksja i introspekcja Serializacja Podsumowanie Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 11 Wprowadzenie Definicja komponentu Bean Cechy komponentu Bean Komponenty wizualne Środowiska programowe Przykład: pakiet ICEBrowser Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 12 Definicja Bean jest obiektem, którego właściwości i funkcjonalność mogą być odczytywane i zmieniane zgodnie z określonym protokołem (JavaBean specification). Protokół ten określa zasady komunikacji pomiędzy Beanem, a otoczeniem (zwykle środowiskiem wizualnym). Definiując Bean definiujemy klasę, uwzględniając konwencje narzucone przez specyfikacje standardu. Nakład dodatkowej pracy „na bycie komponentem Bean” jest niewielki. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 13 Cechy komponentu Bean Komponenty Bean mają wspólne cechy: Wsparcie dla introspekcji (ang. introspection) udostępnianie informacji o właściwościach i obsługiwanych zdarzeniach. Wsparcie dla dostosowywania (ang. customization) – ustawiania wyglądu i zachowania komponentu. Wsparcie dla obsługi zdarzeń (ang. events) – komunikacji między komponentami Wsparcie dla utrwalania (ang. persistency) – zapisywania i odczytywania informacji o stanie komponentu do/z pliku. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 14 Komponenty wizualne Większość komponentów Bean jest definiowana jako komponenty wizualne (zawierają metodę public void paint(Graphics g) { ... }). Takie komponenty wyprowadzane są z klasy java.awt.Component (lub klas pochodnych), aby mogły być umieszczane w kontenerach. Klasy bibliotek AWT i Swing są komponentami Bean. Komponenty, które nie są wizualne (np. Timer lub ButtonGroup z biblioteki Swing), nie muszą być wyprowadzone z żadnej konkretnej klasy. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 15 Środowiska programowe Przykłady programowe zademonstrowane zostaną w dwóch środowiskach: RealJ – bardzo proste środowisko tekstowe, dające pełną kontrolę nad tekstem źródłowym programu (środowisko niczego nie „dopisuje”). NetBeans – złożone środowisko graficzne – dużo ułatwień, ale łatwo też stracić kontrolę nad tym co się dzieje w projekcie i w tekstach źródłowych. Popularnym środowiskiem graficznym jest Eclipse. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 16 Pakiet ICEBrowser Zestaw komponentów do budowania przeglądarki dokumentów HTML pochodzących z różnych żródeł (z połączenia HTTP, z anonimowego połączenia FTP, z lokalnego dyskowego systemu plikowego, z pliku spakowanego .JAR, ze strumienia javowego Reader) Pakiet zrealizowany został w firmie ICEsoft Technologies, Inc. (www.icesoft.com). Wersja komercyjna jest bardziej rozbudowana i występuje w produktach wielu firm (Sun, Novell, Oracle, ...). Komponenty zawarte są w pliku spakowanym icebrowserbean.jar. Dostępna jest również dokumentacja i przykłady użycia komponentów. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 17 Komponenty ICEBrowser Pakiet ice.htmlbrowser zawiera oprócz klas pomocniczych trzy komponenty: Document – komponent interpretujący hipertekst i zarządzający dokumentami HTTP. Zbudowany na bazie klasy Panel biblioteki AWT. Browser – komponent na bazie klasy Document, umożliwiający dostęp do hipertekstu pochodzącego z różnych źródeł, prowadzenie historii, itp. ICEBrowser – gotowy aplet reprezentujący przeglądarkę dokumentów hipertekstowych. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 18 Komponent Document Dostępnych jest ok. 70 metod. Wybrane metody: gotoLocation – ustawia wskazany dokument w głównej ramce get-/setCurrentLocation – pobiera/ustawia dokument w bieżącej ramce get-/setCurrentFrame – pobiera/ustawia bieżącą ramkę get-/setDocumentTitle – pobiera/ustawia tytuł dokumentu getSelectedText – pobiera zaznaczony tekst reload – ponownie załadowuje dokument (odświeżanie) add-/removeMouseOverLinkListener – obsługa zdarzeń dot. linków add-/removePropertyChangeListener – obsługa zdarzeń dot. właściwości firePropertyChange – wygenerowanie zdarzenia dot. zmiany właściwości search – wyszukuje podany tekst w dokumencie printDoc – organizuje wydruk dokumentu HTML lub ramki Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 19 Komponent Browser Browser dziedziczy metody komponentu Document, a ponadto zawiera 12 własnych metod. Wybrane metody: gotoLocation – załaduj dokument HTML goBack – wróć do poprzedniego dokumentu goForward – wróć do dokumentu wcześniejszego getBackHistory – zwróć historię odwiedzin getForwardHistory - zwróć historię odwiedzin get-/setCacheSize – podaj/ustaw rozmiar buforu dokumentów Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 20 Zastosowania komponentów ICEBrowser do budowy systemu pomocy on-line dla aplikacji. dostęp do stron www z poziomu aplikacji. budowanie interfejsu dla aplikacji rozproszonych. Przykłady: HTMLBrowser\demo1 (RealJ) HTMLBrowser\TestHTMLBrowser1 (NetBeans) Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 21 Budowa komponentu Bean Konstruktor bezparametrowy Właściwości i akcesory Właściwości proste Właściwości logiczne Właściwości indeksowane Właściwości związane i ograniczone Metody publiczne komponentu Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 22 Konstruktor bezparametrowy Komponent JavaBeans musi zawierać konstruktor bezparametrowy, np.: void Button() { ... } Umożliwia to dynamiczne ładowanie klasy i tworzenie obiektu przy użyciu metody Bean.instantiate(...) Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 23 Właściwości i akcesory Właściwość (ang. property) jest definiowana poprzez nazwę funkcji nazywanej akcesorem (ang. accessor). Nazwa ta rozpoczyna się przedrostkiem get- , set- lub is- . Getter jest akcesorem służącym do pobierania wartości właściwości. Przykład: getLabel definiuje właściwość label (zmiana wielkości pierwszej litery) Setter służy do ustawiania właściwości, np.: setColor definiuje właściwość color. Zmiana właściwości jest często uzgadniana z innymi komponentami. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 24 Właściwości proste Właściwość prosta ma pojedynczą wartość. Getter: <typ właściwości> get<nazwa właściwości> () { <definicja> } Setter: void set<nazwa właściwości> (<typ właściwości>) { <definicja> } Przykład definiowania właściwości prostej foreground: private Color color; // zmienna pomocnicza // definicja akcesorów public Color getForeground() // getter { return color; } public void setForeground(Color c) // prosty setter { color = c; } Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 25 Fragment kodu klasy AbstractButton.java (Sun Microsystems) /** * Returns the button's text. * @return the buttons text * @see #setText */ public String getText() { return text; } /** * Sets the button's text. * @param text the string used to set the text * @see #getText * @beaninfo * bound: true * preferred: true * attribute: visualUpdate true * description: The button's text. */ public void setText(String text) { String oldValue = this.text; this.text = text; firePropertyChange(TEXT_CHANGED_PROPERTY, oldValue, text); updateDisplayedMnemonicIndex(text, getMnemonic()); } if (accessibleContext != null) { accessibleContext.firePropertyChange( AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY, oldValue, text); } if (text == null || oldValue == null || !text.equals(oldValue)) { revalidate(); repaint(); } Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 26 Właściwości logiczne Właściwość logiczna jest właściwością prostą typu boolean, ale ma inne akcesory. Getter: boolean is<nazwa właściwości> () { <definicja> } Setter: void set<nazwa właściwości> (boolean) { <definicja> } Przykład: private boolean selected; // definicja akcesorów public boolean isSelected() { return selected; } public void setSelected(boolean sel) { selected = sel; } Wprowadzenie Beans COM NET XML CORBA // getter // prosty setter © H.Budzisz 27 Fragment kodu klasy AbstractButton.java (Sun Microsystems) /** * Returns the state of the button. True if the * toggle button is selected, false if it's not. * @return true if the toggle button is selected, otherwise false */ public boolean isSelected() { return model.isSelected(); } /** * Sets the state of the button. Note that this method does not * trigger an <code>actionEvent</code>. * Call <code>doClick</code> to perform a programatic action change. * * @param b true if the button is selected, otherwise false */ public void setSelected(boolean b) { boolean oldValue = isSelected(); model.setSelected(b); } Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 28 Właściwości indeksowane Właściwość indeksowana jest tablicą elementów. Getter elementu: <typ właściwości> get<nazwa właściwości> (int) { <definicja> } Setter elementu: void set<nazwa właściwości> (int, <typ właściwości>) { <definicja> } Getter tablicy: <typ właściwości>[] get<nazwa właściwości> () { <definicja> } Setter tablicy: void set<nazwa właściwości> (<typ właściwości>[]) { <definicja> } Przykład: void setWykaz (String[]) { ... } Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 29 Właściwości związane i ograniczone Właściwości komponentu mogą być związane (ang. bounded), tzn. o zmianie tej właściwości informowane są inne komponenty i reagują na tą zmianę. Właściwości komponentu mogą być ograniczane (ang. constrained), tzn. o zmianie powiadamiane są inne komponenty; zmiana dochodzi do skutku jeżeli żaden z powiadomionych komponentów nie zawetuje zmiany. Mechanizm ten funkcjonuje w oparciu o generowanie i rozsyłanie odpowiednich zdarzeń. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 30 Metody publiczne komponentu Metody publiczne komponentu są często wykorzystywane w trakcie komunikacji pomiędzy komponentami (są wywoływane w trakcie obsługi zdarzeń przychodzących z innych komponentów). Metody te powinny być synchronizowane dla zapewnienia poprawnej obsługi wywołań pochodzących z różnych wątków. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 31 Zdarzenia Koncepcja Hierarchia zdarzeń Tworzenie słuchacza Przyłączanie słuchacza Adaptery Anonimowe klasy wewnętrzne Klasa narzędziowa PropertyChangeSupport Rozgłaszanie zmian właściwości Wetowanie zmian właściwości Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 32 Koncepcja Źródło zdarzenie Słuchacz Programowa realizacja: Utworzyć obiekt Słuchacza (obiekt klasy implementującej interfejs nasłuchu lub klasy potomnej z adaptera). Przyłączyć obiekt słuchacza do jednego lub większej liczby komponentów. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 33 Hierarchia zdarzeń Object EventObject PropertyChangeEvent AWTEvent ActionEvent AdjustmentEvent ContainerEvent ItemEvent WindowEvent TextEvent FocusEvent ComponentEvent InputEvent KeyEvent MouseEvent Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 34 Zdarzenie ActionEvent ActionEvent(Object source, int id, String command) gdzie: source - odniesienie na obiekt, który jest źródłem zdarzenia; słuchacz może użyć metody getSource aby pobrać to odniesienie. id – identyfikator liczbowy (zwykle stała ACTION_PERFORMED); słuchacz może użyć metody getID command – dowolny łańcuch, np. etykietka przycisku albo nazwa opcji menu; słuchacz może użyć metody getActionCommand Przykład generowania zdarzenia: ActionEvent action = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, label); Powiadamianie przyłączonego słuchacza o zdarzeniu action: listener.actionPerformed(action); Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 35 Zdarzenie MouseEvent public MouseEvent(Component source, int id, long when, int modifiers, int x, int y, int clickCount, boolean popupTrigger) Parametry: source – odniesienie na żródło zdarzeń id – identyfikator liczbowy (patrz: dokumentacja JDK) when – czas powstania zdarzenia. modifiers – modyfikator wskazujący na użycie klawisza modyfikującego (np.: shift, ctr, alt). x, y- współrzędne kursora myszki. clickCount – liczba kliknięć myszki związanych ze zdarzeniem. popupTrigger – zmienna logiczna; prawdziwa jeżeli zdarzenie wyzwala menu rozwijane. Parametry te są dostępne z poziomu słuchacza poprzez gettery. Przykład: demo2.java klasa Nawigacja funkcja mouseReleased (RealJ) Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 36 Zdarzenie PropertyChangeEvent PropertyChangeEvent(Object source, String propertyName, Object oldValue, Object newValue) Parametry: source – komponent JavaBean, który jest źródłem zdarzenia. propertyName – nazwa właściwości, która została zmieniona. oldValue – poprzednia wartość właściwości. newValue – nowa wartość właściwości. Przykład generowania zdarzenia: PropertyChangeEvent propEvt = new PropertyChangeEvent(this, ”text”, oldTxt, newTxt); Jeżeli wartość właściwości jest typu prostego (np. int), musi być przekształcona w obiekt klasy opakowującej (dla int jest to Integer) Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 37 Tworzenie słuchacza Słuchacz - to klasa, która może obsługiwać zdarzenie. Każda klasa, która implementuje interfejs nasłuchu staje się Słuchaczem, np.: public class MojaKlasa implements ActionListener { ... } Każda klasa implementująca interfejs musi zdefiniować metody interfejsu. Dla interfejsu ActionListener jest to: public void actionPerformed(ActionEvent e) { // instrukcje obsługujące zdarzenie } Klasa-słuchacz może też implementować większą liczbę interfejsów nasłuchu (określamy w ten sposób zestaw obsługiwanych zdarzeń). Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 38 Interfejsy nasłuchu EventListener ActionListener Action WindowListener MouseListener MouseInputListener MouseMotionListener MenuListener PropertyChangeListener VetoableChangeListener Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 39 Interfejsy nasłuchu zmian właściwości Interfejs PropertyChangeListener ma jedną metodę: public void propertyChange(PropertyChangeEvent e) Interfejs VetoableChangeListener również ma jedną metodę: public void vetoableChange(PropertyChangeEvent e) throws PropertyVetoException Schemat definiowania słuchacza: public class Listener implements PropertyChangeListener { ... public void propertyChange(PropertyChangeEvent e) { ... } // propertyChange ... } Przykład: demo6.java (RealJ) Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 40 Przyłączanie słuchacza Wszystkie komponenty JavaBeans powinny umożliwiać przyłączanie/odłączanie określonych typów Słuchaczy. Służą do tego metody: addXXXListener() oraz removeXXXListener(), gdzie XXX jest typem słuchacza. Np.: zdarzenie ActionEvent może być obsłużone przez Słuchacza implementującego interfejs ActionListener; Słuchacz taki może być przyłączony do komponentów, które mają dostęp do metody addActionListener(). Są to: Button, List, TextField, MenuItem oraz klasy pochodne. Przykład: Słuchacz słuchacz = new Słuchacz(); przyciskOK.addActionListener(słuchacz); Przykład programowy – Demo2.java (RealJ) Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 41 Rejestracja słuchaczy (AWTEventMulticaster) Przykład użycia klasy bibliotecznej java.awt.AWTEventMulticaster do rejestrowania słuchaczy w komponencie AWT (JDK Help) public myComponent extends Component { ActionListener actionListener = null; public synchronized void addActionListener(ActionListener l) { actionListener = AWTEventMulticaster.add(actionListener, l); } public synchronized void removeActionListener(ActionListener l){ actionListener = AWTEventMulticaster.remove(actionListener, l); } public void processEvent(AWTEvent e) { // when event occurs which causes "action" semantic ActionListener listener = actionListener; if (listener != null) { listener.actionPerformed(new ActionEvent()); } } } Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 42 Rejestracja słuchaczy w kontenerze // Utility field holding table of ActionListeners. // Generic type is used to declare this container. private Vector<ActionListener> listeners = new Vector<ActionListener>(); // Registers ActionListener to receive events. public synchronized void addActionListener(ActionListener listener) { listeners.addElement(listener); } // Removes ActionListener from the list of listeners. public synchronized void removeActionListener(ActionListener listener) { listeners.removeElement(listener); } // Notifies all registered listeners about the event. private void fireActionPerformed(ActionEvent event) { Vector<ActionListener> targets; synchronized (this) { targets = (Vector<ActionListener>)listeners.clone(); } Przykład (NetBeans) : for (ActionListener target : targets) target.actionPerformed(event); OvalButton.java } Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 43 Adaptery Implementację interfejsu można wykonać w pomocniczej klasie zwanej adapterem, a następnie zdefiniować klasę słuchacza jako rozszerzenie adaptera, np.: class WindowAdapter implements WindowListener { public void windowActivated(WindowEvent e) {} public void windowClosed(WindowEvent e) {} // następne metody } class Słuchacz extends WindowAdapter { // definicja potrzebnej metody } Pakiet java.awt.event zawiera takie adaptery. Przykład: Demo3.java (RealJ) Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 44 Anonimowe klasy wewnętrzne Klasa wewnętrzna jest definiowana wewnątrz innej klasy, np.: class KlasaZewnętrzna { class KlasaWewnętrzna { ... } ... } Klasa wewnętrzna może być zdefiniowana nawet wewnątrz funkcji. Klasa anonimowa, to klasa bez nazwy, definiowana w momencie tworzenia obiektu (tj. przy użyciu operatora new). Przykłady: Demo4.java, Demo5.java (RealJ) i TestHTMLBrowser2 (NetBeans) Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 45 Klasa narzędziowa PropertyChangeSupport Wybrane metody klasy: public PropertyChangeSupport(Object sourceBean) // constructor public void addPropertyChangeListener(PropertyChangeListener listener) public void removePropertyChangeListener(PropertyChangeListener listener) public void firePropertyChange(String propertyName, Object oldValue, Object newValue) public void firePropertyChange(String propertyName, int oldValue, int newValue) public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) public void firePropertyChange(PropertyChangeEvent evt) Dla właściwości ograniczonych stosuje się klasę VetoableChangeSupport. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 46 Rozgłaszanie zmian właściwości Setter, który zmienia właściwość związaną powinien powiadomić o tym wszystkich słuchaczy zmian właściwości (obiekty klas implementujących interfejs PropertyChangeListener) przyłączonych do komponentu, np.: private PropertyChangeSupport propertySupport = new PropertyChangeSupport(this); ... public void setURL(String URL) { String oldURL = this.URL; this.URL = URL; propertySupport.firePropertyChange("URL", oldURL, URL); } Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 47 Przykład: HyperlinkedLabel Demonstruje: Użycie klasy narzędziowej PropertyChangeSupport. Setter i getter właściwości hyperlinkColor oraz URL. Zgłaszanie zmian właściwości hyperlinkColor oraz URL. Implementację interfejsu MouseListener (jest słuchaczem zdarzeń MouseEvent). Sposób dodawania i usuwania słuchaczy akcji. Generowanie i rozsyłanie zdarzeń ActionEvent w odpowiedzi na kliknięcie myszki. Dorysowanie podkreślenia pod etykietką. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 48 Wetowanie zmian właściwości Setter, który zmienia właściwość ograniczoną powinien przed tą zmianą powiadomić wszystkich przyłączonych słuchaczy implementujących interfejs VetoableChangeListener, np.: public void setCount(int newCount) throws PropertyVetoException { int oldCount = count; // count - wartość właściwości vetoableChangeSupport.fireVetoableChange("count",oldCount,newCount); count = newCount; // przy braku wyjątku dochodzi do zmiany } Słuchacze mogą z przesłanego zdarzenia typu PropertyChangeEvent, odczytać (getNewValue) proponowaną nową wartość. Jeżeli proponowana nowa wartość nie może być zaakceptowana, zgłaszany jest wyjątek PropertyVetoException. Zgłoszenie wyjątku przerywa wykonywanie dalszych instrukcji (do zmiany właściwości nie dochodzi) i następuje przejście do obsługi wyjątku. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 49 Przykład: Counter sterowanie decButton addActionListener komponent klasa wyjątek JavaBeans słuchacza (exception) wetowanie addVetoableChangeListener (JButton) limitator (Limitator) anonymous (ActionListener) decreace addActionListener anonymous setText (ActionListener) PropertyVetoExceprion counter (Counter) setCounter ostrzeganie (JTextField) increace addPropertyChangeListener anonymous (ActionListener) setText propListener counterWarning (PropertyChangeListener) incButton (JLabel) addActionListener (JButton) Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 50 Podsumowanie Zasady definiowania komponentu JavaBeans: Klasę definiującą komponent należy zdefiniować jako serializowaną. Należy zdefiniować konstruktor bezparametrowy. Właściwości komponentu są zdefiniowane przez zestaw setterów i getterów (również odziedziczonych). Zdarzenia obsługiwane przez komponent określone są przez parę metod addXXXListener/removeXXXListener (gdzie: XXX jest nazwą zdarzenia) zdefiniowanych w komponencie (lub odziedziczonych). Metody publiczne komponentu powinny być synchronizowane. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 51 Refleksja i introspekcja Automatyzacja tworzenia i dostosowywania komponentu Klasa Class Dynamiczne ładowanie klas Dynamiczne pobieranie metody Dynamiczne wywołanie metody Introspekcja Narzędzia graficzne Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 52 Automatyzacja tworzenia i dostosowywania komponentu Przeciągnięcie ikony komponentu z palety na obszar projektowanego interfejsu graficznego powoduje utworzenie obiektu tej klasy. Przy użyciu mechanizmu refleksji rozpoznaje się typ komponentu, a następnie stosując konstruktor bezparametrowy, tworzy się obiekt. Następnie, stosując introspekcję należy z komponentu „wydobyć” informacje o właściwościach, setterach, getterach, obsługiwanych zdarzeniach i dostępnych metodach publicznych (bez dostępu do kodu źródłowego). W końcu, przy użyciu mechanizmu refleksji, należy udostępnić wartości właściwości (z możliwością edycji) oraz przyłączyć stosownych słuchaczy. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 53 Klasa Class W trakcie wykonywania instrukcji: Button anuluj = new Button(”Anuluj”); potrzebna jest definicja klasy java.awt.Button. JVM odszukuje plik Button.class, dekoduje binarny format klasy, sprawdza kompatybilność z innymi klasami, weryfikuje sekwencję operacji kodu bajtowego, ładuje do pamięci i tworzy obiekt klasy Class zawierający metadane udostępniające informacje o modyfikatorach, konstruktorach, metodach i polach klasy Button. Dostęp do tego obiektu jest możliwy przez użycie metody getClass z klasy Object lub dodanie literału .class do nazwy klasy. Przykłady: Class c = anuluj.getClass(); Class c = java.awt.Button.class; Przykład programowy: Introspekcja\ClassViewer.java (RealJ) Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 54 Dynamiczne ładowanie klas Jeżeli w trakcie realizacji programu potrzebny jest obiekt pewnej klasy, można ją odszukać i załadować przy użyciu metody forName z klasy Class, np.: Class c = null; try { c = Class.forName(”beans.Counter”); } catch (ClassNotFoundException ex) { // obsługa wyjątku } Obiekt załadowanej klasy można stworzyć przy użyciu metody newInstance z klasy Class, np.: Object object = = null; Objekt objekt new c(); try { object = c.newInstance(); } catch ... Wprowadzenie Beans COM NET XML CORBA Nie można użyć operatora new © H.Budzisz 55 Dynamiczne pobieranie metody W załadowanej klasie, dostępnej przez obiekt klasy Class (w przykładzie beanClass) występują pola i metody. Dostęp do zdefiniowanej metody uzyskuje się przez podanie jej nazwy i parametrów jako argumentów metody getMethod z klasy Class. // tablica parametrów odpowiadających argumentom metody Class[] parameterTypes = new Class[] {String.class}; Method addTxt = null; try { addTxt = beanClass.getMethod("addText", parameterTypes); } catch (NoSuchMethodException ex) { ...} Definicja metody jest reprezentowana przez obiekt klasy Method. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 56 Dynamiczne wywoływanie metody beanObject.addTxt(”Anuluj”); Metodę reprezentowaną przez obiekt klasy Method (w przykładzie addTxt) wywołuje się stosując metodę invoke. Argumentami jej są: obiekt na rzecz którego jest wywoływana i argumenty wywołania. Object[] arguments = ”Anuluj”; try { addTxt.invoke(beanObject, arguments); } catch (IllegalArgumentException ex) { ... } catch (InvocationTargetException ex) { ... } catch (IllegalAccessException ex) { ... } Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 57 Podsumowanie W mechanizmie refleksji wykorzystuje się następujące klasy: java.lang.Class – reprezentuje klasę lub interfejs. java.lang.Object – udostępnia metodę getClass. java.lang.reflect.Constructor – udostępnia konstruktor klasy. Umożliwia dynamiczne tworzenie obiektu klasy. java.lang.reflect.Method – udostępnia metodę klasy. Umożliwia wywołanie metody. java.lang.reflect.Field – udostępnia pole klasy i dynamiczny dostęp do tego pola. java.lang.reflect.Modifier – udostępnia informacje o modyfikatorach definicji klasy (abstract, public, final). java.lang.reflect.Array – umożliwia dynamiczne deklarowanie tablic. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 58 Introspekcja Dynamicznie załadowany Bean musi zostać „przebadany”. Służy do tego klasa Introspector, umieszczając wynik „badania” w obiekcie klasy BeanInfo. Służy do tego metoda: getBeanInfo(Class<?> beanClass, Class<?> stopClass) Następuje odczytanie informacji o właściwościach, metodach i zdarzeniach w podanej klasie beanClass oraz wszystkich klasach bazowych, aż do klasy stopClass (ale bez niej). Przykład: BeanInfo bi = null; try { bi = Introspector.getBeanInfo(bean, Object.class); } catch(IntrospectionException e) { // obsługa wyjątku } Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 59 Wynik introspekcji Informacje o właściwościach, getterach, setterach, zdarzeniach i metodach umieszczone są w obiektach odpowiednich klas (rysunek), zdefiniowanych w pakiecie java.beans. PropertyDescriptor[] descriptors = bi.getPropertyDescriptors(); for(PropertyDescriptor d: descriptors) // extended for print("Nazwa właściwości:\n " + d.getName()); // inne metody klasy PropertyDescriptor // getPropertType, getReadMethod, getWriteMethod, ... Przykład: BeanDumper.java (RealJ) Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 60 Sterowanie introspekcją Realizuje się przez zdefiniowanie klasy o nazwie: <nazwa komponentu>BeanInfo, np.: JugglerBeanInfo implementującej interfejs BeanInfo. Ponieważ interfejs wymusza implementację ośmiu metod, wygodniejsze jest rozszerzenie pomocniczej klasy SimpleBeanInfo, np.: public class JugglerBeanInfo extends SimpleBeanInfo { } W klasie tej definiuje się metody wykorzystywane podczas introspekcji: BeanDescriptor getBeanDescriptor() PropertyDescriptor[] getPropertyDescriptors(); MethodDescriptor[] getMethodDescriptors(); EventSetDescriptor[] getEventSetDescriptors(); Definiując metodę getIcon, można przypisać do komponentu ikonę. Przykład: JugglerBeanInfo.java (NetBeans) Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 61 Narzędzia graficzne W środowisku graficznym nazwy komponentów są zastąpione przez ikony. Przeciągnięcie ikony z palety do obszaru projektowego powoduje utworzenie obiektu z użyciem konstruktora bezparametrowego. Poprzez introspekcję „odczytane” zostają właściwości komponentu oraz obsługiwane zdarzenia. Nazwy właściwości tworzą pierwszą kolumnę tabeli edycyjnej. Wartości właściwości są odczytywane przez gettery i tworzą drugą kolumnę tabeli edycyjnej. Do edycji właściwości używane są wyspecjalizowane edytory (koloru, czcionki, łańcuchów itp.). Nowe wartości są ustawiane w komponencie przez settery. W podobny sposób wiąże się zdarzenia z funkcjami odpowiedzi. Przykład: PropertyViewer.java (NetBeans) Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 62 Serializacja komponentów Serializacja obiektu, czyli zapis stanu obiektu w pliku dyskowym realizuje metoda writeObject() z klasy ObjectOutputStream. Jest to klasa opakowująca dowolny strumień zdefiniowany przez klasę wyprowadzoną z klasy abstrakcyjnej OutputStream. Przykład: JButton przycisk = new JButton(”Pomoc”); // serializacja obiektu przycisk ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(”przycisk.ser”)); out.writeObject(przycisk); // pominięto wyjątki Klasy obiektów serializowanych muszą implementować interfejs Serializable. Interfejs ten nie zawiera żadnych metod. Składowe klasy, których nie chcemy serializować (hasła, zmienne pomocnicze itp.) oznaczamy słowem kluczowym transient. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 63 Deserializacja komponentów Deserializacja obiektu, czyli odczytanie stanu obiektu wcześniej zserializowanego realizuje metoda readObject() z klasy ObjectInputStream. Jest to klasa opakowująca dowolny strumień zdefiniowany przez klasę wyprowadzoną z klasy abstrakcyjnej InputStream. Przykład: ObjectInputStream in = new ObjectInputStream( new FileInputStream(”przycisk.ser”)); // deserializacja obiektu przycisk JButton przycisk = (JButton)in.readObject(); Przykład programowy: Serializacja.java i Deserializacja.java (RealJ). Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 64 Serializacja do dokumentu XML Możliwa jest również serializacja przez utworzenie dokumentu tekstowego XML. Klasy ObjectOutputStream i ObjectInputStream zastępuje się odpowiednio przez klasy XMLEncoder i XMLDecoder zdefiniowane w pakiecie java.beans. Klasy te mają takie same metody odczytu i zapisu: readObject() i writeObject(). Przykład: SerializacjaXML.java i DeserializacjaXML.java (RealJ) Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 65 Podsumowanie technologii JavaBeans Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 66 Technologia COM COM – Interfejsy Technologia COM w Visual C++ Globally Unique Identifiers (GUIDs) Biblioteka COM Tworzenie obiektu COM Inteface IUnknown Definiowanie komponentu Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 67 COM – co to jest? COM (Component Object Model) jest opracowaną przez Microsoft technologią umożliwiającą efektywną komunikację między aplikacjami Component Object Model definiuje: binarny standard wywoływania funkcji między komponentami, struktury interfejsów udostępnianych przez poszczególne obiekty, mechanizmy jednoznacznej identyfikacji komponentów i ich interfejsów. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 68 Klient Diagram binarnego standard wywoływania funkcji Obiekt komponentowy (instancja komponentu) wskaźnik na VTBL (pole prywatne) VTBL jest wspólna dla wszystkich klientów korzystających z danego komponentu COM Serwer wskaźnik na funkcję1 wskaźnik na funkcję2 wskaźnik na funkcję3 VTBL (tablica funkcji wirtualnych) funkcja1(pObj,arg1,arg2,...) { ... } Implementacja komponentu COM zawarta zwykle w bibliotece DLL Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 69 Komponenty COM w jednym procesie Technologia COM definiuje sposób współpracy pomiędzy komponentem a jego klientem. W pojedynczym procesie, klient łączy się z komponentem bez pośrednich komponentów. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 70 Komponenty COM w różnych procesach Klient, który chce się skomunikować z komponentem musi skorzystać z pośrednictwa tzw. RPC stubs oraz obiektów Proxy, istniejących po obu stronach połączenia. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 71 Komponenty COM na różnych komputerach Jeżeli klient i komponent rezydują na różnych komputerach, w technologii DCOM (Distributed COM) komunikację międzyprocesorową zastępuje się protokołem sieciowym. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 72 COM – Interfejsy Obiekty COM udostępniają swoje funkcje obiektom zewnętrznym za pośrednictwem interfejsów. Interfejs jest zestawem prototypów funkcji składowych komponentu COM (metod). Nazwy interfejsów przyjęło się poprzedzać przedrostkiem I, np.: ILookup Interfejs można zdefiniować na bazie innego interfejsu, czyli zastosować dziedziczenie (ale wyłącznie jednobazowe) Interfejs nie posiada własnej implementacji. Każdy komponent może implementować wiele interfejsów oferować wiele zestawów usług. Klienty (aplikacje lub inne komponenty) odwołują się do interfejsów za pośrednictwem wskaźników. Każdy interfejs posiada własny, unikalny identyfikator - Globally Unique Identifier (GUID). Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 73 Schemat implementacji interfejsu Interfejsy Każdy komponent może implementować wiele interfejsów oferować wiele zestawów usług. IA IB Komponent COM (coclass) IC Komponent lub coclass (skrót od component object class) zawiera kod wszystkich funkcji udostępnianych przez interfejsy, funkcje pomocnicze i dane. Komponent jest umieszczony w serwerze COM - pliku binarnym (DLL lub EXE). Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 74 Implementacja interfejsu Implementacja interfejsu (definicja komponentu) może być zrealizowana w różnych językach programowania. Komponenty mogą być używane przez inne komponenty lub aplikacje realizowane w różnych językach programowania. Technologia COM definiuje standardy komunikowania się na poziomie binarnym. Na poziomie źródłowym ta sama funkcja będzie kodowana zgodnie ze składnią danego języka, np.: Visual Basic object.Add( Time As Double, Name As String ) As Variant C++ HRESULT Add( double Time, BSTR Name, VARIANT* pVal ); Java public com.ms.com.Variant Add( double Time, String Name ); Trzeba ponadto zapewnić konwersję typów danych. Szczegóły w dokumentacji MSDN (COM Language Translation). Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 75 Technologia COM w Visual C++ Preferowanym językiem implementacji jest C++. Interfejs jest definiowany jako klasa abstrakcyjna z czystymi funkcjami wirtualnymi. Do tworzonych obiektów automatycznie dołączana jest Vtable (tablica wywołań funkcji wirtualnych). W C trzeba taką tablicę samemu zdefiniować. Każda metoda musi mieć wskaźnik zwrotny na obiekt. W C++ automatycznie dodawany jest wskaźnik this. W C trzeba dodać do każdej funkcji dodatkowy parametr. Definicja klasy stanowi też automatycznie przestrzeń nazw dla składowych. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 76 Globally Unique Identifiers (GUIDs) Do identyfikacji każdego interfejsu i każdego komponentu, używany jest unikalny identyfikator (128-bitowa struktura; long + uint + uint + 8*char = 16 bajtów). Plik guiddef.h zawiera definicję struktury opisującej GUID i pomocnicze makrodefinicje, np.: DEFINE_GUID. Programista – zamiast tego identyfikatora – używa związanej z nim stałej symbolicznej IID_<nazwa interfejsu> lub CLSID_<nazwa komponentu> . Przykład definicji stałej IID_ILOOKUP: DEFINE_GUID(IID_ILOOKUP, 0xc4910d71, 0xba7d, 0x11cd, 0x94, 0xe8, 0x08, 0x00, 0x17, 0x01, 0xa8, 0xa3); Do generowania GUID udostępnione są programy narzędziowe (w Visual C++ Tools/Create GUID) oraz funkcja CoCreateGuid z COM API. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 77 Obsługa błędów Wszystkie funkcje COM w Visual C++ (z wyjątkiem AddRef i Release z interfejsu IUnknown) zwracają jako wynik wartość typu HRESULT zawierającą informacje o błędach (plik winerror.h wiersz 17 040). Typowy schemat obsługi błędów: HRESULT hr; hr = funkcja_COM(parametry); if (hr == S_OK) { // dalsze operacje ... } else cerr << ”Wywołanie funkcja_COM nie powiodło się”; Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 78 Makrodefinicje do obsługi błędów Powszechną praktyką jest wykorzystywanie do obsługi błędów makrodefinicji SUCCEEDED i FAILED, którym przekazuje się wynik wywołania funkcji typu HRESULT. Przykład: if ( FAILED( CoInitialize(NULL) )) { cerr << "Inicjacja nie powiodła się"; return 1; } Kod błędu można odczytać z HRESULT stosując makrodefinicję HRESULT_CODE. Opis tekstowy błędu (na podstawie jego kodu, np.: 0x800401F0) można uzyskać przy użyciu narzędzia Tools/Error Lookup w VC++. Opis jest widoczny również w debuggerze. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 79 Biblioteka COM COM library API udostępnia funkcje i makra do obsługi zadań związanych z definiowaniem i używaniem obiektów COM. Bibliotekę trzeba przed użyciem zainicjować przy użyciu funkcji HRESULT CoInitialize(LPVOID pvReserved); Schemat inicjacji: // Inicjacja biblioteki COM - załadowanie plików DLL. if ( FAILED( CoInitialize(NULL) )) { cerr << "Inicjacja biblioteki COM nie powiodła się" << endl; return 1; } ... // Zwolnij zasoby przydzielone bibliotece COM. CoUninitialize(); Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 80 Tworzenie obiektu COM Aby otworzyć obiekt COM i uzyskać wskaźnik na interfejs stosuje się funkcję CoCreateInstance() z COM API HRESULT CoCreateInstance( REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID* ppv); Parametry: rclsid – identyfikator komponentu, np.: CLSID_ShellLink. pUnkOuter – używany przy agregacji obiektu COM (NULL gdy nie jest stosowana agregacja) dwClsContext – rodzaj serwera; dla serwera DLL w tym samym procesie stosuje się stałą CLSCTX_INPROC_SERVER. riid - identyfikator interfejsu, np.: IID_IShelLink. ppv - wskaźnik na interfejs; parametr zwrotny funkcji. Zwracane wartości: S_OK, REGDB_E_CLASSNOTREG, CLASS_E_NOAGGREGATION lub E_NOINTERFACE. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 81 Interfejs IUnknown Podstawowym interfejsem implementowanym przez każdy obiekt COM jest interfejs IUnknown, udostępniający trzy funkcje: QueryInterface, AddRef, Release. IUnknown Komponent COM QueryInterface implementacja AddRef implementacja Release implementacja Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 82 IUnknown – funkcje AddRef i Release Funkcje AddRef i Release zarządzają licznikiem referencji do interfejsów. AddRef jest wywoływana, gdy klient używa danego interfejsu i zwiększa licznik o 1. Release jest wywoływana, gdy klient nie potrzebuje już interfejsu i zmniejsza licznik. Obie zwracają nową wartość licznika referencji. Funkcja QueryInterface zostanie omówiona w dalszej części wykładu. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 83 Schemat tworzenia i użycia obiektu COM HRESULT hr; // zmienna przechowująca wynik wywołania IShellLink* pISL; // wskaźnik na interfejs hr = CoCreateInstance ( CLSID_ShellLink, // CLSID komponentu NULL, // nie używa się agregacji CLSCTX_INPROC_SERVER, // typ serwera IID_IShellLink, // IID interfejsu (void**) &pISL ); // zwracany wskaźnik if ( SUCCEEDED ( hr ) ) { // Wywołania metod komponentu z użyciem pISL ... pISL->Release(); // powiadom obiekt COM o zakończeniu } else { // Nie utworzono obiektu COM; hr zawiera kod błędu } Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 84 Przykład użycia komponentu Active Desktop Active Desktop jest usługą Internet Explorera pozwalającą na wyświetlanie stron www, dokumentów HTML, apletów Javy i komponentów ActiveX na tapecie (bez użycia przeglądarki; Uwaga! Spowalnia system). Działania zrealizowane w przykładzie: 1. Inicjalizacja biblioteki COM (funkcja CoInitialize). 2. Utworzenie obiektu COM typu ActiveDesktop i pobranie wskaźnika do interfejsu IActiveDesktop. 3. Wywołanie metody GetWallpaper() komponentu ActiveDesktop. 4. Jeżeli wywołanie zakończyło się powodzeniem – wydrukowanie nazwy pliku przechowującego tapetę widoczną na pulpicie. 5. Pobranie przy użyciu metody GetDesktopItemCount() liczby elementów (stron www dodanych do listy) – wydruk informacji. 6. Pobranie (w pętli) informacji o elementach (funkcja GetDesktopItem) do struktury COMPONENT i wydruk nazw elementów (stron www). 7. Zwolnienie interfejsu (funkcja Release) 8. Zwolnienie zasobów przydzielonych bibliotece COM (funkcja CoUninitialize). Przykład: C:\HB\Programowanie komponentowe\COM\Pulpit (project Pulpit1) Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 85 Łańcuchy w technologii COM uzupełnienie przykładu Łańcuchy zwracane przez funkcje COM kodowane są w Unikodzie (2 bajty na znak). Znak w Unikodzie definiowany jest w VC++ przez typ WCHAR, np.: char tekst1[80]; // zwykła tablica znakowa WCHAR tekst2[80]; // tablica znaków w Unikodzie Do przekształcenia tekstu Unikod na tekst ASCII służy funkcja WideCharToMultiByte(). Prostszym sposobem jest użycie klasy CString z biblioteki MFC, której konstruktor akceptuje zarówno łańcuchy ASCII jak i Unikod (konwersja polskich znaków jest poprawna), np.: CString str1(unicode_text); Strumień standardowy wcout akceptuje teksty wyłącznie w Unikodzie. „Zwykły” tekst musi być przekształcony w Unikod (prefiks L, np.: L”Nazwa: ”). Uwaga: nie radzi sobie z polskimi znakami – przerywa wyprowadzanie. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 86 IUnknown – funkcja QueryInferface Funkcja QueryInterface dostępna w każdym komponencie (ponieważ zdefiniowana jest w interfejsie IUnknown) pozwala na uzyskanie dostępu do innych interfejsów danego obiektu. Jej składnia jest następująca: HRESULT QueryInterface (REFIID iid, void** ppvObject); Parametry: iid - identyfikator GUID żądanego interfejsu ppvObject - wskaźnik do żądanego interfejsu; parametr zwrotny. Gdy interfejs jest dostępny, funkcja zwraca wartość S_OK. W przypadku, gdy interfejs nie jest zaimplementowany zwracana jest wartość E_NOINTERFACE. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 87 Przykład użycia komponentu z wieloma interfejsami Zadaniem programu (Pulpit2) jest dodanie na pulpicie skrótu do własnego pliku exe. Etapy realizacji zadania: 1. Pobranie ścieżki do własnego pliku exe (GetModuleFileName). 2. Pobranie ścieżki do foldera pulpitu (SHGetSpecialFolderPath). 3. Uzupełnienie ścieżki o nazwę pliku typu .lnk zawierającego link. 4. Sprawdzenie czy plik już istnieje (CFile::GetStatus). 5. Przekształcenie ścieżki z nazwą pliku na łańcuch w Unikodzie (MultiByteToWideChar). 6. Inicjacja biblioteki COM (CoInitialize). 7. Utworzenie obiektu COM typu Shell Link (CoCreateInstance) i uzyskanie wskaźnika na interfejs IShellLink. 8. Ustawienie ścieżki do pliku, dla którego tworzony jest skrót (IShellLink::SetPath) 9. Uzyskanie wskaźnika na kolejny interfejs IPersistFile obiektu Shell Link (IShellLink::QueryInterface) 10. Zapisanie pliku skrótu do foldera pulpitu (IPersistFile::Save) 11. Zwolnienie interfejsów IPersistFile i IShellLink (Release) oraz CoUninitialize Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 88 Definiowanie komponentu Definiowanie interfejsu Makra i typy danych stosowane w definicjach Implementacja interfejsu Użycie biblioteki MFC Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 89 Definiowanie interfejsu Każdy interfejs komponentu COM musi być bezpośrednio lub pośrednio wyprowadzony z interfejsu IUnknown. Interfejs jest definiowany w języku IDL (Microsoft Interface Definition Language). Przykład: import "unknwn.idl"; // definicja interfejsu IUnknown [ object, uuid(4411B7FE-EE28-11ce-9054-080036F12502) ] interface ISome : IUnknown { HRESULT SomeMethod(void); }; [ object, uuid(4411B7FD-EE28-11ce-9054-080036F12502) ] interface ISomeOther : ISome { HRESULT SomeOtherMethod([in]long l); }; Definicja interfejsu zapisywana jest w pliku .idl Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 90 Definiowanie interfejsu w VC++ W SDK VC++ dostępna jest klasa IUnknown (plik nagłówkowy unknown.h) reprezentująca interfejs IUnknown. Z klasy tej można wyprowadzić własny interfejs stosując składnię C++, np.: // {15038B10-3D3E-11ce-9EE5-00AA004231BF} DEFINE_GUID(IID_IPrintInterface, 0x15038b10, 0x3d3e, 0x11ce, 0x9e, 0xe5, 0x0, 0xaa, 0x0, 0x42, 0x31, 0xbf); class IPrintInterface : public IUnknown { public: // Standard IUnknown interface functions virtual HRESULT QueryInterface(REFIID riid, LPVOID ppvObj)=0; virtual ULONG AddRef(void) = 0; virtual ULONG Release(void) = 0; // This interface virtual HRESULT PrintObject(void) = 0; }; Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 91 Makra i typy danych stosowane w definicjach typedef struct _GUID { unsigned long Data1; // 32 bity unsigned short Data2; // 16 bitów unsigned short Data3; // 16 bitów byte Data4[ 8 ]; // 8*8 = 64 bity } GUID; // definicja Globally Unique Identifier, 128 bitów typedef GUID IID - identyfikator interfejsu; synonim GUID typedef IID* LPIID; - wskaźnik daleki na ID interfejsu #define REFIID const IID& - referencja na identyfikator interfejsu typedef GUID CLSID - identyfikator co-klasy; synonim GUID typedef CLSID* LPCLSID; - wskaźnik daleki na ID co-klasy typedef long HRESULT; - typ wyniku funkcji COM; synonim long Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 92 Konwencja wywoływania metod interfejsu W Microsoft C++ wprowadzono kilkanaście dodatkowych słów kluczowych (rozpoczynających się od __ ), które służą do sterowania kompilacją. Słowo kluczowe __stdcall określa sposób wywołania wymagany przez funkcje Win32 API. class IPrintInterface : public IUnknown { public: // Standard IUnknown interface functions virtual HRESULT __stdcall QueryInterface(REFIID riid, LPVOID ppvObj)=0; virtual ULONG __stdcall AddRef(void) = 0; virtual ULONG __stdcall Release(void) = 0; // This interface virtual HRESULT __stdcall PrintObject(void) = 0; }; Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 93 Makrodefinicja STDMETHODCALLTYPE Definicja: #ifdef _WIN32 // Win32 doesn't support __export #define STDMETHODCALLTYPE __stdcall #else #define STDMETHODCALLTYPE __export __stdcall #endif class IPrintInterface : public IUnknown { public: // Standard IUnknown interface functions virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID ppvObj)=0; virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0; virtual ULONG STDMETHODCALLTYPE Release(void) = 0; // This interface virtual HRESULT STDMETHODCALLTYPE PrintObject(void) = 0; }; Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 94 Makra STDMETHODIMP_ oraz STDMETHODIMP #define STDMETHODIMP HRESULT STDMETHODCALLTYPE #define STDMETHODIMP_(type) type STDMETHODCALLTYPE Pierwsze makro definiuje konwencję i wynik wywołania HRESULT, drugie podany typ wyniku. Przykład: class IPrintInterface : public IUnknown { public: // Standard IUnknown interface functions virtual STDMETHODIMP QueryInterface(REFIID riid, LPVOID ppvObj)=0; virtual STDMETHODIMP_(ULONG) AddRef(void) = 0; virtual STDMETHODIMP_(ULONG) Release(void) = 0; // This interface virtual STDMETHODIMP PrintObject(void) = 0; }; Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 95 Makra STDMETHOD_ i STDMETHOD #define STDMETHOD(method) virtual HRESULT STDMETHODCALLTYPE method #define STDMETHOD_(type,method) virtual type STDMETHODCALLTYPE method Pierwsza wersja definiuje funkcję wirtualną interfejsu, która zwraca wynik typu HRESULT. Druga definiuje funkcję, która zwraca wynik wskazanego typu. class IPrintInterface : public IUnknown { public: // Standard IUnknown interface functions STDMETHOD(QueryInterface)(REFIID riid, LPVOID ppvObj)=0; STDMETHOD_(ULONG, AddRef)(void) = 0; STDMETHOD_(ULONG, Release)(void) = 0; // This interface STDMETHOD(PrintObject)(void) = 0; }; Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 96 Makrodefinicja PURE Definiuje czystą funkcję wirtualną: #define PURE = 0 class IPrintInterface : public IUnknown { public: // Standard IUnknown interface functions STDMETHOD(QueryInterface)(REFIID riid, LPVOID ppvObj) PURE; STDMETHOD_(ULONG, AddRef)(void) PURE; STDMETHOD_(ULONG, Release)(void) PURE; // This interface STDMETHOD(PrintObject)(void) PURE; }; Przykład: SimpleComServ, projekt: MsgBoxServ, plik: ISimpleMsgBox.h Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 97 Implementacja interfejsu W C++ interfejs jest implementowany przez klasę. Przykład: class CPrintObject : public IPrintInterface { public: CPrintObject() : refCount(0) {} // konstruktor virtual ~CPrintObject() {} // destruktor // Standardowe funkcje interfejsu IUnknown virtual STDMETHODIMP QueryInterface(REFIID riid, LPVOID ppvObj); virtual STDMETHODIMP_(ULONG) AddRef(void); virtual STDMETHODIMP_(ULONG) Release(void); // Funkcje interfejsu IPrintInterface virtual STDMETHODIMP PrintObject(void); Przykład: protected: CPrintObject(); Projekt: MsgBoxServ ULONG refCount; // licznik referencji SimpleMsgBoxImpl.h }; Metody już nie są czystymi funkcjami wirtualnymi i muszą zostać zdefiniowane. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 98 Definicja funkcji AddRef i Release Funkcje AddRef i Release zarządzają licznikiem referencji. Każde użycie interfejsu powoduje zwiększenie stanu licznika (wywołanie AddRef), a kiedy już nie jest potrzebny – aplikacja klienta ma obowiązek wywołać funkcję Release. STDMETHODIMP_(ULONG) CPrintObject::AddRef(void) { return ++refCount; // zwiększa licznik referencji } STDMETHODIMP_(ULONG) CPrintObject:: Release(void) { if (--refCount == 0) delete this; // usunięcie obiektu !!! return refCount; } Przykład – projekt: MsgBoxServ, plik: SimpleMsgBoxImpl.cpp Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 99 Funkcja QueryInterface Zadaniem funkcji jest zwrócenie wskaźnika na interfejs o podanym identyfikatorze GUID. Przykład użycia funkcji (założenie: pUnk jest wskaźnikiem na obiekt IUnknown): IPrintInterface* pPrint = NULL; if (pUnk->QueryInterface(IID_IPrintInterface, (void**)&pPrint) == NOERROR) { pPrint->PrintObject(); pPrint->Release(); // release pointer obtained via QueryInterface ! } Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 100 Definicja QueryInterface STDMETHODIMP CPrintObject::QueryInterface(REFIID iid, void FAR* FAR* ppvObj) { if (iid == IID_IUnknown || iid == IID_IPrintInterface) { *ppvObj = this; AddRef(); // zwiększenie licznika referencji return NOERROR; } return E_NOINTERFACE; // zgłoszenie błędu } Przykład – projekt: MsgBoxServ, plik: SimpleMsgBoxImpl.cpp Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 101 Klasa CCmdTarget z biblioteki MFC Klasa zawiera standardową implementację IUnknown (licznik referencji modyfikowany przez AddRef i Release oraz definicję QueryInterface) class CPrintObject : public CCmdTarget { public: // definicja składowych klasy ... protected: DECLARE_INTERFACE_MAP() BEGIN_INTERFACE_PART(PrintObject, IPrintInterface) STDMETHOD_(void, PrintObject)(); END_INTERFACE_PART(PrintObject) }; Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 102 Makra BEGIN_ i END_INTERFACE_PART #define BEGIN_INTERFACE_PART(localClass, baseClass) \ class X##localClass : public baseClass \ { \ public: \ STDMETHOD_(ULONG, AddRef)(); \ STDMETHOD_(ULONG, Release)(); \ STDMETHOD(QueryInterface)(REFIID iid, LPVOID* ppvObj); \ #define END_INTERFACE_PART(localClass) \ } m_x##localClass; \ friend class X##localClass; \ Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 103 Komponenty .NET Struktura platformy .NET Interfejsy w .NET Definiowanie właściwości komponentów Delegacyjny model obsługi zdarzeń Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 104 Struktura platformy .NET Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 105 Biblioteki klas Biblioteka klas, dostępna w pakiecie .NET Framework SDK, składa się z klas, interfejsów i stałych. Elementy biblioteki są pogrupowane w hierarchiczną strukturę przestrzeni nazw (ang. namespace). Biblioteki klas umożliwiają dostęp do usług systemowych i stanowią podstawę tworzenia aplikacji, komponentów i kontrolek .NET. Zadaniem przestrzeni nazw jest zawężenie zakresu, w którym obowiązują nazwy typów. Dzięki temu mogą istnieć dwie klasy o tej samej nazwie pod warunkiem, że są zdefiniowane w różnych przestrzeniach nazw. Podstawowe klasy zgrupowane są w przestrzeni System. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 106 Przestrzeń nazw System Przestrzeń nazw System zawiera klasy podstawowe oraz klasy bazowe, które definiują najczęściej używane referencyjne i skalarne typy danych, zdarzenia, procedury obsługi zdarzeń, interfejsy, atrybuty i klasy przeznaczone do obsługi wyjątków. Inne klasy tej przestrzeni nazw udostępniają usługi związane z konwersją typów danych, manipulowaniem parametrami metod, obliczeniami i stałymi matematycznymi, zdalnym i lokalnym przekazywaniem wywołań, zarządzaniem środowiskiem wykonywania aplikacji oraz nadzorowaniem zarządzanego i niezarządzanego kodu. Jedną ze składowych jest kolejna przestrzeń o nazwie System.ComponentModel, zawierająca klasy i interfejsy pomocne w tworzeniu komponentów i kontrolek. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 107 Przestrzeń System.ComponentModel Przestrzeń System.ComponentModel udostępnia klasy (135 klas) i interfejsy (27) zapewniające funkcjonowanie komponentów i kontrolek zarówno w trybie projektowania (design-time), jaki w trybie uruchomieniowym (run-time). Podstawowe klasy to: Component i Container. Podstawowe interfejsy to: IComponent i IContainer. Pozostałe klasy umożliwiają implementację atrybutów i deskryptorów, konwersję typów (TypeConverter), dowiązanie do źródeł danych oraz licencjonowanie komponentów (License, LicenseManager, LicenseProvider i LicenseProviderAttribute). Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 108 Komponent w .NET Z punktu widzenia programisty Komponent w .NET jest klasą, która w sposób bezpośredni lub pośredni implementuje interfejs System.ComponentModel.IComponent Co to jest interfejs? Jak klasa implementuje interfejs? Jak definiuje się właściwości komponentu? Jak obsługiwać zdarzenia? Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 109 C++/CLI C++/CLI (Common Language Infrastructure) to język programowania oparty na C++, umożliwiający programowanie aplikacji korzystających za wspólnego środowiska uruchomieniowego CLR, jak i bez użycia CLR. C++ zarządzany C++ natywny C++ natywny Biblioteka klas .NET Biblioteka MFC CLR System operacyjny Sprzęt (hardware) Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 110 Kod natywny i zarządzany W kodzie natywnym, obiekty tworzy się przy użyciu operatora new. Tak utworzone obiekty dostępne są przez wskaźnik. Po wykorzystaniu obiektu, trzeba pamiętać o usunięciu go z pamięci (operator delete). std::string* stdstr; // deklaracja wskaźnika stdstr = new std::string("String niezarządzany"); ... delete stdstr; // usunięcie łańcucha W kodzie zarządzanym, obiekty tworzy się przy użyciu operatora gcnew. Tak utworzone obiekty dostępne są przez odniesienie. Obiekty wykorzystane są automatycznie usuwane z pamięci (odśmiecanie). System::String^ clrstr; // deklaracja odniesienia clrstr = gcnew System::String("String zarządzany"); ... Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 111 Klasa zarządzana Klasa poprzedzona słowem ref class (jedno słowo kluczowe) staje się klasą zarządzaną (garbage-collected). Czas życia obiektu tej klasy kontrolowany jest przez CLR. Nie ma potrzeby jawnego wywołania operatora delete. Przykład: ref class Test { public: Test():i(0){} private: int i; }; // Test Klasa zarządzana nie ma domyślnego konstruktora kopiującego ani domyślnego operatora przypisania. Parametry funkcji (również konstruktorów) nie mogą mieć wartości domyślnych. Można definiować właściwości (słowo kluczowe property). void main () { while(true) { // pętla bez końca Test^ tst = gcnew Test; } // tworzone obiekty są automatycznie usuwane } Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 112 Klasa wartości Klasa poprzedzona słowem value class (jedno słowo kluczowe) jest przeznaczona do grupowania pewnej liczby danych w jedną całość. Obiekt klasy wartości może być umieszczony zarówno na stosie (jak zmienne typów standardowych) jak i na stercie CLR (operator gcnew). Przykład: value class Wynik { public: Wynik(double czas, double Par) : t(czas),par(Par) {} private: double t; // czas od początku pomiaru double par; // wartość mierzonego parametru }; // Wynik void main () { Wynik wynik(0.5, 0.342); } Wprowadzenie Beans COM NET XML CORBA // umieszczenie na stosie © H.Budzisz 113 Interfejs w .NET Interfejs zawiera deklaracje metod, zdarzeń i właściwości, ale nie może zawierać ich implementacji. Nie może również zawierać deklaracji zmiennych (pól klasy) – z wyjątkiem pól statycznych. Oprócz pól statycznych może zawierać także deklaracje innych składowych statycznych (metod, zdarzeń lub właściwości). Wszystkie składowe interfejsu mają publiczne prawa dostępu. Interfejs podlega dziedziczeniu. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 114 Deklaracja interfejsu prawa_dostępu interface class nazwa {}; prawa_dostępu interface class nazwa : interfejs_bazowy {}; gdzie: prawa_dostępu - prawa dostępu do interfejsu spoza podzespołu .NET (.NET assembly): public lub private (domyślne) Przykład: public interface class Interface_A { void Function_1(); void Function_2(); }; // Interface_A Nazwy interfejsów przyjęło się rozpoczynać od litery I, np.: IContainer. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 115 Implementacja interfejsu Implementacja interfejsu jest realizowana w oparciu o składnię dziedziczenia – przykład: public ref class MyClass : public Interface_A { virtual void Function_1() { Console::WriteLine(“Function_1”); } virtual void Function_2() { Console::WriteLine(“Function_2”); } }; // MyClass Ponieważ interfejs jest w Visual C++ typem zarządzanym, klasa implementująca interfejs musi być również klasą zarządzaną. Klasa może implementować wiele interfejsów. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 116 Właściwości Definiowanie właściwości Funkcje dostępowe Właściwości proste Właściwości indeksowane Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 117 Rozwiązanie klasyczne – pola klasy Pola klasy przechowują dane reprezentujące atrybuty obiektu opisywanego przez tą klasę. class Okno { public: void Szer(int nowa_szer) { szer = nowa_szer; Rysuj(); } int Szer(void) { return szer; } // podobnie dla pola wys private: void Rysuj() { ... } int szer, wys; }; // Okno Wprowadzenie Beans COM NET XML CORBA // ustaw szerokość i odrysuj // pobierz szerokość © H.Budzisz 118 Rozwiązanie z użyciem właściwości Definicja właściwości zawiera standardowe funkcje dostępowe (akcesory), o z góry określonych nazwach: set i get. Przykład: public ref class Okno { public: property int Szer { void set(int nowa_szer) { // ustaw szerokość i odrysuj szer = nowa_szer; Rysuj(); } int get(void) { // pobierz szerokość return szer; } } // szer // podobnie dla właściwości Wys private: void Rysuj() { ... } int szer, wys; }; // Okno Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 119 Definiowanie właściwości Właściwość definiuje się w klasie lub interfejsie przy użyciu słowa kluczowego property. modyfikator property typ nazwa { <deklaracja akcesorów> } // brak średnika gdzie: modyfikator – opcjonalny modyfikator static lub virtual, Przykład: public ref class Okno { public: property int Szer { // deklaracja akcesorów } property int Wys { // deklaracja akcesorów } }; Właściwość jest objęta prawami dostępu na takich samych zasadach jak inne składowe klasy lub interfejsu. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 120 Funkcje dostępowe (akcesory) Właściwość może zawierać funkcję pobierającą (getter), funkcję ustawiającą (setter) lub obie funkcje. Składnia deklaracji: modyfikator void set(typ); modyfikator typ get(); gdzie: modyfikator – opcjonalny modyfikator static lub virtual. Typ argumentu settera i wynik gettera muszą być jednakowe i zgodne z typem właściwości. Setter nie zwraca żadnego wyniku. Getter nie ma natomiast argumentów. Poza tymi ograniczeniami definicje funkcji mogą być dowolne. Deklaracja akcesorów może być poprzedzona deklaracją praw dostępu. Przy braku deklaracji, prawa dostępu są takie same jak dla właściwości. Prawa dostępu dla settera i gettera mogą być różne, ale w żadnym przypadku nie mogą rozszerzać praw dostępu określonych dla właściwości. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 121 Przykład definiowania akcesorów Definicja akcesorów może być połączona z deklaracją (jak w poprzednim przykładzie) lub znajdować się poza klasą, z zastosowaniem nazwy kwalifikowanej (tak jak dla zwykłych metod). Przykład: public ref class Okno { public: property int Szer { void set(int); int get(void); } // podobnie dla właściwości Wys private: void Rysuj() { ... } int szer, wys; }; // Okno void Okno::Szer::set(int nowa_szer) { szer = nowa_szer; Rysuj(); } int Okno::Szer::get(void) { return szer; } Wprowadzenie Beans COM NET XML CORBA // ustaw szerokość i odrysuj // pobierz szerokość © H.Budzisz 122 Właściwości proste Deklaracja właściwości, dla której setter kopiuje wartość do pomocniczej zmiennej prywatnej, a getter pobiera wartość z tej samej zmiennej, może być uproszczona do postaci: modyfikator property typ nazwa; Przykład: public ref class Vector { public: property double x; property double y; property double z; }; Kompilator tworzy zmienną pomocniczą oraz domyślne akcesory. Nie ma bezpośredniego dostępu do automatycznie utworzonej zmiennej pomocniczej. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 123 Właściwości indeksowane Właściwość indeksowana reprezentuje strukturę danych, w której dane dostępne są poprzez operator indeksowania [ ]. Właściwości indeksowanych używa się zwykle do udostępniania elementów tablicy zadeklarowanej wewnątrz klasy lub składowych wewnętrznych kontenerów. Składnia: modyfikator property typ_właściwości nazwa[typ_indeksu] { modyfikator void set(typ_indeksu, typ_właściwości); modyfikator typ_właściwości get(typ_indeksu); } // brak średnika W kodzie klienta, właściwość z indeksem jest używana jak zwykła tablica. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 124 Zdarzenia Delegacyjny model obsługi zdarzeń Deklarowanie delegata Deklaracja zdarzenia Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 125 Delegacyjny model obsługi zdarzeń Subskrybenci Subskrybent 1 funkcja Sub1A delegat źródło zdarzenia Subskrybent 2 zgłoszenie zdarzenia invocation funkcja Sub2B list Subskrybent3 funkcja Sub3C Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 126 Deklaracja delegata Delegat jest typem obiektowym. Do deklaracji delegata nie używa się jednak żadnej klasy bibliotecznej, ale słowa kluczowego delegate, według składni: prawa_dostępu delegate deklaracja_funkcji; Przykład: using namespace System; void Drukuj(char* tekst) { Console::WriteLine("Wydruk - {0}\n", gcnew String(tekst)); } void Zapisz(char* tekst) { Console::WriteLine("Zapis - {0}\n", gcnew String(tekst)); } delegate void Operacja(char*); // deklaracja delegata void main() { Operacja^ opr; opr += gcnew Operacja(&Drukuj); opr += gcnew Operacja(&Zapisz); opr("wywołanie z delegata"); } Wynik działania programu: Wydruk – wywołanie z delegata Zapis – wywołanie z delegata Typ argumentów i wyniku w deklaracji delegata i deklaracji dodawanych funkcji muszą być zgodne. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 127 Rejestrowanie metod klasy using namespace System; ref class Dokument { public: void Czytaj(String^ nazwa) { Console::WriteLine("Czytaj dokument: {0}\n", nazwa); } void Drukuj(String^ nazwa) { Console::WriteLine("Drukuj dokument: {0}\n", nazwa); } void Zapisz(String^ nazwa) { Console::WriteLine("Zapisz dokument: {0}\n", nazwa); } }; // Dokument ref class Obraz { public: static void Rysuj(String^ nazwa) { Console::WriteLine("Rysuj obraz: {0}\n", nazwa); } }; // Obraz delegate void Operacja(String^); void main() { Dokument^ dok = gcnew Dokument(); Operacja^ opr = gcnew Operacja(dok, &Dokument::Czytaj); opr += gcnew Operacja(dok, &Dokument::Drukuj); opr += gcnew Operacja(dok, &Dokument::Zapisz); opr += gcnew Operacja(&Obraz::Rysuj); // funkcja statyczna Console::WriteLine("Operacje:\n"); opr(gcnew String("Rysunek")); } Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 128 Deklaracja zdarzenia W C++/CLI koncepcja delegacyjnego modelu obsługi zdarzeń została zrealizowana poprzez deklarację zdarzenia (słowo kluczowe event) i powiązanie go z delegatem. modyfikator event nazwa_delegatu ^ nazwa_zdarzenia; Wersja rozszerzona deklaracji umożliwia zdefiniowanie sposobu realizacji metod add, remove i raise. modyfikator event nazwa_delegatu ^ nazwa_zdarzenia { modyfikator typ_zwracany add (nazwa_delegatu ^ nazwa); modyfikator typ_zwracany remove(nazwa_delegatu ^ nazwa); modyfikator typ_zwracany raise(parametry); } // event block Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 129 Przykład using namespace System; // deklaracja delegatów delegate void ZoomEventHandler(double); delegate void PrintEventHandler(String^); int main() { // utworzenie żródła i subskrybenta EventSource ^ EventSourceObject = gcnew EventSource(); // klasa źródło zdarzeń ref class EventSource { public: event ZoomEventHandler^ OnZoom; event PrintEventHandler^ OnPrint; void FireEvents() { // symulacja zdarzeń OnZoom(1.5); OnPrint("diagram"); } }; // klasa subskrybent ref class EventReceiver { public: void OnZoomClick(double scale) { Console::WriteLine(„Skala {0}", scale); } void OnPrintClick(String^ document) { Console::WriteLine(“Drukuj {0}", document); } }; Wprowadzenie Beans COM NET XML CORBA EventReceiver^ EventReceiverObject = gcnew EventReceiver(); // rejestracja funkcji obsługujących EventSourceObject->OnZoom += gcnew ZoomEventHandler(EventReceiverObject, &EventReceiver::OnZoomClick); EventSourceObject->OnPrint += gcnew PrintEventHandler(EventReceiverObject, &EventReceiver::OnPrintClick); // wygeneruj zdarzenia EventSourceObject->FireEvents(); } Wynik na ekranie: Skala 1.5 Drukuj diagram © H.Budzisz 130 Przykład: ExcelIntegration Przykład demonstruje następujące konstrukcje programowe: Definiowanie klasy wartości Wynik do przechowywania dwóch pól typu double. Definiowanie klasy referencyjnej Pomiar symulującej przeprowadzanie pomiarów. Wynik każdego pomiaru przechowywany jest w obiekcie klasy Wynik. Po każdym pomiarze generowane jest zdarzenie PomiarEvent, obsługiwane przez delegata PomiarEventHeandler. Tworzenie graficznego interfejsu użytkownika – klasa Form1. W obsłudze zdarzenia Click przycisku pomiarBtn, tworzony jest obiekt klasy Pomiar, rejestrowana jest funkcja OnPomiar obsługująca zdarzenie PomiarEvent i wywoływana jest funkcja wykonująca symulację pomiarów. Definiowanie funkcji odpowiedzi OnPomiar, która kolejne wyniki umieszcza na liście przewijanej – pomiarListBox. Wywołanie funkcji transfer z klasy Pomiar, powoduje utworzenie obiektu komponentu Excel i transfer wyników do arkusza kalkulacyjnego. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 131 Definiowanie komponentu Komponent w .NET jest klasą, która w sposób bezpośredni lub pośredni implementuje interfejs System.ComponentModel.IComponent Interfejs IComponent definiuje standard komponentu .NET. Nowy komponent definiuje się zwykle w oparciu o klasę Component, która implementuje interfejs IComponent, a ponadto definuje typową dla komponentu funkcjonalność (np.: metodę Dispose zwalniającą przydzielone komponentowi zasoby). Schemat definicji komponentu: public ref class NowyKomponent : public System::ComponentModel::Component { } Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 132 Komponenty a kontenery Komponenty zwykle umieszcza się w kontenerze (w obiekcie klasy Conteiner (System::ComponentModel::Container). Użycie kontenera (chociaż nie jest obowiązkowe) ułatwia wzajemną komunikację pomiędzy komponentami, zarządzanie zasobami przydzielonymi komponentom oraz umożliwia integrację komponentu ze środowiskiem programistycznym (design-time environment). Kreator aplikacji typu Windows Forms Applications tworzy klasę formularza w oparciu o klasę biblioteczną Form. W klasie tej tworzony jest kontener components, przeznaczony do przechowywania komponentów. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 133 Szkielet aplikacji typu Windows Forms public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); } protected: ~Form1() { if (components) delete components; } // usunięcie kontenere private: System::ComponentModel::Container ^components; // deklaracja odniesienia void InitializeComponent(void) { this->components = gcnew System::ComponentModel::Container(); } }; Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 134 Kontener w komponencie Szkielet klasy definiującej komponent najłatwiej można wygenerować przy użyciu odpowiedniego kreatora (Project/Add Class…/Component Class). Konstruktor klasy zawiera operację dodania komponentu do kontenera zewnętrznego: NowyKomponent(System::ComponentModel::IContainer ^container) { container->Add(this); // inne operacje do wykonania przez konstruktor } W klasie jest również definiowany kontener wewnętrzny do przechowania innych komponentów. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 135 Szkielet definicji komponentu public ref class NowyKomponent : public System::ComponentModel::Component { public: NowyKomponent(void) { InitializeComponent(); } NowyKomponent(System::ComponentModel::IContainer ^container) { container->Add(this); InitializeComponent(); } protected: ~NowyKomponent() { if (components) delete components; } private: System::ComponentModel::Container ^components; void InitializeComponent(void) { components = gcnew System::ComponentModel::Container(); } }; Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 136 Przykład: komponent HiResTimer Zadaniem komponentu jest pomiar czasu pomiędzy wywołaniem funkcji składowych Start() i Stop(). Właściwości statyczne Counter i TicksPerSecond zwracają odpowiednio stan i częstotliwość licznika procesora, udostępniane przez funkcje API QueryPerformanceCounter i QueryPerformanceFrequency. Częstotliwość licznika jest dla danego procesora stała i dlatego zostaje odczytana tylko jeden raz i zapamiętana w zmiennej statycznej frequency. Właściwości Seconds, Milliseconds i Microseconds zwracają w odpowiednich jednostkach interwał czasu jaki upłynął pomiędzy Start () i Stop(), a właściwość Ticks liczbę tyknięć licznika. Solution: PerformanceTesting; Project: Timer. Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 137 Komponent w trybie projektowania Właściwości i zdarzenia zdefiniowane w komponencie można w trybie projektowania odczytywać i ustawiać w przeglądarce właściwości (property browser). Procesem tym można sterować przy użyciu odpowiednich atrybutów (design-time attributes). Atrybuty definiowane są przez klasy biblioteczne (np.: CategoryAttribute) wyprowadzone z klasy System::Attribute. Można też zdefiniować własne klasy opisujące niestandardowe atrybuty. W definicji komponentu atrybuty umieszcza się według składni: [nazwa_atrybutu(argumenty), nazwa_atrybutu(argumenty), … ] Przykład: [Category("Format"), Description("Returns time interval")] Atrybut Category określa przynależność do grupy atrybutów w przeglądarce. Atrybut Description definiuje tekst wyświetlany na dole okienka przeglądarki. Atrybut Editor umożliwia przypisanie do właściwości odpowiedniego edytora (np.: do wyboru koloru) Wprowadzenie Beans COM NET XML CORBA © H.Budzisz 138