OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM OPRACOWANIE: JAROSŁAW FRANCIK LISTING #1 PIERWSZE PODEJŚCIE LISTING #2 DRUGIE PODEJŚCIE #include <iostream.h> #include <objbase.h> interfejs A interface IA virtual void stdcall fa() = 0; ; interfejs B interface IB virtual void stdcall fb() = 0; ; Składnik class CMyComp : public IA, public IB Implementacja interfejsu A virtual void stdcall fa(); Implementacja interfejsu B virtual void stdcall fb(); ; void CMyComp::fa() cout << "Jestem fa" << endl; void CMyComp::fb() cout << "Jestem fb" << endl; / Plik nagłówkowy interface.h #include <objbase.h> interfejs A interface IA : public IUnknown virtual void stdcall fa() = 0; ; interfejs B interface IB : public IUnknown virtual void stdcall fb() = 0; ; deklaracje identyfikatorów IID extern const IID IID_IA; extern const IID IID_IB; deklaracja funkcji tworzącej egzemplarz składnika IUnknown *CreateInstance(); / Definicja identyfikatorów IID 033E7EC7-50FA-404c-9C59-4CBA8892F054 static const GUID IID_IA = 0x33e7ec7, 0x50fa, 0x404c, 0x9c, 0x59, 0x4c, 0xba, 0x88, 0x92, 0xf0, 0x54 ; FD854FCC-6AE2-4876-BB60-DC276E5DFC68 static const GUID IID_IB = 0xfd854fcc, 0x6ae2, 0x4876, 0xbb, 0x60, 0xdc, 0x27, 0x6e, 0x5d, 0xfc, 0x68 ; void main() CMyComp *pcomp = new CMyComp; wskaźnik do interfejsu A IA *pia = pcomp; pia->fa(); wskaźnik do interfejsu B IB *pib = pcomp; pib->fb(); delete pcomp; 1
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM OPRACOWANIE: JAROSŁAW FRANCIK / Aplikacja kliencka #include "interface.h" void main() inicjalizacja składnika IUnknown *punknown = CreateInstance(); IUnknown *CreateInstance() IUnknown *p = (IA*)new CMyComp; p->addref(); return p; chcemy skorzystać z interfejsu IA IA *pia = NULL; HRESULT hr = punknown->queryinterface(iid_ia, (void**)&pia); if (SUCCEEDED(hr)) pia->fa(); pia->release(); chcemy skorzystać z interfejsu IB IB *pib = NULL; hr = punknown->queryinterface(iid_ib, (void**)&pib); if (SUCCEEDED(hr)) pib->fb(); pib->release(); punknown->release(); delete punknown; / Moduł składnika #include "interface.h" #include <iostream.h> Klasa składnika class CMyComp : public IA, public IB Implementacja interfejsu IUnknown virtual HRESULT _stdcall QueryInterface(REFIID iid, void **ppv); virtual ULONG _stdcall AddRef(); virtual ULONG _stdcall Release(); Implementacja interfejsu A virtual void stdcall fa() cout << "Jestem fa" << endl; HRESULT _stdcall CMyComp::QueryInterface(REFIID iid, void **ppv) if (iid == IID_IUnknown) *ppv = (IA*)this; else if (iid == IID_IA) *ppv = (IA*)this; else if (iid == IID_IB) *ppv = (IB*)this; else ppv = NULL; return E_NOINTERFACE; ((IUnknown*)(*ppv))->AddRef(); ULONG _stdcall CMyComp::AddRef() return InterlockedIncrement(&m_nRef); ULONG _stdcall CMyComp::Release() if (InterlockedDecrement(&m_nRef) == 0) delete this; return 0; return m_nref; Implementacja interfejsu B virtual void stdcall fb() cout << "Jestem fb" << endl; public: CMyComp() : m_nref(0) private: long m_nref; ; 2
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM OPRACOWANIE: JAROSŁAW FRANCIK / Aplikacja kliencka - korzystająca z serwera COM LISTING #3 TRZECIE PODEJŚCIE / Plik nagłówkowy interface.h #include <objbase.h> interfejs A interface IA : public IUnknown virtual void stdcall fa() = 0; ; interfejs B interface IB : public IUnknown virtual void stdcall fb() = 0; ; deklaracje identyfikatorów IID extern const IID IID_IA; extern const IID IID_IB; #include "interface.h" void main() inicjalizacja podsystemu COM CoInitialize(NULL); inicjalizacja składnika IUnknown *punknown = NULL; HRESULT hr = CoCreateInstance(CLSID_Component, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&punknown); if (SUCCEEDED(hr)) chcemy skorzystać z interfejsu IA IA *pia = NULL; hr = punknown->queryinterface(iid_ia, (void**)&pia); if (SUCCEEDED(hr)) pia->fa(); pia->release(); / Definicja identyfikatorów IID 59F1DE0D-8843-4a57-82CE-D6DBA3D28D28 static const GUID CLSID_Component = 0x59f1de0d, 0x8843, 0x4a57, 0x82, 0xce, 0xd6, 0xdb, 0xa3, 0xd2, 0x8d, 0x28 ; 033E7EC7-50FA-404c-9C59-4CBA8892F054 static const GUID IID_IA = 0x33e7ec7, 0x50fa, 0x404c, 0x9c, 0x59, 0x4c, 0xba, 0x88, 0x92, 0xf0, 0x54 ; FD854FCC-6AE2-4876-BB60-DC276E5DFC68 static const GUID IID_IB = 0xfd854fcc, 0x6ae2, 0x4876, 0xbb, 0x60, 0xdc, 0x27, 0x6e, 0x5d, 0xfc, 0x68 ; dalej jak w listingu #2 punknown->release(); / Moduł składnika (serwer) #include "interface.h" #include <iostream.h> #include "registry.h" narzędzia do rejestru systemowego... Zmienne globalne static HMODULE g_hmodule = NULL; uchwyt modułu DLL static long g_ccomponents = 0 ; Licznik aktywnych składników static long g_cserverlocks = 0 ; Licznik dla LockServer Dane do rejestru systemowego const char g_szprogid[] = "COMLecture.Approach3.1"; const char g_szfriendlyname[] = "Wykład z COM, podejście 3"; const char g_szverindprogid[] = "COMLecture.Approach3"; 3
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM OPRACOWANIE: JAROSŁAW FRANCIK Klasa składnika Class Factory class CMyComp : public IA, public IB public: Implementacja interfejsu IUnknown virtual HRESULT _stdcall QueryInterface(REFIID iid, void **ppv); virtual ULONG _stdcall AddRef(); virtual ULONG _stdcall Release(); Implementacja interfejsu A virtual void stdcall fa() cout << "Jestem fa" << endl; Implementacja interfejsu B virtual void stdcall fb() cout << "Jestem fb" << endl; public: CMyComp() : m_nref(1) InterlockedIncrement(&g_cComponents); ~CMyComp() InterlockedDecrement(&g_cComponents); private: long m_nref; ; HRESULT _stdcall CMyComp::QueryInterface(REFIID iid, void **ppv) if (iid == IID_IUnknown) *ppv = (IA*)this; else if (iid == IID_IA) *ppv = (IA*)this; else if (iid == IID_IB) *ppv = (IB*)this; else ppv = NULL; return E_NOINTERFACE; ((IUnknown*)(*ppv))->AddRef(); ULONG _stdcall CMyComp::AddRef() return InterlockedIncrement(&m_nRef); ULONG _stdcall CMyComp::Release() if (InterlockedDecrement(&m_nRef) == 0) delete this; return 0; return m_nref; class CFactory : public IClassFactory public: Implementacja interfejsu IUnknown virtual HRESULT _stdcall QueryInterface(REFIID iid, void **ppv); virtual ULONG _stdcall AddRef(); virtual ULONG _stdcall Release(); Implementacja interfejsu IClassFactory virtual HRESULT stdcall CreateInstance( IUnknown* punknownouter, const IID& iid, void** ppv); virtual HRESULT stdcall LockServer(BOOL block); CFactory() : m_nref(1) private: long m_nref ; ; Class Factory interfejs IUnknown HRESULT stdcall CFactory::QueryInterface(const IID& iid, void** ppv) if ((iid == IID_IUnknown) (iid == IID_IClassFactory)) *ppv = (IClassFactory*)this; else ppv = NULL; return E_NOINTERFACE; ((IUnknown*)(*ppv))->AddRef(); ULONG stdcall CFactory::AddRef() return InterlockedIncrement(&m_nRef) ; ULONG stdcall CFactory::Release() if (InterlockedDecrement(&m_nRef) == 0) delete this ; return 0 ; return m_nref ; 4
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM OPRACOWANIE: JAROSŁAW FRANCIK Class Factory interfejs IClassFactory Funkcje eksportowane! HRESULT stdcall CFactory::CreateInstance(IUnknown* punknownouter, const IID& iid, void** ppv) if (punknownouter!= NULL) return CLASS_E_NOAGGREGATION; Utworzenie składnika CMyComp* pmycomp = new CMyComp; if (!pmycomp) return E_OUTOFMEMORY; Utworzenie żądanego interfejsu HRESULT hr = pmycomp->queryinterface(iid, ppv); pmycomp->release(); return hr; HRESULT stdcall CFactory::LockServer(BOOL block) if (block) InterlockedIncrement(&g_cServerLocks) ; else InterlockedDecrement(&g_cServerLocks) ; return S_OK ; Inicjalizacja DLL BOOL APIENTRY DllMain(HMODULE hmodule, DWORD ul_reason_for_call, LPVOID lpreserved) if (ul_reason_for_call == DLL_PROCESS_ATTACH) g_hmodule = hmodule; return TRUE ; STDAPI DllCanUnloadNow() if ((g_ccomponents == 0) && (g_cserverlocks == 0)) else return S_FALSE; STDAPI DllGetClassObject(const CLSID& clsid, const IID& iid, void** ppv) Czy możemy utworzyć taki składnik? if (clsid!= CLSID_Component) return CLASS_E_CLASSNOTAVAILABLE ; Utwórz fabrykę klas CFactory* pfactory = new CFactory; if (pfactory == NULL) return E_OUTOFMEMORY; Zwróć żądany interfejs HRESULT hr = pfactory->queryinterface(iid, ppv); pfactory->release(); return hr; STDAPI DllRegisterServer() return RegisterServer(g_hModule, CLSID_Component, g_szfriendlyname, g_szverindprogid, g_szprogid); STDAPI DllUnregisterServer() return UnregisterServer(CLSID_Component, g_szverindprogid, g_szprogid); / Plik COMPONENT.DEF LIBRARY Component.dll DESCRIPTION 'Przykład do wykładu COM, (C) Jarosław Francik 2001' EXPORTS DllGetClassObject @2 PRIVATE DllCanUnloadNow @3 PRIVATE DllRegisterServer @4 PRIVATE DllUnregisterServer @5 PRIVATE 5
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM OPRACOWANIE: JAROSŁAW FRANCIK / Aplikacja kliencka - korzystająca z serwera COM LISTING #4 CZWARTE PODEJŚCIE (SERWER EXE) / Specyfikacja mytypes.idl import "unknwn.idl"; Interface IA [ object, uuid(b5db3493-9925-4633-b7db-30da5e75d347), helpstring("ia Interface"), pointer_default(unique) ] interface IA : IUnknown HRESULT fa(); ; Interface IB [ object, uuid(10ae43a2-2d3f-48a9-8ed3-d3f1f21cc898), helpstring("ib Interface"), pointer_default(unique) ] interface IB : IUnknown HRESULT fb(); ; Biblioteka typów + clsid składników [ uuid(3221ccff-68b2-442b-afdc-fa9c767f1fc4), version(1.0), helpstring("biblioteka typów Approach3a") ] library MyTypeLib importlib("stdole32.tlb") ; [ uuid(e843265d-b374-4d56-a980-0ec5e9188b47), helpstring("klasa komponentu Approach3a") ] coclass Component [default] interface IA; interface IB; ; ; #include "..\\mytypes.h" #include "..\\mytypes_i.c" void main() inicjalizacja podsystemu COM CoInitialize(NULL); inicjalizacja składnika IUnknown *punknown = NULL; HRESULT hr = CoCreateInstance(CLSID_Component, NULL, CLSCTX_LOCAL_SERVER, IID_IUnknown, (void**)&punknown); / Moduł proxy zawiera wyłącznie pliki utworzone przez midl: dlldata.c, abc_i.c, abc_p.c, linkowane z rpcndr.lib, rpcns4.lib i rpcrt4.lib Oto plik PROXY.DEF: LIBRARY proxy.dll DESCRIPTION 'Przykład do wykładu COM, (C) Jarosław Francik 2001' EXPORTS DllGetClassObject @1 PRIVATE DllCanUnloadNow @2 PRIVATE GetProxyDllInfo @2 PRIVATE DllRegisterServer @4 PRIVATE DllUnregisterServer @5 PRIVATE / Moduł składnika (serwer) #include "..\\mytypes.h" #include "..\\mytypes_i.c" #include <iostream.h> #include <memory.h> #include "registry.h" narzędzia do rejestru systemowego... Zmienne globalne usunieto zmienną g_hmodule ponadto jak w podejściu Trzecim 6
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM OPRACOWANIE: JAROSŁAW FRANCIK Klasa składnika class CMyComp : public IA, public IB public: Implementacja interfejsu IUnknown jak w Podejściu Trzecim Implementacja interfejsu A virtual HRESULT stdcall fa() MessageBox(0, "Jestem fa", "Jestem fa", 0); Implementacja interfejsu B virtual HRESULT stdcall fb() MessageBox(0, "Jestem fb", "Jestem fb", 0); public: CMyComp() : m_nref(1) InterlockedIncrement(&g_cComponents); ~CMyComp() InterlockedDecrement(&g_cComponents); if (g_ccomponents == 0 && g_cserverlocks == 0) PostQuitMessage(0); private: long m_nref; ; Implementacja klasy CMyComp jak w Podejściu Trzecim Class Factory Deklaracja i implementacja fabryki klas jak w Podejściu Trzecim; jedyna różnica dotyczy poniższej funkcji: HRESULT stdcall CFactory::LockServer(BOOL block) if (block) InterlockedIncrement(&g_cServerLocks); else InterlockedDecrement(&g_cServerLocks); if (g_ccomponents == 0 && g_cserverlocks == 0) PostQuitMessage(0); return S_OK ; Część aplikacyjna + obsługa COM LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int APIENTRY WinMain(HINSTANCE hinstance, HINSTANCE hprevinstance, LPSTR lpcmdline, int ncmdshow) Inicjalizacja COM flaga określająca tryb pracy bool bembedding = false; Inicjalizacja COM HRESULT hr = CoInitialize(NULL) ; if (FAILED(hr)) return 0 ; Odczyt linii wywołania char sztokens[] = "-/"; char* sztoken = strtok(lpcmdline, sztokens) ; while (sztoken!= NULL) if (_stricmp(sztoken, "UnregServer") == 0) UnregisterServer(FALSE, CLSID_Component, g_szverindprogid, g_szprogid); CoUninitialize(); return 0; else if (_stricmp(sztoken, "RegServer") == 0) RegisterServer(FALSE, hinstance, CLSID_Component, g_szfriendlyname, g_szverindprogid, g_szprogid); CoUninitialize(); return 0; else if (_stricmp(sztoken, "Embedding") == 0) bembedding = true; break ; sztoken = strtok(null, sztokens); 7
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM OPRACOWANIE: JAROSŁAW FRANCIK Rejestracja fabryki klas DWORD dregister; CFactory *pfactory = new CFactory; hr = ::CoRegisterClassObject(CLSID_Component, pfactory, CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &dregister); if (FAILED(hr)) pfactory->release() ; return FALSE ; / Standardowa część aplikacji if (!bembedding) Rejestracja klasy okna WNDCLASS wc; memset(&wc, 0, sizeof(wc)); wc.lpfnwndproc = (WNDPROC)WndProc; wc.hinstance = hinstance; wc.hcursor = LoadCursor(NULL, IDC_ARROW); wc.hbrbackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszclassname = "my_class1"; RegisterClass(&wc); Utworzenie okna HWND hwnd; hwnd = CreateWindow("my_class1", "Tytuł", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinstance, NULL); if (!hwnd) return FALSE; ShowWindow(hWnd, ncmdshow); UpdateWindow(hWnd); / Końcowa deinicjalizacja systemu COM! CoRevokeClassObject(dRegister); CoUninitialize(); return msg.wparam; Funkcja okienkowa LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) switch (message) case WM_DESTROY: nie pozwala zamknąć aplikacji, dopóki klienci korzystają z serwera if (g_ccomponents == 0 && g_cserverlocks == 0) PostQuitMessage(0); break ; case WM_CLOSE: znosi blokadę na czas używania interfejsu użytkownika ::InterlockedDecrement(&g_cServerLocks); return (DefWindowProc(hWnd, message, wparam, lparam)) ; default: return DefWindowProc(hWnd, message, wparam, lparam); return 0; Blokuje serwer na czas używania interfejsu użytkownika InterlockedIncrement(&g_cServerLocks); Pętla komunikatów MSG msg; while (GetMessage(&msg, NULL, 0, 0)) TranslateMessage(&msg); DispatchMessage(&msg); 8
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM OPRACOWANIE: JAROSŁAW FRANCIK / Moduł składnika (serwer) LISTING #5 PIĄTE PODEJŚCIE (DISPINTERFEJSY) / Specyfikacja mytypes.idl import "unknwn.idl"; Interface IA [ object, uuid(b5db3493-9925-4633-b7db-30da5e75d347), helpstring("ia Interface"), pointer_default(unique), dual, oleautomation ] interface IA : IDispatch HRESULT fa(); ; reszta bez zmian / Aplikacja kliencka - korzystająca z interfejsu vtable BEZ ŻADNYCH ZMIAN / Moduł proxy bez zmian rozszerzenie klasy CMyComp obejmuje implementację interfejsu IDispatch class CMyComp : public IA, public IB public: Implementacja interfejsu IDispatch virtual HRESULT stdcall GetTypeInfoCount( UINT RPC_FAR *pctinfo); virtual HRESULT stdcall GetTypeInfo( UINT itinfo, LCID lcid, ITypeInfo RPC_FAR * RPC_FAR *pptinfo); virtual HRESULT stdcall GetIDsOfNames( REFIID riid, LPOLESTR RPC_FAR *rgsznames, UINT cnames, LCID lcid, DISPID RPC_FAR *rgdispid); virtual HRESULT stdcall Invoke( DISPID dispidmember, REFIID riid, LCID lcid, WORD wflags, DISPPARAMS RPC_FAR *pdispparams, VARIANT RPC_FAR *pvarresult, EXCEPINFO RPC_FAR *pexcepinfo, UINT RPC_FAR *puargerr); protected: ITypeInfo *m_pitypeinfo; HRESULT InitTypeInfo(); public: rzeczy pomocne uzupełnienie konstruktora: CMyComp() : m_nref(1) InterlockedIncrement(&g_cComponents); InitTypeInfo(); ; RESZTA BEZ ZMIAN 9
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM OPRACOWANIE: JAROSŁAW FRANCIK Funkcja tworząca obiekt informacji o typach Implementacja interfejsu IDispatch opiera się na wykorzystaniu funkcjonalności obiektu informacji o typach HRESULT stdcall CMyComp::GetTypeInfoCount(UINT RPC_FAR *pctinfo) *pctinfo = 1; HRESULT stdcall CMyComp::GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo RPC_FAR * RPC_FAR *pptinfo) *pptinfo = NULL; if (itinfo!= 0) return DISP_E_BADINDEX; m_pitypeinfo->addref(); *pptinfo = m_pitypeinfo; HRESULT stdcall CMyComp::GetIDsOfNames(REFIID riid, LPOLESTR RPC_FAR *rgsznames, UINT cnames, LCID lcid, DISPID RPC_FAR *rgdispid) if (riid!= IID_NULL) return DISP_E_UNKNOWNINTERFACE; HRESULT hr; hr = m_pitypeinfo->getidsofnames(rgsznames, cnames, rgdispid); return hr; HRESULT stdcall CMyComp::Invoke( DISPID dispidmember, REFIID riid, LCID lcid, WORD wflags, DISPPARAMS RPC_FAR *pdispparams, VARIANT RPC_FAR *pvarresult, EXCEPINFO RPC_FAR *pexcepinfo, UINT RPC_FAR *puargerr) if (riid!= IID_NULL) return DISP_E_UNKNOWNINTERFACE; HRESULT hr = m_pitypeinfo->invoke((ia*)this, dispidmember, wflags, pdispparams, pvarresult, pexcepinfo, puargerr); return hr; HRESULT CMyComp::InitTypeInfo() HRESULT hr; spróbuj załadować bibliotekę typów z rejestru systemowego ITypeLib *ptypelib = NULL; hr = LoadRegTypeLib(LIBID_MyTypeLib, 1, 0, 0x00, &ptypelib); if (FAILED(hr)) załaduj z pliku ustal nazwę pliku exe i podziel ją na czynniki pierwsze char szmodule[512] ; DWORD dwresult = ::GetModuleFileName(g_hInstance, szmodule, 512); char szdrive[_max_drive]; char szdir[_max_dir]; _splitpath(szmodule, szdrive, szdir, NULL, NULL); Określ nazwę pliku TLB char sztypelibfullname[_max_path]; sprintf(sztypelibfullname, "%s%s%s", szdrive, szdir, "mytypes.tlb"); convert to wide char wchar_t wsztypelibfullname[_max_path]; mbstowcs(wsztypelibfullname, sztypelibfullname, _MAX_PATH); hr = LoadTypeLib(wszTypeLibFullName, &ptypelib); if (FAILED(hr)) return hr; na wszelki wypadek zarejestruj hr = RegisterTypeLib(pTypeLib, wsztypelibfullname, NULL); if (FAILED(hr)) return hr; pobierz TypeInfo dla IA m_pitypeinfo = NULL; hr = ptypelib->gettypeinfoofguid(iid_ia, &m_pitypeinfo); ptypelib->release(); if (FAILED(hr)) return hr; Podnadto uzupełniono CMyComp::QueryInterface tak, by zwracała w razie potrzeby wskaźnik na IDispatch 10
Aternatywna Aplikacja kliencka Aplikacja korzysta z dispinterfejsu #include <objbase.h> #include <assert.h> OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM OPRACOWANIE: JAROSŁAW FRANCIK Klient w Visual Basicu: Dim x As Object Set x = CreateObject(COMLecture.Approach5 ) x.fa void main() inicjalizacja podsystemu COM CoInitialize(NULL); wchar_t progid[] = L"COMLecture.Approach5"; CLSID clsid; CLSIDFromProgID(progid, &clsid); / Klient w MFC (po utworzeniu klasy wrapper IA ia; ia.createdispatch("comlecture.approach5"); ia.fa(); IDispatch *pidispatch = NULL; HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch, (void**)&pidispatch); assert(succeeded(hr)); DISPID dispid; OLECHAR *name = L"fa"; hr = pidispatch->getidsofnames(iid_null, &name, 1, GetUserDefaultLCID(), &dispid); assert(succeeded(hr)); VARIANTARG varg; VariantInit(&varg); varg.vt = VT_EMPTY; DISPPARAMS param; param.cargs = 0; param.rgvarg = &varg; param.cnamedargs = 0; param.cargs = NULL; hr = pidispatch->invoke(dispid, IID_NULL, GetUserDefaultLCID(), DISPATCH_METHOD, ¶m, NULL, NULL, NULL); assert(succeeded(hr)); 11
Wstęp do programowania składnikowego Programowanie składnikowe Złota zasada: Interfejsem jest wszystko CZĘŚĆ 1: składniki i interfejsy Interfejsy lepiej oddają funkcjonalność obiektów niż dziedziczenie Jarosław Francik Założenia: 1. Programowanie składnikowe to sposób programowania (filozofia) 2. COM to tylko jedna ze specyfikacji wspierających programowanie składnikowe 3. W ramach wykładu korzystamy ze zwykłego C++ by tworzyć oprogramowanie składnikowe 4. W miarę potrzeb będziemy wprowadzać elementy COM Pierwsze podejście... Interfejs = klasa abstrakcyjna Składnik = klasa implementująca abstrakcyjny interfejs (dziedzicząca po klasie abstrakcyjnej) Wiele interfejsów = dziedz. wielobazowe #include <iostream.h> interfejs A class IA public: virtual void fa() = 0; ; interfejs B class IB public: virtual void fb() = 0; ; Składnik class CMyComp : public IA, public IB Implementacja interfejsu A virtual void fa(); void CMyComp::fa() cout << "Jestem fa" << endl; void CMyComp::fb() cout << "Jestem fb" << endl; void main() CMyComp *pcomp = new CMyComp; wskaźnik do interfejsu A IA *pia = pcomp; pia->fa(); wskaźnik do interfejsu B IB *pib = pcomp; pib->fb(); Model komunikacji klient-składnik Klient Składnik ; Implementacja interfejsu B virtual void fb(); delete pcomp; 1
Pierwsze podejście... Specyfikacja COM nie wymusza stosowania klas C++, jednak: binarny format tablicy funkcji wirtualnych C++ jest zgodny ze specyfikacją COM Pierwsze podejście... Wprowadzamy ułatwienia i wymogi COM: include <objbase.h> interface (słowo kluczowe C++) stdcall konwencja wywołania Pierwsze podejście... [ listing #1 ] Słaby punkt: wykorzystujemy bezpośrednio wskaźnik na klasę komponentu w ten sposób zbyt mocno wiążemy klienta z implementacją składnika Rozwiązanie: stworzyć odrębną funkcję CreateInstance, której implementacja jest wewnętrznym szczegółem modułu składnika Lepsze podejście... Publicznie widzimy: interfejsy deklarację funkcji CreateInstance narzędzie do uzyskiwania wskazanych interfejsów? oczywiście też interfejs: IUnknown 2
Wstęp do programowania składnikowego CZĘŚĆ 2: interfejs Iunknown Jarosław Francik Interfejs IUnknown interface IUnknown virtual HRESULT stdcall QueryInterface (const IID &iid, void **ppc) = 0; virtual ULONG stdcall AddRef() = 0; virtual ULONG stdcall Release() = 0; ; Każdy interfejs COM dziedziczy po IUnknown IUnknown::QueryInterface Klient 1. Klient wywołuje QueryInterface(IID) 2. Obiekt zwraca wskaźnik na interfejs 3. Klient może wywoływać metody Składnik COM HRESULT QueryInterface(const IID &iid, void **p); IUnknown::QueryInterface (użycie przez klienta) inicjalizacja składnika IUnknown *pcomp = CreateInstance(); chcemy skorzystać z interfejsu IA IA *pia = NULL; HRESULT hr = pcomp->queryinterface (IID_IA, (void**)&pia); if (SUCCEEDED(hr)) pia->fa(); identyfikator interfejsu IID (GUID) IUnknown::QueryInterface (implementacja) HRESULT _stdcall CMyComp::QueryInterface(REFIID iid, void **ppv) if (iid == IID_IUnknown) *ppv = (IA*)this; else if (iid == IID_IA) *ppv = (IA*)this; else if (iid == IID_IB) *ppv = (IB*)this; else ppv = NULL; return E_NOINTERFACE; ((IUnknown*)(*ppv))->AddRef(); Funkcja CreateInstance IUnknown *CreateInstance() IUnknown *p = (IA*)new CMyComp; p->addref(); return p; W bibliotece COM API dostępna jest funkcja CoCreateInstance o podobnym działaniu 1
Drugie podejście T T Co widzi klient? T interfejsy T identyfikatory interfejsów (IID) T nagłówek funkcji CreateInstance (w COM korzystamy raczej z funkcji bibliotecznej CoCreateInstance) Co implementuje składnik? T klasa implementująca interfejs IUnknown oraz własne interfejsy T funkcja CreateInstance [ listing #2 ] Nie używamy dziedziczenia wirtualnego! Jest ono niezgodne z formatem binarnym COM QueryInterface QueryInterface możemy stosować do dowolnego interfejsu, nie tylko do IUnknown gdyż wszystkie interfejsy dziedziczą po IUnknown QueryInterface IUnknown *p = CreateInstance(); IA *pia = NULL; hr = p->queryinterface(iid_ia, (void**)&pia); if (!SUCCEEDED(hr)) return; Interfejs IB otrzymujemy z IA IB *pib = NULL; hr = pia->queryinterface(iid_ib, (void**)&pib); if (!SUCCEEDED(hr)) return; Interfejs IUnknown otrzymujemy z IB IUnknown * punknown = NULL; hr = pib->queryinterface(iid_iunknown, (void**)&punknown); if (!SUCCEEDED(hr)) return; QueryInterface T T T T Za każdym razem otrzymuję ten sam interfejs IUnknown Zawsze mogę otrzymać interfejs, który już kiedyś uzyskałem Zawsze mogę uzyskać interfejs, który już mam Jeśli mogę otrzymać interfejs gdziekolwiek, to mogę go otrzymać wszędzie 2
QueryInterface T definiuje obiekt T pozwala udostępniać różne wersje interfejsów dla tego samego obiektu T pomaga utrzymać kompatybilność wstecz przy wypuszczaniu nowej wersji produktu T ALE: wymaga to dotrzymania założeń przez projektanta nowej wersji!!! Zliczanie referencji: AddRef/Release T Klasa składnika zawiera licznik referencji T AddRef zwiększa licznik T Release zmniejsza licznik i w razie potrzeby usuwa składnik z pamięci T Utworzenie obiektu = AddRef T Porzucenie obiektu = Release Zliczanie referencji: AddRef/Release ULONG _stdcall CMyComp::AddRef() return InterlockedIncrement(&m_nRef); ULONG _stdcall CMyComp::Release() if (InterlockedDecrement(&m_nRef) == 0) delete this; return 0; return m_nref; AddRef/Release: trzy proste zasady T Wywołaj AddRef zanim zwrócisz wynik T jeśli zwracasz interfejs jako wartość funkcji. Dotyczy też QueryInterface i CreateInstance! T Wywołaj Release kiedy skończysz T gdy nie będziesz już dłużej wykorzystywał interfejsu T Wywołaj AddRef gdy robisz przypisanie T AddRef/Release: Optymalizacja Nie musisz stosować AddRef/Release w przypadku użycia wskaźnika o zagnieżdżonym zasięgu (w obrębie zasięgu innego wskaźnika) T T T T T T parametr wejściowy nic (jest zagnieżdżony) parametr wyjściowy AddRef parametr wejściowo-wyjściowy być może AddRef + Release zmienna lokalna nic (jest zagnieżdżona) zmienna globalna AddRef + Release w przypadku wątpliwości AddRef + Release Drugie podejście... T Słaby punkt: T funkcja CreateInstance zbyt mocno wiąże klienta z serwerem T Rozwiązanie: T skorzystamy z technologii COM... 3
Wstęp do programowania składnikowego CZĘŚĆ 3: Funkcja CoCreateInstance, fabryki klas i serwery COM Jarosław Francik Podstawy technologii COM HRESULT standardowy typ wartości zwracanych przez funkcje S_OK S_FALSE E_UNEXPECTED E_NOTIMPL E_NOINTERFACE E_OUTOFMEMORY Bity: 0-15 return code co się stało 16-30 facility code id części systemu 31 sukces czy porażka Makro SUCCEEDED Funkcja FormatMessage Podstawy technologii COM Rejestr Windows HKEY_CLASSES_ROOT CLSID 59F1DE0D-8843-4A57-82CE-D6DBA3D28D28: Wykład COM, podejście 3 T InProcServer32: c:\windows\system32\component.dll T ProgID: COMLecture.Approach3.1 T VersionIndependentProgID: COMLecture.Approach3 COMLecture.Approach3.1: Wykład COM, podejście 3 CLSID: 59F1DE0D-8843-4A57-82CE-D6DBA3D28D28 COMLecture.Approach3: Wykład COM, podejście 3 CLSID: 59F1DE0D-8843-4A57-82CE-D6DBA3D28D28 CurVer: COMLecture.Approach3.1 Podstawy technologii COM Rejestr Windows (c.d.) Przydatne funkcje: CLSIDFromProgID Rejestrowanie serwerów COM Serwer musi sam dostarczyć eksportowanych funkcji: DllRegisterServer DllUnregisterServer Przydatne narzędzia: RegOpenKeyEx, RegCreateKeyEx, RegSetValueEx, RegEnumKeyEx, RegDeleteKey, RegCloseKey Pliki Registry.h i Registry.cpp można znaleźć wraz z programem przykładowym approach3 Podstawy technologii COM Przydatne funkcje: CoInitialize(NULL); dla każdego procesu osobno! CoUninitialize StringFromCLSID StringFromIID CLSIDFromString IIDFromString CoTaskMemAlloc CoTaskMemFree Podstawy technologii COM CoCreateInstance ( const CLSID &clsid, IUnknown *piunknownouter, DWORD dwclscontext, const IID &iid, void **ppv); 1
Trzecie podejście - klient Zastępujemy dotychczasowy CreateInstance wywołaniem CoCreateInstance Powiązanie między klientem a serwerem już tylko poprzez CLSID! Trzecie podejście - klient CoInitialize(NULL); IA *pia = NULL; HRESULT hr = CoCreateInstance( CLSID_MyComponent, NULL, CLSCTX_INPROC_SERVER, IID_IA, (void**)&pia); if (SUCCEEDED(hr)) pia->fa(); pia->release(); Fabryka klas IClassFactory interfejs służący do tworzenia egzemplarzy klas (obiektów) interface IClassFactory HRESULT stdcall CreateInstance (IUnknown *punknownouter, const IID& iid, void **ppv); HRESULT stdcall LockServer(BOOL b); w wywołaniu CreateInstance brakuje CLSID Fabryka klas służy do tworzenia obiektów określonej klasy Fabryka klas Funkcja CoGetClassObject zwraca wskaźnik na fabrykę klas dla określonego CLSID HRESULT stdcall CoGetClassObject( const CLSID &clsid, DWORD dwclscontext, COSERVERINFO *pserverinfo, const IID &iid, void **ppv); CoGetClassObject wywołuje DllGetClassObject: funkcję obowiązkowo eksportowaną przez serwery COM Fabryka klas Fabryka klas CoCreateInstance vc CoGetClassObject HRESULT stdcall CoCreateInstance (...) *ppv = NULL; IClassFactory *pfactory = NULL; HRESULT hr = CoGetClassObject(clsid,..., IID_IClassFactory, &pfactory); if (SUCCEEDED(hr)) hr = pfactory->createinstance(..., iid, ppv); pfactory->release; return hr; Klient CoCreateInstance COM API CoGet- ClassObject IA* DLL DllGetClassObject Class Factory new CFactory IClassFactory* IClassFactory::CreateInstance IClassFactory::Release pia->fa Składnik new CMyComp QueryInterface 2
Trzecie podejście - serwer Implementacja klasy składnika: prawie bez zmian Implementacja fabryki klas: implementacja IUnknown standardowa implementacja IClassFactory::CreateInstance implementacja IClassFactory::LockServer Implementacja eksportowanych funkcji: DllCanUnloadNow wymaga zliczania instancji DllGetClassObject DllRegisterServer / DllUnRegisterServer [ listing #3 ] Trzecie podejście co dalej? Serwer COM rejestrujemy za pomocą programu regsrv32 Interfejsy mają stałe IID Aplikacje wcale nie muszą korzystać stale z tych samych CLSID 3
TWORZENIE SKŁADNIKÓW WEWNĄTRZPROCESOWYCH Poniższy zwięzły opis przedstawia najważniejsze kroki niezbędne dla uzyskania klienta i serwera wewnątrzprocesowego COM. Ilustracją poniższej instrukcji są kody źródłowe approach3 i approach3a, a także inne materiały dostępne na stronie sun.iinf.polsl.gliwice.pl/~jfrancik/lectures/com.html. 1. STWORZENIE SPECYFIKACJI IDL Plik IDL zawiera specyfikację tworzonych interfejsów i klas składników (a także biblioteki typów). Wynikiem kompilacji pliku abc.idl są między innymi pliki abc.h (deklaracje interfejsów w języku C/C++) oraz abc_i.c (definicje identyfikatorów iid i clsid). [Krok ten eliminuje potrzebę tworzenia pliku nagłówkowego interface.h oraz definiowania explicite identyfikatorów iid / clsid por. listing approach 3] 2. STWORZENIE APLIKACJI KLIENCKIEJ Aplikacja kliencka powinna obejmować obydwa pliki wynikowe kompilacji specyfikacji IDL wspomniane w poprzednim punkcie. Aplikacja inicjalizuje system COM (CoInitialize), zakłada nowy egzemplarz składnika za pomocą funkcji CoCreateInstance i otrzymuje wskaźnik na podany interfejs. Wskaźnik na dowolny, inny interfejs aplikacja może otrzymać wywołując funkcję QueryInterface już posiadanego interfejsu. Aplikacja jest zobowiązana wywoływać funkcje AddRef/Release zgodnie z obowiązującymi zasadami (por. prezentacja approach2.pdf). 3. STWORZENIE MODUŁU DLL SERWERA Na stworzenie modułu serwera składają się następujące czynności: 1. Wprowadzenie do projektu plików wynikowych kompilacji specyfikacji IDL wyszczególnionych w p. 1. 2. Założenie globalnych liczników: aktywnych składników oraz blokad serwera. 3. Stworzenie klasy składnika. Wszystkie implementowane interfejsy powinny być wyszczególnione jako klasy bazowe. Należy zaimplementować wszystkie funkcje składowe tych interfejsów, a także funkcje składowe interfejsu IUnknown: QueryInterface, AddRef oraz Release. [Użycie specyfikacji IDL wymusza, by wszystkie funkcje składowe zwracały HRESULT. Listing Approach3 nie spełnia tego wymogu; stosowną poprawkę można znaleźć w listingu Approach3a] 4. Stworzenie klasy implementującej interfejs IClassFactory (fabrykę klas). Należy zaimplementować zarówno funkcje składowe interfejsu IClassFactory (CreateInstance i LockServer), jak i wyszczególnione wcześniej funkcje składowe interfejsu IUnknown. 5. Implementacja punktu wejściowego DLL (DllMain). W obrębie tej funkcji należy zachować uchwyt modułu DLL HMODULE. 6. Implementacja obowiązkowych funkcji eksportowanych: - DllCanUnloadNow na podstawie wspomnianych w p. 3-2 globalnych liczników ustala, czy serwer może być usunięty z pamięci. - DllGetClassObject sprawdza podany clsid i tworzy odpowiadającą mu fabrykę klas. Zwraca wskaźnik do wskazanego w parametrze interfejsu fabryki klas. - DllRegisterServer na podstawie zachowanego w funkcji DllMain uchwytu modułu DLL oraz dodatkowych, pomocniczych zmiennych globalnych, przeprowadza rejestrację serwera. Pomocne są tu pliki registry.h i registry.cpp ( Dale Rogerson) zawierające pomocne funkcje. - DllUnregisterServer na podstawie dodatkowych, pomocniczych zmiennych globalnych, przeprowadza wyrejestrowanie serwera. Pomocne są tu pliki registry.h i registry.cpp ( Dale Rogerson) zawierające pomocne funkcje. 7. Rejestracja modułu w systemie (za pomocą opcji Tools Register Control w Visual Studio lub wywołując program regsvr32). opracowanie: Jarosław Francik
IDL EKSPRESOWE WPROWADZENIE Jarosław Francik PRZYKŁAD DEFINICJI INTERFEJSU: OBJAŚNIENIA: import "unknwn.idl"; [ object, uuid(b5db3493-9925-4633-b7db-30da5e75d347), helpstring("przykładowy interfejs IA"), pointer_default(unique) ] interface IA : IUnknown HRESULT fun([in] int x, [out] int &r); ; Powyższa definicja odpowiada następującej klasie C++: interface IA : public IUnknown virtual void stdcall fa() = 0; ; Specyfikacja klasy jest poprzedzona fragmentem znajdującym się w nawiasach kwadratowych. Są to rozszerzenia IDL w stosunku do C++: import wstawia plik w IDL (w tym przypadku: definicję klasy IUnknown) object wyróżnik obiektu COM (IDL ma zastosowania szersze niż tylko COM) uuid identyfikator GUID helpstring zwięzły opis w języku naturalnym, udostępniany na poziomie biblioteki typów pointer_default domyślny tryb marshalingu wskaźników; dostepne wartości to: ref wskaźniki są traktowane jak referencje: nie mogą zawierać NULL, nie mogą być zmieniane, w obrębie funkcji nie mogą być tworzone aliasy unique wskaźniki mogą zawierać NULL, mogą być zmieniane, ale w obrębie funkcji nie mogą być tworzone aliasy ptr wszystko powyższe jest dozwolone Specyfikacja funkcji składowych. Wszystkie funkcji w IDL muszą zwracać wartość HRESULT. Poszczególne parametry mogą zawierać dodatkowe określniki, stanowiące rozszerzenia IDL w stosunku do C++. Są one niezbędne dla zapewnienia prawidłowego marshalingu parametrów. Do zdefiniowania parametrów marshalingu są potrzebne: adres przesyłanej zmiennej, kierunek przesyłu argumentu, rozmiar przesyłanego bloku. Pierwszy z powyższych parametrów jest znany podczas wykonania programu. Kierunek przesyłu argumentu określa się za pomocą następujących określników: in out in, out wyróżnik argumentu wejściowego funkcji wyróżnik argumentu wyjściowego (wyniku) funkcji (na ogół wskaźnika lub referencji) wyróżnik argumentu będącego jednocześnie wejściowym i wyjściowym Rozmiar przesyłanego bloku jest najczęściej jednoznacznie określany na podstawie typu przesyłanych danych. W kolejnych podpunktach zostaną przedstawione elementy języka IDL pozwalające na określenie rozmiaru przesyłanych danych w przypadkach, gdy nie może on być ustalony tylko na podstawie typu danych. Przesyłanie łańcuchów wymaga użycia określnika string. Należy pamiętać, że COM obsługuje wyłącznie łańcuchy Unicode (wchar_t zamiast char): HRESULT strfun([in, string] wchar_t *pin, [out, string] wchar_t **pout);
Przesyłanie tablic wymaga określenia rozmiaru tablicy za pomocą określnika size_is. Pozwala on na podanie rozmiaru posługując się innym parametrem, który powinien zawierać rozmiar tablicy: HRESULT arrfunin([in] long nsize, [in, size_is(nsize)] long array[]); HRESULT arrfunout([out, in] long *nsize, [out, size_is(nsize)] long array[]); należy zauważyć, że parametr użyty w size_in może być tylko wejściowy [in] lub wejściowo-wyjściowy [in, out]. Wynika z tego, że COM będzie zawsze wiedzieć, jak dużą tablicę udostępnił klient. Serwer może zapełnić tylko część tej tablicy, stosownie ustawiając parametr nsize. Z tego względu warto rozważyć następującą modyfikację funkcji arrfunout, w której rozdzielono parametr wejściowy i wyjściowy: HRESULT arrfunout2([in] nsizein, [out, size_is(nsizein)] long array[], [out] long *nsizeout); Przekazywanie struktur jest łatwe, gdyż IDL pozwala na bezpośrednie definiowanie struktur: typedef struct Date int D; int M; int Y; ;... HRESULT structfun([in] Date birthdate); Sprawa się komplikuje, gdy elementami struktury są wskaźniki na inne struktury, które również powinny być odwzorowane w ramach marshalingu. Kompilator IDL musi dokładnie wiedzieć, na co wskazuje wskaźnik nie należy więc raczej stosować typu void*. Jeśli przekazywany jest wskaźnik na interfejs COM, dobrym pomysłem jest przekazanie w osobnym parametrze jego IID. Służy temu specjalny określnik iid_is: HRESULT intfun([in] const IID &iid, [out, iid_is(iid)] IUnknown **ppv); Oczywiście mniej więcej to samo można uzyskać przekazując typ interfejsu wprost, np.: HRESULT intfun_naive([out] IMyInterface **ppv); rozwiązanie takie zemści się jednak, gdy zamiast wskaźnika na IMyInterface zechcemy przesłać coś, co wskazuje na jakąś klasę wywiedzoną z IMyInterface. PRZYKŁAD DEFINICJI BIBLIOTEKI TYPÓW: OBJAŚNIENIA: library coclass [ uuid(3221ccff-68b2-442b-afdc-fa9c767f1fc4), version(1.0), helpstring("przykładowa biblioteka typów") ] library MyTypeLib importlib("stdole32.tlb") ; [ uuid(e843265d-b374-4d56-a980-0ec5e9188b47), helpstring("klasa komponentu") ] coclass Component [default] interface IA; interface IB; ; ; definiuje bibliotekę typów o podanym GUID (LIBID). Biblioteka może zawierać jeden lub więcej komponentów klas składników definiuje klasę składników o podanym GUID (CLSID). Klasa może udostępniać jeden lub więcej interfejsów. Jeden z nich może być domyślny, (istotne dla języków makr i VB). Opis powyższy ma charakter wstępny; w szczególności pominięto zagadnienia związane z definiowaniem dispinterfejsów (dipinterface, propget, propput, dual, oleautomation). Wyczerpujący opis języka IDL można znaleźć w MSDN; dobrym sposobem nauki IDL jest też analizowanie różnych dostępnych definicji klas.
TWORZENIE SKŁADNIKÓW ZEWNĄTRZPROCESOWYCH Poniższy zwięzły opis przedstawia najważniejsze kroki niezbędne dla uzyskania klienta i serwera zewnątrzprocesowego COM (serwer w pliku EXE). Tekst ten powinien być używany łącznie z instrukcją dot. tworzenia składników wewnątrzprocesowych (inproc). Ilustracją jest też kod źródłowy approach4, a także inne materiały dostępne na stronie sun.iinf.polsl.gliwice.pl/~jfrancik/lectures/com.html. 1. STWORZENIE SPECYFIKACJI IDL Ten krok przebiega tak samo, jak w przypadku składników inproc. 2. STWORZENIE APLIKACJI KLIENCKIEJ Jedyną różnicą pomiędzy aplikacją korzystającą ze składnika zewnątrzprocesowego (w stosunku do aplikacji używającej składnika inproc) jest wartość trzeciego parametru funkcji CoCreateinstance, która musi zawierać ustawiony bit CLSCTX_LOCAL_SERVER. 3. STWORZENIE MODUŁU DLL PROXY Moduł proxy jest niezbędny przy komunikacji klienta z serwerem zewnątrzprocesowym, nie wymaga jednak specjalnego kodowania. Wystarczy w tym celu: - Stworzyć projekt DLL obejmujący następujące pliki źródłowe, automatycznie generowane podczas kompilacji specyfikacji IDL: dlldata.c, abc_i.c, abc_p.c (przy założeniu, że kompilowano plik abc.idl). - Włączyć linkowanie nastepujących bibliotek (poza standardowymi): rpcndr.lib, rpcns4.lib i rpcrt4.lib. - Dodać definicję symbolu REGISTER_PROXY_DLL - Dołączyć plik def (por. przykład approach4) - Skompilować i zlinkować projekt. - Zarejestrować otrzymany plik DLL (za pomocą opcji Tools Register Control w Visual Studio lub wywołując program regsvr32). 4. STWORZENIE MODUŁU EXE SERWERA Większa część kodu źródłowego serwerów zewnątrz- i wewnątrzprocesowych jest taka sama. W szczególności punkty 1 4 w części instrukcji dotyczącej tworzenia serwerów inproc można zastosować do serwerów zewnątrzprocesowych. Punkty 5 7 tej instrukcji nie odnoszą się do serwerów w plikach EXE, w szczególności nie ma tu funkcji eksportowanych. Oto specyficzne dla tego typu serwerów czynności dodatkowe: 1. Stworzenie funkcji WinMain jak w przykładzie Approach4. Poniżej przedstawiono najważniejsze elementy: - Inicjalizacja systemu COM (CoInitialize). - Analiza linii wywołania: parametrów RegServer (rejestracja serwera), UnregServer (wyrejestrowanie serwera) i Embedding (praca tylko w trybie serwera, a nie aplikacji zwykle powoduje wyłączenie wyświetlenia okna aplikacji). [Uwaga wśród plików projektu Approach4 znajduja się pliki Registry.h i Registry.cpp z modyfikacjami niezbędnymi przy obsłudze składników zewnątrzprocesowych] - Utworzenie i rejestracja fabryk(i) klas (funkcja CoRegisterClassObject). - Wyświetlenie okna (zwykle z wyjątkiem trybu /Embedding) i petla komunikatów - Wyrejestrowanie fabryk(i) klas (CoRevokeClassObject) i deinicjalizacja COM (CoUninitialize) 2. Implementacja zarządzania usuwaniem aplikacji z pamięci. Wymaga to: - przy dekrementacji globalnych liczników (g_ccomponents i g_cserverlocks) sprawdzenia, czy aplikacja nie powinna być usunięta z pamięci: - if (g_ccomponents == 0 && g_cserverlocks == 0) PostQuitMessage(0); - Blokady usuwania aplikacji, gdy używany jest interfejs użytkownika (okno aplikacji): - InterlockedIncrement(&g_cServerLocks); wywołane tuż po otwarciu okna - Zniesienia powyższej blokady w chwili zamknięcia okna aplikacji: - case WM_CLOSE: ::InterlockedDecrement(&g_cServerLocks); w funkcji okienkowej - Dodatkowej kontroli podczas obsługi komunikatu WM_DESTROY: case WM_DESTROY: if (g_ccomponents == 0 && g_cserverlocks == 0) PostQuitMessage(0); 3. Zarejestrowanie serwera poprzez wywołanie go z parametrem /RegServer. opracowanie: Jarosław Francik
Interfejs IDispatch, dispinterfejsy i automatyzacja Jarosław Francik Interface Idispatch : IUnknown HRESULT GetTypeInfoCount([out] UINT RPC_FAR *pctinfo); HRESULT GetTypeInfo( [in] UINT itinfo, [in] LCID lcid, [out] ITypeInfo RPC_FAR * RPC_FAR *pptinfo); HRESULT GetIDsOfNames( [in] REFIID riid, [in] LPOLESTR RPC_FAR *rgsznames, [in] UINT cnames, [in] LCID lcid, [out, size_is(cnames)] DISPID RPC_FAR *rgdispid); HRESULT Invoke( [in] DISPID dispidmember, [in] REFIID riid, [in] LCID lcid, [in] WORD wflags, [in] DISPPARAMS RPC_FAR *pdispparams, [out] VARIANT RPC_FAR *pvarresult, [out] EXCEPINFO RPC_FAR *pexcepinfo, [out] UINT RPC_FAR *puargerr); ; interfejs IDispatch (IDL) Dispinterface Dispinterface Klient IDispatch QueryInterface Składnik COM Klient IDispatch QueryInterface Składnik COM AddRef Release GetTypeInfoCount dispinterface "fun1" 1 "fun2" 2 AddRef Release GetTypeInfoCount dispinterface "fun1" 1 "fun2" 2 GetTypeInfo GetIDsOfNames Invoke "fun3" 3 1 fun1 2 fun2 GetTypeInfo GetIDsOfNames Invoke "fun3" 3 pvtbl fun1 fun2 3 fun3 fun3 dual interface IDispatch Klient QueryInterface AddRef Release GetTypeInfoCount GetTypeInfo GetIDsOfNames Invoke fun1 fun2 fun3 Składnik COM dispinterface "fun1" 1 "fun2" 2 "fun3" 3 Parametry wywołania Invoke dispidmember dispid wywoływanej funkcji riid zarezerwowany, ma być IID_NULL lcid użyj GetUserDefaultLCID() wflags rodzaj funkcji: DISPATCH_METHOD, DISPATCH_PROPERTYGET, DISPATCH_PROPERTYPUT, DISPATCH_PROPERTYPUTREF pdispparams tablica parametrów wywołania tagdispparams pvarresult wynik funkcji (wskaźnik na wariant) pexcepinfo, puargerr sytuacje wyjątkowe 1
DISPPARAMS i Warianty struct tagdispparams VARIANTARG *rgvarg; /*... */ ; struct tagvariant /*... */ VARTYPE vt; /*... */ stałe VT_* union LONG lval; VT_I4 BYTE bval; VT_UI1 SHORT ival; VT_I2 /*... */ BSTR bstrval; VT_BYREF VT_BSTR Ciekawe funkcje: SysAllocString VariantChangeType wchar_t progid[] = L"COMLecture.Approach5.1"; CLSID clsid; CLSIDFromProgID(progid, &clsid); IDispatch *pidispatch = NULL; HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch, (void**)&pidispatch); DISPID dispid; OLECHAR *name = L"fa"; hr = pidispatch->getidsofnames(iid_null, &name, 1, GetUserDefaultLCID(), &dispid); VARIANTARG varg; VariantInit(&varg); varg.vt = VT_EMPTY; DISPPARAMS param; param.cargs = 0; param.rgvarg = &varg; param.cnamedargs = 0; param.cargs = NULL; hr = pidispatch->invoke(dispid, IID_NULL, GetUserDefaultLCID(), DISPATCH_METHOD, ¶m, NULL, NULL, NULL); Klient dla dispinterface Visual C++ i MFC: IA ia; ia.createdispatch("comlecture.approach5.1 ) ia.fa(); Visual Basic: Dim x As Object CreateObject("COMLecture.Approach5.1 ) x.fa Klienci dla dispinterface 2
/ Kalkulator (prosty) - wersja agregowalna import "unknwn.idl"; Interface ICalc [ object, uuid(892753ed-d14e-4d2f-b812-041e0c01f5f3), helpstring("kalkulator (prosty), wersja agregowalna"), pointer_default(unique) ] interface ICalc : IUnknown HRESULT digit([in] int n); HRESULT comma(); HRESULT sign(); HRESULT op([in] int n); HRESULT eq(); HRESULT c(); HRESULT ce(); [propget, id(1), helpstring("wyświetlacz")] HRESULT display([out, retval] double *pval); [propput, id(1), helpstring("wyświetlacz")] HRESULT display([in] double newval); ; Biblioteka typów + clsid składników [ uuid(7fb7b169-2288-4713-98bd-4360e0147dd8), version(1.0), helpstring("kalkulator (prosty), wersja agregowalna, bibl.typów") ] library CalcAggrTypeLib importlib("stdole32.tlb") ; [ uuid(4d3b9d9b-8dcc-4443-bac8-1dcae9955270), helpstring("kalkulator (prosty), wersja agregowalna") ] coclass CalcSimple [default] interface ICalc; ; ; Serwer prostego kalkulatora - wersja agregowalna Copyright (c) 2001 by Jarosław Francik #include "stdafx.h" #include "calc2.h" plik automatycznie utworzony przez MIDL #include "calc2_i.c" plik automatycznie utworzony przez MIDL #include <iostream.h> #include "registry.h" pomocnicze narzędzia do rejestru systemowego... Zmienne globalne static HMODULE g_hmodule = NULL ; static long g_ccomponents = 0 ; static long g_cserverlocks = 0 ; uchwyt modułu DLL Licznik aktywnych składników Licznik dla LockServer Dane do rejestru systemowego const char g_szprogid[] = "Calc.SimpleAggr.2"; const char g_szfriendlyname[] = "Prosty kalkulator COM - wersja agregowalna"; const char g_szverindprogid[] = "Calc.SimpleAggr"; Interfejs INondelegatingUnknown interface INondelegatingUnknown virtual HRESULT stdcall NondelegatingQueryInterface(const IID&, void**) = 0 ; virtual ULONG stdcall NondelegatingAddRef() = 0 ; virtual ULONG stdcall NondelegatingRelease() = 0 ; ; Klasa składnika class CCalc : public ICalc, INondelegatingUnknown public: Implementacja interfejsu IUnknown virtual HRESULT _stdcall QueryInterface(REFIID iid, void **ppv); virtual ULONG _stdcall AddRef(); virtual ULONG _stdcall Release(); Implementacja interfejsu INondelegatingUnknown virtual HRESULT _stdcall NondelegatingQueryInterface(REFIID iid, void **ppv); virtual ULONG _stdcall NondelegatingAddRef(); virtual ULONG _stdcall NondelegatingRelease(); Implementacja interfejsu ICalc virtual HRESULT stdcall digit(int n); virtual HRESULT stdcall comma(); virtual HRESULT stdcall sign(); virtual HRESULT stdcall op(int n); virtual HRESULT stdcall eq(); virtual HRESULT stdcall c(); virtual HRESULT stdcall ce(); virtual HRESULT stdcall get_display(double RPC_FAR *pval); virtual HRESULT stdcall put_display(double newval); Implementacja wewnętrzna double m_display, m_reg; wyświetlacz i drugi rejestr int m_op; kod operacji; 0=nic, 1-4 = + - * / bool m_bappenddisp; tryb dodawania do wyświetlacza; public: CCalc(IUnknown *punknownouter) : m_nref(1), m_display(0.0), m_reg(0.0), m_op(0), m_bappenddisp(false) InterlockedIncrement(&g_cComponents); if (punknownouter) m_punknownouter = punknownouter; zostaliśmy zagregowani! else m_punknownouter = (IUnknown*)(INondelegatingUnknown*)this; ~CCalc() InterlockedDecrement(&g_cComponents); private: long m_nref; IUnknown *m_punknownouter; obiekt do którego delegujemy odwołania ;
Implementacja interfejsu ICalc HRESULT stdcall CCalc::digit(int n) if (!m_bappenddisp) m_display = n; else m_display = m_display * 10 + n; m_bappenddisp = true; HRESULT stdcall CCalc::comma() return E_NOTIMPL; HRESULT stdcall CCalc::sign() m_display = -m_display; HRESULT stdcall CCalc::op(int n) eq(); m_op = n; HRESULT stdcall CCalc::eq() switch (m_op) case 1: m_display = m_reg + m_display; break; case 2: m_display = m_reg - m_display; break; case 3: m_display = m_reg * m_display; break; case 4: m_display = m_reg / m_display; break; m_reg = m_display; m_bappenddisp = false; HRESULT stdcall CCalc::c() m_reg = m_display = m_op = 0; m_bappenddisp = false; HRESULT stdcall CCalc::ce() m_display = 0; m_bappenddisp = false; HRESULT stdcall CCalc::get_display(double RPC_FAR *pval) *pval = m_display; HRESULT stdcall CCalc::put_display(double newval) m_display = newval; Implementacja interfejsu IUnknown HRESULT _stdcall CCalc::QueryInterface(REFIID iid, void **ppv) return m_punknownouter->queryinterface(iid, ppv); ULONG _stdcall CCalc::AddRef() return m_punknownouter->addref(); ULONG _stdcall CCalc::Release() return m_punknownouter->release(); Implementacja interfejsu INondelegatingUnknown HRESULT _stdcall CCalc::NondelegatingQueryInterface(REFIID iid, void **ppv) if (iid == IID_IUnknown) *ppv = (INondelegatingUnknown*)this; else if (iid == IID_ICalc) *ppv = (ICalc*)this; else ppv = NULL; return E_NOINTERFACE; ((IUnknown*)(*ppv))->AddRef(); ULONG _stdcall CCalc::NondelegatingAddRef() return InterlockedIncrement(&m_nRef); ULONG _stdcall CCalc::NondelegatingRelease() if (InterlockedDecrement(&m_nRef) == 0) delete this; return 0; return m_nref;
Class Factory class CFactory : public IClassFactory standardowa deklaracja interfejsu IClassFactory ; HRESULT stdcall CFactory::QueryInterface(const IID& iid, void** ppv) standardowa implementacja ULONG stdcall CFactory::AddRef() standardowa implementacja ULONG stdcall CFactory::Release() standardowa implementacja HRESULT stdcall CFactory::CreateInstance(IUnknown* punknownouter, const IID& iid, void** ppv) if (punknownouter!= NULL && iid!= IID_IUnknown) return CLASS_E_NOAGGREGATION; Utworzenie składnika CCalc* pcalc = new CCalc(pUnknownOuter); if (!pcalc) return E_OUTOFMEMORY; Utworzenie żądanego interfejsu HRESULT hr = pcalc->nondelegatingqueryinterface(iid, ppv); pcalc->nondelegatingrelease(); return hr; HRESULT stdcall CFactory::LockServer(BOOL block) standardowa implementacja Funkcje eksportowane! standardowa implementacja / Kalkulator naukowy (z wykorzystaniem agregacji) import "unknwn.idl"; Interface ICalcScientific [ object, uuid(22e6b303-cefe-4f65-9bed-8623af83903e), helpstring("kalkulator naukowy"), pointer_default(unique) ] interface ICalcScientific : IUnknown HRESULT sqroot(); ; Biblioteka typów + clsid składników [ uuid(f3b7ad02-3d70-46e5-9313-d479b95e4089), version(1.0), helpstring("kalkulator naukowy, biblioteka typów") ] library CalcSciTypeLib importlib("stdole32.tlb") ; [ uuid(ba847794-18e2-42ce-b053-754828a6388e), helpstring("kalkulator naukowy - komponent zagregowany") ] coclass CalcScientific [default] interface ICalcScientific; interface ICalc; ; ; Serwer kalkulatora naukowego (z pierwiastkowaniem) Moduł agreguje składnik Calc2 - prosty kalkulator Copyright (c) 2001 by Jarosław Francik #include "stdafx.h" #include "calcsci.h" plik automatycznie utworzony przez MIDL #include "calcsci_i.c" plik automatycznie utworzony przez MIDL #include <iostream.h> #include "registry.h" pomocnicze narzędzia do rejestru systemowego... #include <math.h> #include "..\\calc2\\calc2.h" #include "..\\calc2\\calc2_i.c" Zmienne globalne jak w poprzednim przykładzie...
Klasa składnika class CCalcScientific : public ICalcScientific public: Implementacja interfejsu IUnknown virtual HRESULT _stdcall QueryInterface(REFIID iid, void **ppv); virtual ULONG _stdcall AddRef(); virtual ULONG _stdcall Release(); Implementacja interfejsu ICalcScientific virtual HRESULT stdcall sqroot(); Funkcja inicjalizująca obiekt wewnętrzny (wywoływana przez fabrykę klas) HRESULT Init() return CoCreateInstance(CLSID_CalcSimple, (IUnknown*)this, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&m_punknowninner); public: IUnknown *m_punknowninner; CCalcScientific() : m_nref(1) InterlockedIncrement(&g_cComponents); ~CCalcScientific() InterlockedDecrement(&g_cComponents); private: long m_nref; ; Implementacja interfejsu ICalcScientific HRESULT stdcall CCalcScientific::sqroot() ICalc *pcalc = NULL; HRESULT hr = m_punknowninner->queryinterface(iid_icalc, (void**)&pcalc); if (SUCCEEDED(hr)) double ddisp; pcalc->get_display(&ddisp); ddisp = sqrt(ddisp); pcalc->put_display(ddisp); return hr; Implementacja interfejsu IUnknown HRESULT _stdcall CCalcScientific::QueryInterface(REFIID iid, void **ppv) if (iid == IID_IUnknown) *ppv = (ICalcScientific*)this; else if (iid == IID_ICalcScientific) *ppv = (ICalcScientific*)this; else if (iid == IID_ICalc) return m_punknowninner->queryinterface(iid, ppv); else ppv = NULL; return E_NOINTERFACE; ((IUnknown*)(*ppv))->AddRef(); ULONG _stdcall CCalcScientific::AddRef() ULONG _stdcall CCalcScientific::Release() standardowe implementacje Class Factory standardowa implementacja fabryki klas (jak w pierwszym przykładzie) zmiany dotyczą tylko poniższego: HRESULT stdcall CFactory::CreateInstance(IUnknown* punknownouter, const IID& iid, void** ppv) if (punknownouter!= NULL) return CLASS_E_NOAGGREGATION; Utworzenie składnika CCalcScientific* pcalcscientific = new CCalcScientific; if (!pcalcscientific) return E_OUTOFMEMORY; Utworzenie wewnętrznego składnika HRESULT hr = pcalcscientific->init(); if (!SUCCEEDED(hr)) return CLASS_E_NOAGGREGATION; Utworzenie żądanego interfejsu hr = pcalcscientific->queryinterface(iid, ppv); pcalcscientific->release(); return hr; Funkcje eksportowane! standardowa implementacja
/ Kalkulator (prosty) wersja z punktami połączeń import "unknwn.idl"; Interface ICalc [ object, uuid(14169cfc-8cc3-45ec-8c46-d080c8de6d39), helpstring("kalkulator (prosty), wersja z punktami połączenia"), pointer_default(unique) ] interface ICalc : IUnknown HRESULT digit([in] int n); HRESULT comma(); HRESULT sign(); HRESULT op([in] int n); HRESULT eq(); HRESULT c(); HRESULT ce(); [propget, id(1), helpstring("wyświetlacz")] HRESULT display([out, retval] double *pval); [propput, id(1), helpstring("wyświetlacz")] HRESULT display([in] double newval); ; Interfejs wychodzący [ object, uuid(a1284ed8-990e-4885-901a-26da646245c8), helpstring("kalkulator - Interfejs wychodzący"), pointer_default(unique) ] interface IOutgoingCalc : IUnknown HRESULT changed([in] double val); HRESULT errorb([in] BSTR msg); ; Biblioteka typów + clsid składników [ uuid(af933814-60b5-4017-a695-413d21340d47), version(1.0), helpstring("kalkulator (prosty), wersja z punktami połączenia, biblioteka typów") ] library CalcAggrTypeLib importlib("stdole32.tlb") ; [ uuid(0d49456b-4a4b-423c-9804-762059b7299a), helpstring("kalkulator (prosty), wersja z punktami połączenia") ] coclass CalcSimple [default] interface ICalc; [source] interface IOutgoingCalc; ; ; Serwer prostego kalkulatora - wersja z punktami połączenia Copyright (c) 2001 by Jarosław Francik #include "stdafx.h" #include "calccp.h" #include "calccp_i.c" #include <iostream.h> #include "registry.h" #include <ocidl.h> plik automatycznie utworzony przez MIDL plik automatycznie utworzony przez MIDL pomocnicze narzędzia do rejestru systemowego... IConnectionPoint, IConnectionPointContainer Zmienne globalne static HMODULE g_hmodule = NULL ; static long g_ccomponents = 0 ; static long g_cserverlocks = 0 ; uchwyt modułu DLL Licznik aktywnych składników Licznik dla LockServer Dane do rejestru systemowego const char g_szprogid[] = "Calc.SimpleCP.2"; const char g_szfriendlyname[] = "Prosty kalkulator - wersja z CP ; const char g_szverindprogid[] = "Calc.SimpleCP"; Wskaźnik do interfejsu ujścia IOutgoingCalc *g_poutgoingcalc; Interfejs INondelegatingUnknown jak w pierwszym przykładzie Klasa składnika class CCalc : public ICalc, INondelegatingUnknown, IConnectionPoint, IConnectionPointContainer public: Implementacja interfejsu IUnknown virtual HRESULT _stdcall QueryInterface(REFIID iid, void **ppv); virtual ULONG _stdcall AddRef(); virtual ULONG _stdcall Release(); Implementacja interfejsu INondelegatingUnknown virtual HRESULT _stdcall NondelegatingQueryInterface(REFIID iid, void **ppv); virtual ULONG _stdcall NondelegatingAddRef(); virtual ULONG _stdcall NondelegatingRelease();
; Implementacja interfejsu IConnectionPoint virtual HRESULT _stdcall GetConnectionInterface(IID RPC_FAR *piid); virtual HRESULT _stdcall GetConnectionPointContainer(IConnectionPointContainer RPC_FAR * RPC_FAR *ppcpc); virtual HRESULT _stdcall Advise(IUnknown RPC_FAR *punksink, DWORD RPC_FAR *pdwcookie); virtual HRESULT _stdcall Unadvise(DWORD dwcookie); virtual HRESULT _stdcall EnumConnections(IEnumConnections RPC_FAR * RPC_FAR *ppenum); Implementacja interfejsu IConnectionPointContainer virtual HRESULT _stdcall EnumConnectionPoints(IEnumConnectionPoints RPC_FAR * RPC_FAR *ppenum); virtual HRESULT _stdcall FindConnectionPoint(REFIID riid, IConnectionPoint RPC_FAR * RPC_FAR *ppcp); dalej jak w pierwszym przykładzie... Implementacja interfejsu Icalc jak w pierwszym przykładzie Implementacja interfejsu IUnknown jak w pierwszym przykładzie Implementacja interfejsu INondelegatingUnknown HRESULT _stdcall CCalc::NondelegatingQueryInterface(REFIID iid, void **ppv) if (iid == IID_IUnknown) *ppv = (INondelegatingUnknown*)this; else if (iid == IID_ICalc) *ppv = (ICalc*)this; else if (iid == IID_IConnectionPoint) *ppv = (IConnectionPoint*)this; else if (iid == IID_IConnectionPointContainer) *ppv = (IConnectionPointContainer*)this; else ppv = NULL; return E_NOINTERFACE; ((IUnknown*)(*ppv))->AddRef(); ULONG _stdcall CCalc::NondelegatingAddRef() standardowa implementacja ULONG _stdcall CCalc::NondelegatingRelease() standardowa implementacja Implementacja interfejsu IConnectionPoint HRESULT _stdcall CCalc::GetConnectionInterface(IID RPC_FAR *piid) return E_NOTIMPL; HRESULT _stdcall CCalc::GetConnectionPointContainer(IConnectionPointContainer RPC_FAR * RPC_FAR *ppcpc) return E_NOTIMPL; HRESULT _stdcall CCalc::Advise(IUnknown RPC_FAR *punksink, DWORD RPC_FAR *pdwcookie) Wartość cookie jest nieistotna przy jednym połączeniu *pdwcookie = 1; oto wskaźnik do interfejsu ujścia return punksink->queryinterface(iid_ioutgoingcalc, (void**)&g_poutgoingcalc); HRESULT _stdcall CCalc::Unadvise(DWORD dwcookie) g_poutgoingcalc->release(); g_poutgoingcalc = NULL; HRESULT _stdcall CCalc::EnumConnections(IEnumConnections RPC_FAR * RPC_FAR *ppenum) return E_NOTIMPL; Implementacja interfejsu IConnectionPointContainer HRESULT _stdcall CCalc::EnumConnectionPoints(IEnumConnectionPoints RPC_FAR * RPC_FAR *ppenum) return E_NOTIMPL; HRESULT _stdcall CCalc::FindConnectionPoint(REFIID riid, IConnectionPoint RPC_FAR * RPC_FAR *ppcp) if (riid == IID_IOutgoingCalc) return QueryInterface(IID_IConnectionPoint, (void**)ppcp); return E_NOINTERFACE; Class Factory I funkcje eksportowane standardowa implementacja fabryki klas (jak w pierwszym przykładzie)
Zawieranie i agregacja Zawieranie (Containment) Powtórne użycie (reuse) Dziedziczenie implementacji Przesłanianie tożsamości obiektów Zawieranie = Containment Agregacja = Aggregation ICalcScientific ICalc CalcScientific ICalc CalcSimple IUnknown IUnknown Agregacja (Aggregation) Punkty połączeń IUnknown SERWER ICalcScientific CalcScientific INondelegatingUnknown IUnknown IConnectionPoint ICalc CalcSimple KLIENT ujście (sink) IConnectionPoint Punkty połączeń interface IConnectionPoint : IUnknown Wymień dostępne punkty połączenia HRESULT GetConnectionInterface([out] IID RPC_FAR *piid); Podaj pojemnik punktów połączenia HRESULT GetConnectionPointContainer( [out] IConnectionPointContainer *ppcpc); Przyjmij wskaźnik do mojego ujścia HRESULT Advise( [in] IUnknown *punksink, [out] DWORD *pdwcookie); Nie wywołuj już mojego ujścia HRESULT Unadvise([in] DWORD dwcookie); Wymień połączenia HRESULT EnumConnections([out] IEnumConnections *ppenum); ; Punkty połączeń IConnectionPointContainer : public IUnknown Wymień dostępne punkty połączenia HRESULT EnumConnectionPoints([out] IEnumConnectionPoints *ppenum); ; Znajdź wskazany punkt połączenia HRESULT FindConnectionPoint([in] REFIID riid, [out] IConnectionPoint *ppcp); 1
Wielowątkowość w Windows Wielowątkowość w COM Marek Mittmann Grudzień 2002 Wątek (ang.( thread) ) to sekwencja wykonywana przez proces, każdy proces ma do wykonania jeden lub więcej wątków Tam, gdzie wątki korzystają ze współdzielonych danych, konieczna jest synchronizacja Do synchronizacji wykorzystujemy: zdarzenia, sekcje krytyczne, semafory i wzajemne wykluczanie obiektów (mutex) Przedziały Typy przedziałów Przedział (ang.( apartment) ) to kolekcja obiektów wraz z ich kontekstami Każdy obiekt należy do dokładnie jednego przedziału Przedziały określają zestaw zasad dla danej grupy obiektów, wymaganych do poprawnej pracy w środowisku wielowątkowym Jednowątkowe (STA) Mają po jednym wątku, proces może mieć wiele STA Synchronizowane przy pomocy kolejki komunikatów Wielowątkowe (MTA) Mają wiele wątków, proces może mieć tylko jeden MTA Brak synchronizacji Neutralne (NA) Proces może mieć jeden NA Proces Proces Przedziały a wątki STA STA STA MTA MTA Przedziały jednowątkowe Każdy STA ma tylko jeden wątek, w pojedynczym procesie może istnieć wiele STA Wszystkie komponenty nie obsługujące modelu wielowątkowego działają w głównym STA Wątek deklaruje obsługę modelu STA przez wywołanie CoInitializeEx z drugim parametrm równym COINIT_APARTMENTTHREAD
Przedziały jednowątkowe c.d. Przedziały wielowątkowe Wywołania metod w STA są automatycznie synchronizowane i dysponowane przy użyciu kolejek komunikatów Każdy komponent zewnątrzprocesowy, który obsługuje model STA, musi zawierać pętlę komunikatów Komponent wykonywalny może blokować wywołania metod w STA za pomocą filtrów komunikatów (interfejs IMessageFilter) W pojedynczym procesie może istnieć tylko jeden MTA, który z kolei może zawierać wiele wątków Wywołania metod w MTA nie są automatycznie synchronizowane Wątek deklaruje obsługę modelu STA przez wywołanie CoInitializeEx z drugim parametrm = COINIT_MULTITHREAD Przedziały neutralne W pojedynczym procesie może istnieć tylko jeden NA Nie ma stałych wątków, obiekty z NA są zawsze wykonywane przez wątek wywołujący Brak wbudowanej synchronizacji Przedziały neutralne są obsługiwane tylko przez komponenty wewnątrzprocesowe Przedziały a komponenty wewnątrzprocesowe Komponenty wewnątrzprocesowe nie wywołują CoInitializeEx,, obsługiwany model wielowątkowości deklarują za pomocą wpisu w rejestrze (klucz CLSID\InprocServer32 InprocServer32). Dopuszczalne wartości to: Apartment STA Neutral NA Free MTA Both obsługuje STA, NA i MTA brak komponent jednowątkowy Przypisywanie do przedziałów Współdziałanie przedziałów Typ przedziału twórcy Model wielowątkowości komponentu Nieokreślony Apartment Free Both Neutral Proces Główny STA główny STA główny STA MTA główny STA NA Obiekt Pośrednik Obiekt Lekki pośrednik STA główny STA STA wywołującego MTA STA wywołującego NA STA MTA MTA główny STA macierzysty STA MTA MTA NA Obiekt Lekki pośrednik Obiekt NA (wątek STA) główny STA STA wywołującego MTA NA NA STA NA NA (wątek MTA) główny STA macierzysty STA MTA NA NA
Współdziałanie przedziałów Typ przedziału twórcy Główny STA Model wielowątkowości komponentu Nieokreślony Apartment Free Both Neutral bezpośredni dostęp bezpośredni dostęp dostęp przez pośrednika bezpośred ni dostęp dostęp przez lekkiego pośrednika Przekazywanie interfejsów pomiędzy przedziałami STA1 IMyInterface* pmyinterface; CoCreateInstance(CLSID_MyCOMClass, NULL, CLSCTX_LOCAL_SERVER, IID_IMyInterface, (void**)&pmyinterface); void MyThread(IStream* pscream) Utwórz STA2 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); STA MTA NA (wątek STA) dostęp przez pośrednika dostęp przez pośrednika dostęp przez pośrednika bezpośredni dostęp dostęp przez pośrednika dostęp przez lekkiego pośrednika dostęp przez pośrednika bezpośredni dostęp dostęp przez pośrednika bezpośred ni dostęp bezpośred ni dostęp bezpośred ni dostęp dostęp przez lekkiego pośrednika dostęp przez lekkiego pośrednika bezpośredni dostęp IStream* pstream; CoMarshalInterThreadInterfaceInStream( IID_IMyInterface, pmyinterface, &pstream); DWORD threadid; CreateThread(0, 0, (LPTHREAD_START_ROUTINE) MyThread, (void*)pstream, 0, &threadid); IMyInterface* pmyinterface; CoGetInterfaceAndReleaseStream( pstream, IID_IMyInterface, (void**) &pmyinterface);... pmyinterface->release(); NA (wątek MTA) dostęp przez pośrednika dostęp przez pośrednika dostęp przez lekkiego pośrednika bezpośred ni dostęp bezpośredni dostęp... pmyinterface->release(); CoUninitialize(); Podsumowanie Podsumowanie c.d. Każdy STA może mieć tylko jeden wątek Komponenty wykorzystujace wątki STA muszą pobierać i dysponować komunikaty okna Przekazywanie wskaźników interfejsów pomiedzy wątkami z różnych przedziałów powinno odbywać się za pośrednictwem szeregowania Każdy wątek korzystający z COM powinien wywołać CoInitializeEx Każdy obiekt jest związany tylko z jednym, ale przedział może zawierać wiele obiektów Główny STA jest tworzony przez wątek, który pierwszy wywoła CoInitializeEx z COINIT_APARTMENTTHREAD Proces może mieć wiele STA, ale tylko jeden MTA i jeden NA Dla obiektów wewnątrzprocesowych należy zdefiniować w rejestrze ThreadingModel
Kategorie komponentów Programowanie składnikowe w modelu COM Jarosław Francik maj 2002 Kategorie komponentów Cel: usprawnienie wyszukiwania klas komponentów Pozycje rejestru systemowego: W HKEY_CLASSES_ROOT: Component Categories zestawienie kategorii (CATID) dla poszczególnych klas: Implemented Categories Required categories Kategorie komponentów Domyślne klasy można je tworzyć nie znając CLSID, a tylko CATID! Emulacja klas: klucz TreatAs funkcja CoTreatAsClass Component Categories Manager interface ICatRegister interface ICatInformation 1
Programowanie składnikowe w modelu COM Jarosław Francik maj 2002 Trwałość (Persistency) PODSTAWOWE INTERFEJSY TRWAŁOŚCI: IPersist IPersistStream IPersistStreamInit IPersistStorage IPersistFile IPersistPropertyBag PODSTAWOWY INTERFEJS IPERSIST interface IPersist : IUnknown HRESULT GetClassID([out] CLSID *pclsid); ; OBSŁUGA STRUMIENI interface IPersistStreamInit : IPersist HRESULT IsDirty(); HRESULT Load([in, unique] IStream *pstr); HRESULT Save([in, unique] IStream *pstr, [in] BOOL fcleardirty); HRESULT GetSizeMax([out] ULARGE_INTEGER *psize); HRESULT InitNew(); ; ZAPIS TRWAŁEGO OBIEKTU pobieramy clsid klasy CLSID clsid; hr = ppersistent->getclassid(&clsid); zapisujemy clsid (standardowa funkcja) hr = WriteClassStm(pStream, clsid) zapisujemy dane obiektu hr = ppersistent->save(pstream, TRUE); interface ISequentialStream : IUnknown HRESULT Read([out, size_is(cb), length_is(*pread)] void *p, [in] ULONG cb, [out] ULONG *pread); HRESULT Write( [out, size_is(cb)] void *p, [in] ULONG cb, [out] ULONG *pwritten); ; ODCZYT TRWAŁEGO OBIEKTU pobieramy CLSID CLSID clsid; hr = ReadClassStm(pStream, &clsid); tworzymy nowe wystąpienie obiektu IPersistStream *ppersistent; hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IPersistStream, (void**)ppersistent); PRACA Z ZESTAWAMI WŁAŚCIWOŚCI Wciągamy dane obiektu ppersistent->load(pstream); SPRYTNE FUNKCJE STANDARDOWE hr = OleSaveToStream(pPersistent, pstream); hr = OleLoadFromStream(pPersistent, pstream); interface IPersistPropertyBag : IPersist HRESULT IsDirty(); HRESULT Load([in] IPropertyBag *ppropbag, [in] IErrorLog *perrorlog); HRESULT Save([in] IPropertyBag *ppropbag, [in] BOOL fcleardirty, [in[ BOOL bsaveallprops); HRESULT GetSizeMax([out] ULARGE_INTEGER *psize); HRESULT InitNew(); ; interface IPropertyBag : IUnknown HRESULT Read([in] LPCOLESTR ppropname, [in, out] VARIANT *pvar, [in] IErrorLog *perrorlog); ; HRESULT Write([in] LPCOLESTR ppropname, [in] VARIANT *pvar); opracowanie: Jarosław Francik