Komponentowe systemy rozproszone COM i inni
Komunikacja/komponenty w WINDOWS w ujęciu historycznym 1. Schowek 2. DDE bitowe OLE bitowe OLE OLE 2.0 = COM = ActiveX (1996) bitowe OLE 7. Dcom 8. COM+ = COM + MTS + MSMQ (W2000)
Component Object Model Binarny standard komunikacji Nie jest językiem Nie wymaga konketnego języka Niezależny od języka Interfejsy – standard komunikacji Identyfikacja obiektów i usług (Pół)Automatyczne metody marszalingu danych Wspiera: enkapsulację, dziedziczenie interfejsu i implementacji, polimorfizm Elementy zarządzanie zasobami
Obiektowy model klient - serwer
Rodzaje serwerów COM serwer w procesie=biblioteka dynamiczna InprocServer / InprocServer32 serwer lokalny=oddzielny proces wykonywalny lokalnie LocalServer / LocalServer32 serwer zdalny=proces wykonywalny zdalnie RemoteServer / RemoteServer32
In-proc serwer = DLL if (! FAILED(CoInitialize(NULL))) { err = (CoCreateInstance(CLSID_TESTCLASS, NULL, CLSCTX_INPROC_SERVER, IID_TESTINTERFACE, (void **)&pInterface) ); //użycie COM-a pInterface->DoSmthg(5); //zwolnienie COM-a pInterface->Release(); CoUninitialise(); } Wywołania metod lokalnych serwer ładowany jest do przestrzeni procesu klienta
Local/Remote Server = EXE – marszaling danych między procesami- wymagane jest zarejestrowanie interfejsów if (! FAILED(CoInitialize(NULL))) { err = (CoCreateInstance(CLSID_TESTCLASS, NULL, CLSCTX_INPROC_SERVER, IID_TESTINTERFACE, (void **)&pInterface) ); //użycie COM-a pInterface->DoSmthg(5); //zwolnienie COM-a pInterface->Release(); CoUninitialise(); } if (! FAILED(CoInitialize(NULL))) { err = (CoCreateInstance(CLSID_TESTCLASS, NULL, CLSCTX_INPROC_SERVER, IID_TESTINTERFACE, (void **)&pInterface) ); //użycie COM-a pInterface->DoSmthg(5); //zwolnienie COM-a pInterface->Release(); CoUninitialise(); }
COM – identyfikacja klas GUID - globalnie unikalny identyfikator: 128-bitowa liczba np.: B77D61E0-47D8-11d0-B53F CLSID - GUID identyfikujący klasę obiektu IID - GUID identyfikujący interfejs (niezbędny gdy mamy do czynienia z marszalingiem danych) Generowanie GUID: przez użytkownika - GUIDGEN.EXE, UUIDGEN.EXE Przez środowisko np.NET, VB, VC++ przez API - funkcja CoCreateGuid
Powiązanie obiektu z serwerem systemowa baza danych (rejestr systemu) - REGEDIT.EXE: HKEY_CLASSES_ROOT CLSID {GUID klasy obiektu} = Opis klasy obiektu ProgID = Producent.Serwer.Wersja VersionIndependentProgID = Producent.Serwer RodzajSerwera = Pełna nazwa (ze ścieżką) Insertable Producent.Serwer.Wersja = Opis klasy obiektu CLSID = {GUID klasy obiektu} Producent.Program = Opis klasy obiektu (bez wersji) CurVer = Producent.Serwer.Wersja funkcje: ProgIDFromCLSID CLSIDFromProgID
COM - interfejsy Koncepcja interfejsu: kontrakt na realizację pakietu usług niezależny od języków programowania sposób opisu: IDL Biblioteki typów TLB odpowiednik tablicy wskaźników do funkcji: C, C++, Pascal Java VB
Interfejsy - rejestracja HKEY_CLASSES_ROOT CLSID {a123x3y1-4ce6-4ce6-4ce6-2a } = My Prog Proxy = Ole2.dll InprocServer32 = c:\\coms\\myserver.dll LocalServer32 = c:\\coms\\myserver.exe {x ce6-4ce6-4ce6-2a } InprocServer32 = c:\\coms\\mystub.dll Interface {a123x3y1-4ce6-4ce6-4ce6-2a } NumMethods BaseInterface ProxyStubClsId = {x ce6-4ce6-4ce6-2a }
Rejestracja COM-a ”ręczne” uruchomienie pliku typu.REG automatyczny import pliku.REG bezpośrednia manipulacja rejestrem obsługa /REGISTER i /UNREGISTER 90% błędów na początku to problemy z rejestracją np. katalogi debug/release
COM – zarządzanie obiektem Zarządzanie pamięcią czas życia - zliczanie referencji zarządzanie kopiami danych przekazywanymi między obiektami Zunifikowany sposób tworzenia / likwidowania
Tworzenie obiektów 1. Indywidualna funkcja tworzaca obiekty 2. CoCreateObject() – wymaga dostarczenia IClassFactory
"Ręczne " tworzenie COM-a – InProcess DllGetClassObject(CLSID_Test, IID_IClassFactory, (void**)pClassFactory); pClassFactory->CreateInstance(NULL, IID_Test, (void**)pTest); pClassFactory->Release();
Klient COM if (! FAILED(CoInitialize(NULL))) { err = (CoCreateInstance(CLSID_TESTCLASS, NULL, CLSCTX_INPROC_SERVER, IID_TESTINTERFACE, (void **)&pInterface) ); //użycie COM-a pInterface->DoSmthg(5); //zwolnienie COM-a pInterface->Release(); CoUninitialise(); } CLSCTX_ALL CLSCTX_INPROC_SERVER CLSCTX_LOCAL_SERVER class ComClientClass {... virtual DoSmthg(int); } * pInterface;
Wsparcie dla COM Autorzy implementacji różnych języków definiją rozszerzenia pozwalające wołać COM-y. W C++ interfejs odpowiada wskaźnikowi do tablicy wskaźników do funkcji składowych interfejsu
Realizacja interfejsu w C++ class CBeeper: CUnknown { public: // wysokość dźwięku long m_lSound ; // wydanie dźwięku long GetSound (); void SetSound (long lSound); long Beep () ; } ptr = CoCreateInstance(....) ; (CBeeper)ptr->Beep(); interface IBeeper : IUnknown { long GetSound (); void SetSound (long lSound); long Beep () ; };
Realizacja interfejsu w C++ vs metody wirtualne
Skrypt IDL Język opisu interfejsów [uuid(108dbc1b-1ad2-4bda-b45a-e6b0f014c3a1),object] interface IMKInterface : IUnknown { import "unknwn.idl"; HRESULT GetSound([out]WORD *dw); HRESULT SetSound([in] WORD dw); HRESULT Beep (); } MIDL.EXE – kompilacja IDL do: biblioteki typów zrodla w c dla stub-a (dll) szeregującego
Biblioteka typów Może być linkowana jako zasób lub dostępna oddzielnie: HKEY_CLASSES_ROOT CLSID {a123x3y1-4ce6-4ce6-4ce6-2a } = My Prog ProgID = Producent.Serwer.Wersja TypeLib = {a123x3y1-4ce6-4ce6-4ce6-a } TypeLib {a123x3y1-4ce6-4ce6-4ce6-a } = My Type Lib Dir = c:\tmp HelpDir = c:\tmp\Help 1.00 = Any.TLB 9 = English.TLB
IUnknown - podstawowy interfejs obiektu każdy obiekt COM odostępnia interfejs IUnknown każdy interfejs COM obejmuje (dziedziczy) IUnknown
Właściwości Interfejsów Statyczny (niezmienny w czasie) zestaw interfejsów Jednoznaczny i unikatowy IUnknown dla różnych obiektów Zada zwrotności: IA -> IA Zasada symetrii: IA -> IB -> IA Zasada przechodniości: IA -> IB -> IC i IA -> IC
IUnknown - funkcjonalność interface IUnknown { // zwiększa licznik odniesień ULONG AddRef () ; // zmniejsza licznik odniesień, // gdy licznik == 0 to zwalnia obiekt ULONG Release () ; // udostępnia interfejsy do obiektu HRESULT QueryInterface ( REFIID riid,// identyfikator interfejsu void** ppvObj) ;// wskaźnik do zwracanego interf. } ;
IUnknown – realizacja w C++ (MFC) class IUnknown { public: virtual HRESULT QueryInterface (REFIID, void**) ; virtual ULONG AddRef () ; virtual ULONG Release () ; } ; class IUnknown { public: STDMETHODIMP QueryInterface (REFIID, void**) ; STDMETHODIMP_(ULONG) AddRef () ; STDMETHODIMP_(ULONG) Release () ; } ; Makra MFC
Zliczanie odniesień Tworzenie kopii odniesienia do obiektu – AddRef Unieważnienie odniesienia do obiektu – Release W praktyce AddRef / Release Zwrot referencji przez funkcję Przekazanie odniesienia do oddzielnego wątku
Zarządzanie obiektami przez zliczanie odniesień class CXxxObject : public IUnknown { public: CXxxObject (…): m_cRef (0) {…} ; virtual HRESULT QueryInterface (REFIID, void**) ; virtual ULONG AddRef () ; virtual ULONG Release () ; … private: ULONG m_cRef ; // licznik odniesień … } ;
IUnknown::AddRef / Release ULONG CXxxObject::AddRef () { return ++m_cRef; // licznik odniesień } ULONG CXxxObject::Release () { if (0 != --m_cRef) // licznik odniesień return m_cRef ; delete this ;// usunięcie obiektu return 0 ; }
IUnknown::QueryInterface HRESULT CXxxObject::QueryInterface ( REFIID riid, void** ppvObj) { *ppvObj = NULL ; if ( IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IMyInterface) ) // if (riid == IID_IUnknown || riid == IID_IMyInterface) *ppvObj = this ; if (NULL != *ppvObj) { ((IUnknown)*ppvObj)->AddRef (); //licznik odniesień return NOERROR ; } else return E_NOINTERFACE; }
IClassFactory - tworzenie obiektów interface IClassFactory : public IUnknown { // stworzenie obiektu HRESULT CreateInstance( IUnknown* pUnkOuter, // wskaźnik do obiektu nadrzęd. REFIID riid, // podstawowy interfejs obiektu void** ppvObj) ; // wskaźnik do zwracanego interf. // zablokowanie serwera w pamięci HRESULT LockServer( BOOL fLock ); } ;
Implementacja ClassFactory class CXxxObject ; // klasa tworzonych obiektów class CXxxClassFactory : public IClassFactory { public: // IUnknown STDMETHODIMP QueryInterface (REFIID, void**) ; STDMETHODIMP_(ULONG) AddRef () ; STDMETHODIMP_(ULONG) Release () ; // IClassFactory STDMETHODIMP CreateInstance ( IUnknown*, REFIID, void**) ; STDMETHODIMP LockServer (BOOL) ; private: … } ;
IClassFactory::CreateInstance STDMETHODIMP CXxxClassFactory::CreateInstance ( IUnknown* pUnkOuter, REFIID riid, void** ppvObj) { *ppvObj = NULL ; if (NULL != pUnkOuter)// obiekt nie wspiera agregacji return ResultFromScode(CLASS_E_NOAGGREGATION); // stworzenie obiektu CXxxObject pObj = new CXxxObject ; // udostepnienie żądanego łącza HRESULT hr = pObj->QueryInterface (riid, ppvObj) ; if (FAILED (hr)) delete pObj ; else *ppvObj = pObj ; return hr ; }
Serwer w procesie (in-process) biblioteka dynamiczna udostępniająca funkcje: STDAPI DllGetClassObject ( REFCLSID rclsid, // GUID klasy udostępnianych obiektów REFIID riid, // GUID łącza tworzącego obiekty // (zwykle IID_ClassFactory) void** ppv) ; // wskaźnik do zwracanego łącza STDAPI DllCanUnloadNow () ;
Funkcje serwera typu InProcess STDAPI DllGetClassObject(REFCLSID rclsid,REFIID riid,void**ppv) { if (rclsid != CLSID_XXX) return ResultFromScode (E_FAIL) ; CXxxClassFactory* pObj = new CXxxClassFactory ; if (pObj == NULL) return ResultFromScode (E_OUTOFMEMORY); HRESULT hr = pObj->QueryInterface (riid, ppv) ; if (FAILED (hr)) delete pObj ; return hr; } STDAPI DllCanUnloadNow () { SCODE sc = (/*czy są obiekty? */…) ? S_FALSE : S_OK ; return ResultFromScode (sc); }
Klient COM Server COM CoCreateInstance CoGetClassObject ReadRegistry CoLoadLibrary(“test.dll”,TRUE) Hmodule = LoadLiibrary(test.dll) GetProcAdress(hModule, ”DllGetClassObject”) DllGetClassObject pFactory->CreateInstance pFactory->Release DllGetClassObject pFactory->AddRef pFactory->QueryInterface pFactory-> CreateInstance pObject->QueryInterface pObject->AddRef DllMain CLSID { } InprocServer32 = “test.dll”
Serwer lokalny program wykonywalny (EXE) rejestrujący udostępniane obiekty: zagadnienia utrzymanie programu w pamięci dopóki istnieją obiekty pot. różny wygląd w zależności od typu pracy (/embeeded) STDAPI CoRegisterClassObject( REFCLSID rclsid,// GUID klasy obiektów IUnknown * pUnk,// Interfejs IUnknown // udostępnianego obiektu DWORD dwClsCtxt,// Rodzaj serwera DWORD flags,// Sposób nawiązywania połączenia DWORD** pdwReg// Wskaźnik do zarejestrowanej klasy );
Inicjalizacja i zakończenie serwera lokalnego // inicjalizacja if (FAILED (CoInitializeEx (NULL))) … // błąd inicjalizacji CXxxClassFactory* pClassFact = new CXxxClassFactory ; pClassFact->AddRef () ; DWORD dwReg ; if (FAILED (CoRegisterClassObject(CLSID_XXX, pClassFact,CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &dwReg)) ) … // błąd inicjalizacji // zakończenie CoRevokeClassObject (dwReg) ; pClassFact->Release () ; CoUninitializeEx () ;
Implementacja wielu interfejsów COM przez jedną klasę C++? Podział odpowiedzialności – różne klasy C++ Techniczny problem w przypadku realizacji interfejsów jako oddzielnych klas pot. problem z powoływaniem do życia i zarządzaniem wieloma POWIĄZANYMI obiektami Adres interfejsu vs. tożsamość obiektu możliwe rozwiązanie: klasy zagnieżdzone metody z IUnknown są delegowne do obiektu głównego MFC – BEGIN/END_INTERFACE_PART
COM - REUSABILITY
Wielokrotne wykorzystywanie (reusability) kodu C++ dziedziczenie wspólne pola danych dla klasy podstawowej i pochodnej wymaga bardzo dokładnego dokumentowania, zwykle przez udostępnianie kodu źródłowego COM zawieranie obiektów agregacja obiektów
Zawieranie obiektów modyfikacja metod obiektu obiekt zewnętrzny nie udostępnia łącza IUnknown obiektu wewnętrznego obiekt zewnętrzny udostępnia pośrednio wybrane łącza obiektu wewnętrznego
Zawieranie obiektów - zewnętrzny obiekt class CExtObject : IUnknown, IInterfaceW, IInterfaceZ { public: CExtObject (…): m_pInObj (NULL){ ::CoCreateInstance (CLSID_ExtObject, NULL, CLSCTX_ALL, IID_IUnknown, &m_pInObj) ; } ; ~CExtObject () { Release();delete m_pInObj;} ; … // metody łącz IInterfaceZ i IInterfaceW private: IUnknown* m_pInObj ; // obiekt wewnętrzny … } ;
Agregacja obiektów udostępnienie niezmienionych metod obiektu obiekt zewnętrzny udostępnia pośrednio łącze IUnknown obiektu wewnętrznego obiekt zewnętrzny udostępnia bezpośrednio pozostałe łącza obiektu wewnętrznego
Agregacja - wewnętrzny obiekt class CInObject : IUnknown, IInterfaceW { public: CInObject (IUnknown* pUnkOuter, …) ; virtual HRESULT QueryInterface (REFIID, void**) ; virtual ULONG AddRef () ; virtual ULONG Release () ; … private: ULONG m_cRef ; // licznik odniesień IUnknown *m_pUnkOuter ; // zewnętrzne IUnknown … } ;
Agregacja – zliczanie odniesień CInObject:: CInObject(IUnknown* pUnkOuter, …) m_cRef (0), m_pUnkOuter (pUnkOuter) { … } ULONG CInObject::AddRef (){ if (m_pUnkOuter) return m_pUnkOuter->AddRef () ; return ++m_cRef ; } ULONG CInObject::Release (){ if (m_pUnkOuter) return m_pUnkOuter->Release () ; if (0 != --m_cRef) return m_cRef ; delete this ; return 0 ; }
Agregacja - QueryInterface HRESULT CInObject::QueryInterface (REFIID riid, void** ppvObj) { *ppvObj = NULL ; if (m_pUnkOuter) return m_pUnkOuter->QueryInterface(riid, ppvObj); if (IsEqualIID (riid, IID_IUnknown) || IsEqualIID (riid, IID_IInterface1)) *ppvObj = this ; if (NULL != *ppvObj) { ((LPUNKNOWN)*ppvObj)->AddRef () ; return NOERROR ; } return ResultFromScode (E_NOINTERFACE) ; }
Agregacja – CreateInstance HRESULT CInClassFactory::CreateInstance ( IUnknown* pUnkOuter, REFIID riid, void** ppvObj) { *ppvObj = NULL ; if (pUnkOuter && riid != IID_IUnknown) return ResultFromScode (E_NOINTERFACE); CInObject pObj = new CInObject (pUnkOuter, …) ; HRESULT hr = pObj->QueryInterface (riid, ppvObj) ; if (FAILED (hr)) delete pObj ; else *ppvObj = pObj ; return hr ; }
Emulacja i wersjonowanie HKEY_CLASSES_ROOT MyFirm.MyProg = this is current version of component CLSID = {a123x3y1-4ce6-4ce6-4ce6-2a } CurVer = {a123x3y1-4ce6-4ce6-4ce6-2a } MyFirm.MyProg.1= this is version 1 of component CLSID = {x000x0y0-4ce6-4ce6-4ce6-2a } MyFirm.MyProg.2 = this is version 2 of component CLSID = {a123x3y1-4ce6-4ce6-4ce6-2a } TreatAs = {y ce6-4ce6-4ce6-2a } AutoTreatAs = {y ce6-4ce6-4ce6-2a } funkcje: CoTreateAsClass
ACTIVE X
ActiveX Kontrolki, Serwery Kontrolki ActiveX: standard do rozszerzania zestawu pól dialogowych dostępnych w systemie Windows poprzednie standardy: Custom Controls, Visual Basic Controls OCX = ActiveX (OLE controls = ActiveX controls) gotowe komponenty funkcjonalne do budowania programów Server ActiveX: program udostępniający IDispatch
Interfejs IDispatch interface IDispatch : IUnknown { HRESULT GetTypeInfoCount (…) ; HRESULT GetTypeInfo (…) ; HRESULT GetIDsOfNames (…) ; HRESULT Invoke (DISPID dispID, REFIID riid, LCID lcid, unsigned short wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, unsigned int* puArgErr) ; } ;
Obsługiwane typy danych IDL: boolean(bool), double, float, signed int(int/long), signed short(short), enum, BSTR, CY, DATE, IDispatch*, IUnknown*, SAFEARRAY, VARIANT
Klient automatyzacji - Visual Basic Dim ThisExcel As Excel.Application Dim ThisChart As Excel.Chart …. Dim TitleArray As Variant Dim DataArray As Variant TitleArray = Array("Dogs", "Cats", "Horses") DataArray = Array(34, 53, 12) Set ThisExcel = CreateObject("Excel.application") With ThisExcel.Workbooks.Add.Range("A1:C1").Value = TitleArray.Range("A2:C2").Value = DataArray.Range("A1:C2").Select Set ThisChart =.Charts.Add().Visible = True End With
Dedykowany klient automatyzacji MFC/.Net potrafią wygenerować klasę udostępniającą funkcjonalność obiektu ActiveX na postawie: biblioteki TLB (również np. wkompilowanej w dll) zawartości rejestru (ID.klasy) klasa pośredniczy w wołaniu metod serwera dostępie do pól udostepnianych przez serwer (Get/Set) CMyComClassDriver c; c.CreateDispatch(ComObjectCLSID); x = c.Calculate(15); c.ReleaseDispatch();
Klient automatyzacji - VC++ CLSID clsid; IDispatch* pIDispatch ; ::CLSIDFromProgID(T2OLE(_T("Excel.application")),&clsid); HRESULT hr = ::CoCreateInstance (clsid, NULL, CLSCTX_SERVER, IID_IDispatch, (void**) &pIDispatch); CApplication m_pThisExcel = new CApplication (pIDispatch) ; CChart*m_pThisChart = new CChart(CCharts(m_pThisExcel->Charts ()).Add ()); CRange( m_pThisExcel->Range("A1", "C1")).SetValue(TitleArray); CRange (m_pThisExcel->Range ("A1", "C2")).Select () ; m_pThisExcel->SetVisible (TRUE) ; m_pThisChart->SetRotation (iRotate) ;
IDispatch z perspektywy klienta Informacje o interfejsie ? IDispatch – samodokumentujacy się interfejs Biblioteka typów: Pliki.TLB Wbudowana w komponent (jako zasób) Sterowniki: VC++ - ClassWizard - dedykowana klasa (na podst. biblioteki typów) VB – import komponentu lub biblioteki
Właściwości interfejsu IDispatch Zalety: Samodokumentowanie się interfejsów Możliwość późnego wiązania Wady: Duży narzut na pojedyncze wołanie Techniczna realizacje ActiveX: Dwa zestawy interfejsów (dla COM/ActiveX) Interfejsy dualne (IDL: dual, HRESULT)
IDispatchEx Zmienione zestawy parametrów Dynamiczna zmiana udostępnianej funkcjonalności interface IDispatchEx: IDispatch { GetDispID(...); GetNextDispID(...); InvokeEx(...); DeleteMemberByName(...); DeleteMemberByDispID(...); GetMemberProperties(...); GetMemberName(...); GetNameSpaceParent(...); };
COM VS.NET Com wiecznie żywy
Współpraca z COM Klient.Net Runtime Callable Wrapper Natywny COM Server IUnknown IDispatch IFoo RCW jest generowany przez runtime
Współpraca z COM Obiekt.Net klient COM IUnknown IDispatch IFoo Object name IFoo R egasm.exe pozwala wygenerować GUID i zaktualizować wpisy w rejestrze.
Klient Com w.NET Import i generacja wrapera Visual Studio Type Library Importer (Tlbimp.exe) System.Runtime.InteropServices.TypeLib Converter class “Ręcznie” napisana klasa Managed C++
Serwer Com w.NET using System.Runtime.InteropServices; using System.Reflection; [GuidAttribute("EA5A0174-D a-8B9B-28F6674E9371"), ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsDual)] public interface IKlasaN { uint SetState(int state); uint GetState(out int state); } [ClassInterface(ClassInterfaceType.None), GuidAttribute("493DEA38-15EB-4acf CF32A29F3"), ComVisible(true), ProgId(„comtestnet.klasa.1”)] public class KlasaN: IKlasaN {... } Rejestracja: regasm.exe