Systemy rozproszone W. Bartkiewicz Uniwersytet Łódzki Katedra Informatyki W. Bartkiewicz Systemy rozproszone Wykład 5. Komunikacja międzyprocesowa z wykorzystaniem usług warstwy pośredniej – zdalne obiekty Część 2
Ewolucja COM Katedra Informatyki
COM Interfejsy Katedra Informatyki COM korzysta z modelu obiektu zdalnego. Obiekty COM umieszczane mogą być: w procesie klienta, w procesie na tej samej maszynie co klient, w procesie na maszynie zdalnej. Podobnie jak CORBA, COM skupia się na realizacji interfejsów. Obiekt COM jest generalnie realizacją interfejsu. Jeden obiekt może realizować kilka interfejsów. Do definiowania interfejsów wykorzystywany jest język opisu interfejsu Microsoft IDL (MIDL). W przeciwieństwie do CORBY interfejsy COM mają charakter binarny. Są to tablice wskaźników do funkcji będących jego częściami.
COM Interfejsy binarne i zależne od języka Katedra Informatyki Interfejsy binarne Wskaźniki do funkcji Kompilator z IDL na interfejs Specyfikacja IDL Tablica funkcji Interfejsy zdefiniowane w języku Definicje klas Javy Definicje klas C++ Kompilator z IDL na język Kompilator konkretnego języka Prototypy C
COM Interfejsy Katedra Informatyki W środowisku CORBA standaryzacja interfejsów odbywa się na poziomie źródłowym, tzn. standardowy charakter ma IDL oraz odwzorowanie jego specyfikacji na dany język programowania. W COM standaryzacja odbywa się na poziomie binarnym. Kompilator interfejsów generuje standardową postać binarną interfejsu, która wykorzystana może być w dowolnym języku programowania. Każdy interfejs COM ma jednoznaczny 128 bitowy identyfikator, zwany identyfikatorem interfejsu (IID – Interface Identifier). Każdy IID jest globalnie jednoznaczny, tzn. nie ma dwu interfejsów o tym samym IID. Generowany jest na podstawie adresu interfejsu sieciowego danego komputera, czasu oraz dużej liczby losowej. Prawdopodobieństwo wygenerowania dwu takich samych IID jest praktycznie zerowe. Każda klasa obiektu COM również posiada jednoznaczny identyfikator klasy (CLSID – Class Identifier), tworzony na tej samej zasadzie co UUID.
COM Interfejsy Katedra Informatyki W środowisku CORBA obiekty funkcjonują w permanentnie działających procesach. W środowisku COM, odmiennie – obiekty mają charakter tymczasowy. Tworzone są z chwilą gdy klient żąda dostępu do obiektu, gdy zabraknie klientów odwołujących się do obiektu, następuje jego likwidacja. Czas życia obiektu COM zarządzany jest na zasadzie zliczania odwołań. Klienci powinni zwiększać licznik odwołań za każdym razem, gdy pobierają nowe odniesienie do obiektu. Gdy odniesienie przestaje być potrzebne, licznik odwołań powinien być przez klientów zmniejszany. Wszystkie obiekty COM realizują ten sam interfejs standardowy IUnknown. Interfejs ten stanowi początkowy uchwyt do obiektu po jego utworzeniu. Zawiera funkcje AddRef i Release, zarządzające licznikiem odwołań. Zawiera funkcję QueryInterface, która pozwala pobrać odniesienie do innego z interfejsów realizowanych przez obiekt COM.
COM Przykładowy interfejs IDL Katedra Informatyki import "unknwn.idl"; [ object, uuid (7223BBFD-8F42-43e7-92D9-5080991112C7) ] interface ISum : IUnknown { HRESULT Sum([in] int x, [in] int y, [out, retval] int* retvl); };
COM Implementacja obiektu COM Katedra Informatyki #include "component.h” //wygenerowany przez MIDL zawiera deklarację interfejsu ISum w C++ const CLSID CLSID_MathComp = {0xcec2b111, 0xa70f, 0x41ac, {0xab, 0xb9, 0xc, 0x2, 0x43, 0xbe, 0xae,0x49}}; class CMathComp : public ISum { public: ULONG __stdcall AddRef(); ULONG __stdcall Release(); HRESULT __stdcall QueryInterface(REFIID riid, void** ppv); HRESULT __stdcall Sum(int x, int y, int* retvl); CMathComp(): m_cRef(1) { g_cLocks++; } ~CMathComp() { g_cLocks--;} private: ULONG m_cRef; };
COM Implementacja obiektu COM Katedra Informatyki ULONG CMathComp::AddRef() { return ++m_cRef; } ULONG CMathComp::Release() { if ( --m_cRef != 0 ) return m_cRef; delete this; return 0; HRESULT CMathComp::QueryInterface(REFIID riid, void** ppv) { if ( riid == IID_IUnknown ) *ppv = (IUnknown*) this; else if ( riid == IID_ISum ) *ppv = (ISum*)this; else { *ppv = NULL; return E_NOINTERFACE;} AddRef(); return S_OK; HRESULT CMathComp::Sum(int x, int y, int* retvl) { *retvl = x + y;
COM Interfejsy Katedra Informatyki
COM Fabryki klas Katedra Informatyki Dla tworzenia obiektów COM, powłoka komponentu powinna dostarczać tzw. fabryki klas, tj. specjalnego obiektu COM, który utworzy właściwy obiekt. Fabryki klas implementują standardowy interfejs COM IClassFactory. Głównym elementem tego interfejsu jest funkcja CreateInstance, której zadaniem jest stworzenie obiektu.
COM Implementacja fabryki klas Katedra Informatyki class CFactory: public IClassFactory { public: ULONG __stdcall AddRef(); ULONG __stdcall Release(); HRESULT __stdcall QueryInterface(REFIID riid, void** ppv); HRESULT __stdcall CreateInstance(IUnknown* pUnknownOuter, REFIID riid, void** ppv); HRESULT __stdcall LockServer(BOOL bLock); CFactory(): m_cRef(1) { g_cLocks++; } ~CFactory() { g_cLocks--; } private: ULONG m_cRef; };
COM Implementacja fabryki klas Katedra Informatyki ULONG CFactory::AddRef() { return ++m_cRef; } ULONG CFactory::Release() { if ( --m_cRef != 0 ) return m_cRef; delete this; return 0; HRESULT CFactory::QueryInterface(REFIID riid, void** ppv) { if ( riid == IID_IUnknown ) *ppv = (IUnknown*) this; else if ( riid == IID_IClassFactory ) *ppv = (IClassFactory*) this; else { *ppv = NULL; return E_NOINTERFACE; AddRef(); return S_OK;
COM Implementacja fabryki klas Katedra Informatyki HRESULT CFactory::CreateInstance( IUnknown* pUnknownOuter, REFIID riid, void** ppv) { if ( pUnknownOuter ) return CLASS_E_NOAGGREGATION; CMathComp* pMathComp = new CMathComp(); if ( !pMathComp ) return E_OUTOFMEMORY; HRESULT hr = pMathComp->QueryInterface(riid, ppv); pMathComp->Release(); return hr; } HRESULT CFactory::LockServer(BOOL bLock) { if ( bLock ) g_cLocks++; else g_cLocks--; return S_OK;
COM Działanie klienta Katedra Informatyki Klient tworzy obiekty COM, wykorzystując funkcję CoCreateInstance. Funkcja ta lokalizuje powłokę komponentu (serwer), pobiera fabrykę klas komponentu i tworzy obiekt COM. Klient oddziaływuje na obiektach COM a wykorzystaniem wskaźników do interfejsów implementowanych przez te obiekty, pobieranych z wykorzystaniem funkcji QueryInterface. Komponent dba Każde wywołanie QueryInterface zwiększa licznik odwołań (dba o to sam obiekt COM). Jeśli klient samodzielnie powiela uchwyt di obiektu, powinien ręcznie wywołać AddRef. Dla każdego wywołania QueryInterface lub AddRef, jeśli uchwyt do obiektu nie jest już potrzebny, klient powinien wywołać Release.
COM Implementacja klienta Katedra Informatyki #include "component.h" // Wygenerowany Przez MIDL const CLSID CLSID_MathComp = {0xcec2b111, 0xa70f, 0x41ac, {0xab, 0xb9, 0xc, 0x2, 0x43, 0xbe, 0xae, 0x49}}; void main() { IUnknown* pUnknown; ISum* pSum; HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if ( FAILED(hr) ) { cout<<"Blad CoInitializeEx. "<<endl; return; } hr = CoCreateInstance(CLSID_MathComp, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**) &pUnknown); if ( FAILED(hr) ) { cout<<"Blad CoCreateInstance. "<<endl; return; } hr = pUnknown->QueryInterface(IID_ISum, (void**) &pSum); if ( FAILED(hr) ) { cout<<"IID_ISum nie jest obslugiwany."<<endl; return;} pUnknown->Release(); int sum; hr = pSum->Sum(2,3, &sum); if ( SUCCEEDED(hr) ) cout<<"2+3="<<sum<<endl; pSum->Release(); CoUninitialize(); }
COM Rejestr i SCM Katedra Informatyki Aby naprawdę uaktywnić obiekt, tzn. zapewnić jego utworzenie i umieszczenie w procesie, skąd może akceptować wywołania metod, COM korzysta z rejestru Windows oraz specjalnego procesu nazywanego kontrolerem usług (SCM – Service Control Manager). Każdy komponent COM musi zostać zarejestrowany w kluczu HKEY_CLASSES_ROOT\CLSID pod swoim CLSID. Opis w rejestrze obejmuje między innymi nazwę pliku (DLL lub EXE) zawierającego implementację klasy. Funkcja CoCreateInstance znajduje CLSID komponentu w rejestrze, konkretyzuje serwer (powłokę) obiektu, tzn. ładuje odpowiedni plik DLL lub uruchamia plik EXE i udostępnia komponent do obsługi wywołań klienta. Dla komponentów zdalnych środowisko COM (a właściwie w tym przypadku DCOM) przekazuje CLSID obiektu lokalnemu SCM, który w lokalnym rejestrze odnajduje maszynę serwera, oraz przekazuje CLSID kontrolerowi SCM na tej maszynie.
COM Odwołania zdalne Katedra Informatyki Dla klienta DCOM kontakt z obiektem zewnątrzprocesowym (lokalnym lub zdalnym) wygląda tak samo jak podczas wykonywania obiektów COM wewnątrzprocesowych we własnej przestrzeni adresowej. Po konkretyzacji obiektu zdalnego, środowisko DCOM przetacza jego interfejs na serwerze i zwraca klientowi, gdzie jest on przetaczany odwrotnie do pośrednika realizującego odwołania do obiektu. Po udostępnieniu interfejsu klientowi, środowisko COM praktycznie nie wykonuje żadnych dodatkowych operacji. Odwołania do metod obiektu zdalnego realizowane są niemalże poprzez natywne wywołania Windows RPC. Parametry i wartości powrotne dla wywołań obiektów zdalnych muszą być oczywiście przetaczane. Standardowy kod przetaczania generowany jest przez MIDL. Z plików generowanych przez MIDL należy wygenerować bibliotekę DLL i zarejestrować w opisie komponentu (tzw. szeregowanie standardowe). COM oferuje również możliwości definiowania przetaczania niestandardowego.
COM Odwołania zdalne Katedra Informatyki
COM Odwołania zdalne Katedra Informatyki
COM Biblioteki typów Katedra Informatyki Biblioteka typów (type library) jest odpowiednikiem magazynu interfejsów ze standardu CORBA. Biblioteka typów jest najczęściej kojarzona z aplikacją lub komponentem składającym się z różnych obiektów klas. Kompilowana jest przez MIDL ze specjalnych deklaracji w opisie interfejsu. Zawiera binarną wersję interfejsów klasy COM, a także definiuje ich metody, parametry i zwracane typy. Może być ona przechowywana w komponencie (jak inne zasoby) lub w odrębnym pliku. Jeśli komponent ma korzystać z biblioteki typów, musi być ona zarejestrowana w jego opisie w rejestrze Windows. Biblioteka typów używana jest przede wszystkim do ścisłego określenia sygnatury metody. Narzędzia programowania korzystają z bibliotek typów również w celu pomocy w opracowaniu programu, na przykład wyświetlając w wygodnej postaci interfejsy na ekranie.
COM Dodanie biblioteki typów do interfejsu Katedra Informatyki import "unknwn.idl"; [ object, uuid(10000001-0000-0000-0000-000000000001) ] interface ISum : IUnknown { HRESULT Sum(int x, int y, [out, retval] int* retval); } [ uuid(10000003-0000-0000-0000-000000000001), helpstring("Inside COM+ Component Type Library"), version(1.0) ] library Component importlib("stdole32.tlb"); interface ISum; [ uuid(10000002-0000-0000-0000-000000000001) ] coclass MathComp };
COM Klient korzystający z biblioteki typów Katedra Informatyki #define _WIN32_DCOM //#import "component.tlb" no_namespace //skompil. przez MIDL biblioteka typow #import "component.dll" no_namespace //jeśli biblioteka typow w komponencie #include <iostream.h> void main() { CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); ISumPtr myRef(__uuidof(MathComp)); int result = myRef->Sum(5, 13); cout << "5 + 13 = " << result << endl; myRef = NULL; CoUninitialize(); }
COM Automatyzacja Katedra Informatyki COM umożliwia również dynamiczne wywołania metod obiektów. Obiekty, których zamówienia wywołań mają być budowane w fazie wykonania programu, muszą realizować interfejs standardowy IDispatch. Interfejs ten podobny jest do interfejsu dynamicznych wywołań DII w standardzie CORBA. Obiekty COM realizujące interfejs IDispatch nazywamy obiektami automatyzacji lub automatyzmu (automation objects). Mogą one realizować czysty disp-interfejs, lub udostępniać również własne interfejsy niestandardowe. Mówimy wówczas o tzw. interfejsach dualnych. W obiektach automatyzacji możliwe jest również definiowanie własności. Metody interfejsu IDispatch korzystają dla parametrów ze standardowych typów danych (przede wszystkim typ VARIANT). Mogą więc być przetaczane przy pomocy standardowej biblioteki szeregującej i biblioteki typów, niemal bez żadnej dodatkowej pracy programisty (tzw. szeregowanie z biblioteką typów).
COM Interfejs serwera automatyzacji Katedra Informatyki [ object, uuid(10000001-0000-0000-0000-000000000001), dual ] interface ISum : IDispatch { [id(1)] HRESULT Sum([in] int x, [in] int y, [out, retval] int* retvl); } [id(1)] jest deklaracją tzw. DISPID, identyfikatora metody, który wykorzystywany będzie do dynamicznego jej wywołania. DISPID mogą mieć również właściwości. Musi mieć on charakter jednoznaczny (wszystkie metody i właściwości muszą mieć różne identyfikatory). Obiekt musi odpowiednio implementować interfejs IDispatch, pozwalający na wywołanie metody poprzez podanie jej DISPID oraz zawierający operacje wspomagające wywołania dynamiczne.
COM Interfejs IDispatch Katedra Informatyki interface IDispatch : IUnknown { // Czy obsługujesz informacje o typie? HRESULT GetTypeInfoCount( [out] UINT* pctinfo ); // Zwraca wskaźnik do informacji o typie twojego obiektu. HRESULT GetTypeInfo( [in] UINT iTInfo,[in] LCID lcid, [out] ITypeInfo** ppTInfo); // Zwraca DISPID metody. HRESULT GetIDsOfNames( [in] REFIID riid, [in, size_is(cNames)] LPOLESTR* rgszNames, [in] UINT cNames, [in] LCID lcid, [out, size_is(cNames)] DISPID* rgDispId); // Wywołuje metodę. HRESULT Invoke( [in] DISPID dispIdMember, [in] REFIID riid, [in] LCID lcid, [in] WORD wFlags, [in, out] DISPPARAMS* pDispParams, [out] VARIANT* pVarResult, [out] EXCEPINFO* pExcepInfo, [out] UINT* puArgErr); };
COM Implementacja serwera automatyzacji Katedra Informatyki HRESULT CInsideCOM::GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId) { if(riid != IID_NULL) return DISP_E_UNKNOWNINTERFACE; return DispGetIDsOfNames(m_pTypeInfo, rgszNames, cNames, rgDispId); } HRESULT CInsideCOM::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr) { return DispInvoke(this, m_pTypeInfo, dispIdMember, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr); W większości przypadków funkcje interfejsu IDispatch implementowane są z wykorzystaniem funkcji pomocniczych biblioteki COM, przeszukujących bibliotekę typów. Możliwa jest jednak implementacja niestandardowa.
COM Dynamiczne wywołanie metody Katedra Informatyki IDispatch* pDispatch; OLECHAR* name = L"Sum"; DISPID dispid; pDispatch->GetIDsOfNames(IID_NULL,&name,1,GetUserDefaultLCID(),&dispid); VARIANTARG SumArgsPos[2]; VariantInit(&SumArgsPos[0]); SumArgsPos[0].vt = VT_I4; SumArgsPos[0].lVal = 7; VariantInit(&SumArgsPos[1]); SumArgsPos[1].vt = VT_I4; SumArgsPos[1].lVal = 2; VARIANT result; VariantInit(&result); DISPPARAMS Params1 = { SumArgsPos, NULL, 2, 0 }; if( FAILED(pDispatch->Invoke(dispid, IID_NULL, GetUserDefaultLCID(), DISPATCH_METHOD, &Params1, &result, NULL, NULL)) ) cout << "pDispatch->Invoke() failed" << endl;
COM Punkty połączenia Katedra Informatyki Podstawowy model komunikacyjny COM opiera się na standardowych wywołaniach metod obiektów zdalnych. W niektórych sytuacjach może być on jednak niewystarczający. Jeśli sytuacja wymaga komunikacji dwustronnej, tzn. aby obiekt poinformował klienta o zajściu pewnego zdarzenia i zażądał jego obsłużenia, możliwe jest to dzięki realizacji mechanizmu tzw. punktów połączeń. Schemat działania jest następujący: Obiekt definiuje interfejs komunikacyjny, jakiego wymaga aby przesłać klientowi komunikat o zdarzeniu. Interfejs ten nazywamy źródłowym (source interface) lub wychodzącym (outgoing interface). Klient, który chce otrzymywać zdarzenia implementuje wymagany przez obiekt interfejs źródłowy. Implementację taką nazywamy ujściem (sink). Obiekt z kolei musi implementować standardowe interfejsy COM obsługujące punkty połączeń (najważniejszy to interfejs IConnectionPoint). Interfejs ten zawiera funkcje umożliwiające przekazanie obiektowi przez klienta uchwytu do ujścia (wskaźnika do realizacji interfejsu źródłowego). Klient po utworzeniu obiektu przekazuje mu wskaźnik do swojego ujścia.
COM Punkty połączenia Klient Obiekt dołączalny Interfejs źródłowy Katedra Informatyki Interfejs źródłowy Obiekt dołączalny Ujście Klient
COM Grupowe rozsyłanie zdarzeń Katedra Informatyki
COM Odbieranie zdarzeń od wielu obiektów Katedra Informatyki