330 likes | 461 Views
Connection Points i COM. Bakgrunn Begrunnelse Bruk. Oversikt over foredrag. Observer pattern Bruk Begrunnelse Variasjoner Løsninger i COM Advise sinks Connection Points Begrunnelse + oversikt Bruk. Noen forutsetninger. Relativt god kjennskap til COM IUnknown IDispatch
E N D
Connection Points i COM Bakgrunn Begrunnelse Bruk
Oversikt over foredrag • Observer pattern • Bruk • Begrunnelse • Variasjoner • Løsninger i COM • Advise sinks • Connection Points • Begrunnelse + oversikt • Bruk
Noen forutsetninger • Relativt god kjennskap til COM • IUnknown • IDispatch • Formål og bruk (løs kobling mellom klient og tjener, transpartent distribuering) • IEnumXXX interfaces
Bakgrunn - Observer • Observer er et av “Patterns” som er beskrevet i Gamma, et al “Design Patterns” [GOF] • Fra Design Patterns: • “Intent: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.” • Det vil si: Vi ønsker et design der noen objekter (Observers) ønsker å bli informert når “ting skjer” med et annet object (Subject)
Observer - 2 (Arkitektur) • Observer har følgende design oversikt i “Design Patterns”:
Observer - 3 (Bruk) • Sekvensdiagram:
Observer - 4 (Eksempler) • Forhold mellom brukergrensesnitt og “modell” • Gir mulighet for bruk flere klienter mot samme state uten unødvendig kommunikasjonsoverhead • Brukes i Microsoft’s “Document-View” arkitektur, og i den eldre (og bedre) “Controller-Model-View” arkitekturen. • Kan med fordel brukes i distribuerte systemer .
Observer - 5 Varianter • Flere Subjects, Observers, og Events. • Kan modifiseres med et “Subscription” object • Kan “flates ut” for bruk i f.eks. C. Dette minner endel om Windows’ “WindowProc” • Asynkron oppdatering • Egen(e) tråd(er) for å la Subject oppdatere Observers • Informasjon om state kan sendes med Update • Flere varianter til “Update” funksjonen
COM og Observer • Observer patternet egner seg godt for distribuert og/eller modulbasert bruk • Mulighet for asynkron oppdatering • Løs kobling (“coupling”) mellom klient og tjener
COM og Observers - Problemer • Hvordan kan en scripting klient/dynamisk klient finne ut Run Time hvilke “Observer” interfaces en “Subject” støtter • Hvordan vil COM’s identitetregler fungere med Detach? (Jeg vet ikke)
COM Observer 1: Advise Sink • Den enkleste måten å lage en form for toveis kommunikasjon mellom klient og tjener • Eksempel: [ uuid(…), object, pointer_default(unique) ] interface IAdviseSink : IUnknown { HRESULT StateIsChanged(IAdviseSource* pSource); }; [ uuid(…), object, pointer_default(unique) ] interface IAdviseSource : IUnknown { HRESULT Attach(IAdviceSink* pSink); HRESULT Detach(IAdviceSink* pSink); // … + metoder for å endre tilstanden til objektet }; [ uuid(…) ] coclass AdviseSource { [default] interface IAdviseSource; };
Advise Sink - Bruk • Lag en klasse som implementerer IAdviseSink • Kall Subject’sIAdviseSource::Subscribe • IAdviseSink::StateIsChanged vil bli kalt fra subject når nødvendig • Før klientens Advise Sink slettes, kall IAdviseSource::Unsubscribe class MyAdviseSink : public IAdviseSink { public: MyAdviseSink(IAdviseSource* pSource) : m_pSource(pSource) { m_pSource->Attach(this); } ~MyAdviseSink() { m_pSource->Detach(this); } STDMETHOD(StateIsChanged)(IAdviseSink*) { ::MessageBox(NULL, ”Server state has changed”, ””, MB_OK); } private: CComPtr<IAdviseSource> m_pSource; }; JMB: Det kan hevdes at det ikke er så lurt å gjøre Attach og Detach i Constructoren og destructoren, ettersom disse funksjonene ikke er gode til å håntere feil situasjoner.
COM Observer 2: Connection Points (endelig!) • Formålet med å bruke Connection Points over Advise Sinks er hovedsaklig å gi støtte for Script klienter, og for å gi en dynamisk (run time) mulighet for å finne ut hvilke “observers” et objekt støtter. • Problemer som bør nevnes: • Mer komplisert enn Advise Sinks. • “Unødvendige” kall gir performance hit med distribuert klient/tjener.
Connection Points - Interfaces • IConnectionPointContainer • IEnumConnectionPoints • IConnectionPoint • IEnumConnections • I tilfellet med Advise Sinks er det vi som implementerer denne funksjonaliteten.
Connection Points - Arkitektur 2 • En server kan ha flere connection points. • En klient kan implementere flere Sources.
IConnectionPointContainer [ object, uuid(…), pointer_default(unique) ] interface IConnectionPointContainer : IUnknown { HRESULT EnumConnectionPoints ( [out] IEnumConnectionPoints ** ppEnum ); HRESULT FindConnectionPoint ( [in] REFIID riid, [out] IConnectionPoint ** ppCP ); };
IEnumConnectionPoints [ object, uuid(...), pointer_default(unique) ] interface IEnumConnectionPoints : IUnknown { [local] HRESULT Next( [in] ULONG cConnections, [out, size_is(cConnections), length_is(*pcFetched)] IConnectionPoint** ppCP, [out] ULONG * pcFetched ); [call_as(Next)] HRESULT RemoteNext( [in] ULONG cConnections, [out, size_is(cConnections), length_is(*pcFetched)] IConnectionPoint** ppCP, [out] ULONG * pcFetched ); HRESULT Skip( [in] ULONG cConnections ); HRESULT Reset(); HRESULT Clone( [out] IEnumConnectionPoints ** ppEnum ); };
IConnectionPoint [ object, uuid(...), pointer_default(unique) ] interface IConnectionPoint : IUnknown { HRESULT GetConnectionInterface( [out] IID * pIID ); HRESULT GetConnectionPointContainer( [out] IConnectionPointContainer ** ppCPC ); HRESULT Advise( [in] IUnknown * pUnkSink, [out] DWORD * pdwCookie ); HRESULT Unadvise( [in] DWORD dwCookie ); HRESULT EnumConnections( [out] IEnumConnections ** ppEnum ); };
IEnumConnections [ object, uuid(…), pointer_default(unique) ] interface IEnumConnections : IUnknown { typedef struct tagCONNECTDATA { IUnknown * pUnk; DWORD dwCookie; } CONNECTDATA; [local]HRESULT Next( [in] ULONG cConnections, [out, size_is(cConnections), length_is(*pcFetched)] CONNECTDATA* rgcd, [out] ULONG * pcFetched ); [call_as(Next)] HRESULT RemoteNext( [in] ULONG cConnections, [out, size_is(cConnections), length_is(*pcFetched)] LPCONNECTDATA rgcd, [out] ULONG * pcFetched ); HRESULT Skip([in] ULONG cConnections); HRESULT Reset(); HRESULT Clone([out] IEnumConnections ** ppEnum); };
Connection Points - Eksempel [ uuid(…), object, dual, pointer_default(unique) ] interface IAdviseSink : IDispatch { HRESULT StateIsChanged(IAdviceSource* pSource); }; [ uuid(…), object, dual, pointer_default(unique) ] interface IAdviseSource : IDispatch { … }; [ uuid(…) ] coclass AdviseSource { [default] interface IAdviseSource; [default, source] interface IAdviseSink; interface IConnectionPointContainer; };
Connection points og Advise Sinks - Forskjeller • Både klient og tjener interface er Dispatch • IAdviseSource’s metoder for registrering og avregistrering er fjernet (Implementert i IConnectionPointContainer med venner)
Connection Points - C++ klient • Klient har implementert et objekt med IAdviseSink, og har en peker til en AdviseSource (coclass) DWORD Connect(IAdviseSource* pSource, IAdviseSink *pSink) { CComPtr<IConnectionPointContainer> pCPC; pSource->QueryInterface(__uuidof(IConnectionPointContainer), &pCPC); CComPtr<IConnectionPoint> pCP; pCPC->FindConnectionPoint(__uuidof(IAdviseSink), &pCP); DWORD dwCookie; pCP->Advice(pSink, &dwCookie); return dwCookie; }
Connection points - Vurderinger 1 • Endel lenger enn AdviseSink tilfellet (ville være en linje: pSource->Subscribe(pSink);) • QueryInterface, FindConnectionPoint og Advice fører alle til kall over nettverket. • I implementasjonen av Advice (eller hver gang IAdviseSource::StateIsChanged kalles), må serveren kalle QueryInterface for å få riktig interface (Advice tar en IUnknown* som argument)
Connection points - Vurderinger 2 • IConnectionPointContainer og IConnectionPoint gir mulighet til å finne ut mer informasjon dynamisk, f.eks. hvilke Source interfaces som støttes (gjennom IConnectionPointContainer::EnumConnectionPoints og IConnectionPoint::EnumConnections)
Connection Points Implementasjon - Server • Implementasjon av Connection Points på en server er relativt smertefritt med bruk av ATL. (mk:@MSITStore:<MSDNDIR>\VCMFC.CHM::/html/_atl_connection_points.htm) • ATL Wizard har støtte for connection points.
Connection Points Implementasjon - ATL class CoAdviseSource : public CComObjectRootEx<CComObjectThreadModel>, public CComCoClass<CoAdviceSource, &CLSID_AdviseSource>, public IConnectionPointContainerImpl<CoAdviseSource>, public IConnectionPointImpl<CoAdviseSource,&IID_IAdviseSink> { public: ... BEGIN_COM_MAP(CoAdviseSource) COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer) END_COM_MAP() BEGIN_CONNECTION_POINT_MAP(CoAdviseSource) CONNECTION_POINT_ENTRY(IID_IPropertyNotifySink) END_CONNECTION_POINT_MAP() ... }; JMB: De uthevede linjene gjentas for hvert connection point som skal implementeres
Advise Sinks - Implementasjon class CoAdviseSource : public CComObjectRootEx<CComObjectThreadModel>, public CComCoClass<CoAdviceSource, &CLSID_AdviseSource> { public: ... BEGIN_COM_MAP(CoAdviseSource) COM_INTERFACE_ENTRY(IAdviseSource) END_COM_MAP() class CSourceImpl : public IAdviseSink { public: STDMETHOD(StateIsChanged)(IAdviseSource* pSource) { std::list<IAdviceSink*>::iterator pSink = m_pSinkList.begin(); while ( pSink != m_pSinkList.end() ) (*pSink++)->StateIsChanged(pSource); return S_OK; } // … Implementasjon av Attach og Detach private: std::list<IAdviceSink*> m_pSinkList; } m_SourceImpl; STDMETHOD(Attach)(IAdviceSink* pSink); STDMETHOD(Detach)(IAdviceSink* pSink); }; JMB: Kaller inn til tilsvarende metode i CSourceImpl
Advise Sink Implementasjon - sende event • IAdviseSink: { // … m_SourceImpl->StateIsChanged(this); }
Connection Points ATL Implementasjon - Sende event CComQIPtr<IConnectionPointContainer, &IID_IConnectionPointContainer> pCPC(pUnk); if (!pCPC) return S_OK; CComPtr<IConnectionPoint> pCP; pCPC->FindConnectionPoint(IID_IAdviseSink, &pCP); if (!pCP) return S_OK; CComPtr<IEnumConnections> pEnum; if (FAILED(pCP->EnumConnections(&pEnum))) return S_OK; CONNECTDATA cd; while (pEnum->Next(1, &cd, NULL) == S_OK) { if (cd.pUnk) { HRESULT hr = S_OK; CComQIPtr<IPropertyNotifySink, &IID_IAdviseSink> pSink(cd.pUnk); if (pSink) hr = pSink->StateIsChanged(dispID); cd.pUnk->Release(); if (hr == S_FALSE) return S_FALSE; } } return S_OK; JMB: Koden er komplisert, ettersom den ønsker at serveren skal bruke klientens rammeverk. Det er mulig at det allerede finnes enklere måter å gjøre dette på. JMB: Denne koden er hentet fra CFirePropNotifyEvent::FireOnRequestEditog redigert til vårt formål
Betraktninger • ATL gir endel kode gratis, men gir også endel kompleksitet (tidliger diskusjon) • Det er imidlertid mulig å ha både en ConnectionPoint og en AdviseSink implementasjon av Observer, nemlig: JMB: Signaturen til Attach er modifisert med en Cookie for å ha en enklere mapping til connection points. STDMETHODIMP(CoAdviseSource::Attach)(IAdviseSink* pSink, DWORD* pdwCookie) { typedef IConnectionPointImpl<CoAdviseSource,&IID_IAdviseSink> IAdviseSource; return ((IAdviseSource*)this)->Advise(pSink, pdwCookie); }
Noen merknader • Connection points vil antageligvis gjennomgå en større endring til COM+. For det meste gjennom endringer i C++ språket i VC++ (versjon 7?) • Advise Sinks går ann å bruke fra VB, men det krever mer arbeid enn Connection points. • Connection points er eneste muligheten fra script klienter. • Connection points og Advise Sinks er kompatible, i det minste dersom man lager sin egen implementasjon.
Oppsummering • Formålet med connection points er å åpne for to-veis kommunikasjon mellom klient og tjener i COM. • Den som implementerer tjeneren bestemmer Connection Point (Observer, Advise Sink) interfacet. • Connection points er en noe klønete og ineffektiv implementasjon av Observer Patternet i [GOF]. • Dersom man skal støtte script-klienter må man bruke Connection Points, eller er man fri til å bruke en egen løsning, som Advise Sinks. • ATL har en standard implementasjon av Connection Points. Den er imidlertid ikke helt perfekt.