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” (UML Static structure):
Observer - 3 (Bruk)
Sekvensdiagram (UML):
Observer 4 - faser
Oppkobling– Klienten registerer ønske om å motta informasjon om
hendelser (av en type) fra server
Hendelser– Serveren informerer klienten om ting som har skjedd
Nedkobling– Klienten informerer serveren om at den ikke lenger er
interessert i hendelser.
Observer - 5 (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 - 6 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’s IAdviseSource::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 feilsituasjoner.
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 feilsituasjoner.
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
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)
Denne metoden tar ikke hensyn til feilskjekkingDWORD 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 støtter flere former for informasjon (flere connection points). Dette krever mer arbeid i tilfellet med Advise Sinks.
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 - ATLclass 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
JMB:
De uthevede linjene gjentas for hvert connection point som skal implementeres
Advise Sinks - Implementasjonclass 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: typedef std::list< CComPtr<IAdviceSink> > SinkList;
STDMETHOD(StateIsChanged)(IAdviseSource* pSource); void Attach(IAdviseSink* pSink) { m_pSinkList.push_back(pSink); } HRESULT Detach(IAdviceSink* pSink) { SinkList::iterator it; it = std::find(m_pSinkList.begin(),m_pSinkList.end(), pSink); if ( it == m_pSinkList.end() ) return CONNECT_E_NOCONNECTION; m_pSinkList.erase(it); return S_OK; } private: SinkList m_pSinkList; } m_SourceImpl;
STDMETHOD(Attach)(IAdviceSink* pSink); STDMETHOD(Detach)(IAdviceSink* pSink);};
Advise Sink Implementasjon - sende event IAdviseSink:
STDMETHODIMP(CoAdviseSink::SourceImpl::StateIsChanged)(IAdviceSoure* pSource) { SinkList::iterator pSink = m_pSinkList.begin(); while ( pSink != m_pSinkList.end() ) (*pSink++)->StateIsChanged(pSource); return S_OK;}
{ // …
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:
Denne koden er hentet fra CFirePropNotifyEvent::FireOnRequestEdit og redigert til vårt formål
JMB:
Denne koden er hentet fra CFirePropNotifyEvent::FireOnRequestEdit og redigert til vårt formål
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:
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å.
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:
STDMETHODIMP(CoAdviseSource::Attach)(IAdviseSink* pSink, DWORD* pdwCookie){ typedef IConnectionPointImpl<CoAdviseSource,&IID_IAdviseSink> IAdviseSource; return ((IAdviseSource*)this)->Advise(pSink, pdwCookie);}
JMB:
Signaturen til Attach er modifisert med en Cookie for å ha en enklere mapping til connection points.
JMB:
Signaturen til Attach er modifisert med en Cookie for å ha en enklere mapping til connection points.
MSMQ (Preliminary)
Oppkobling:– Klient og tjener åpner den samme køen, uavhengig av hverandre
Events, sende:– Lag et objekt av typen MSMQMessage
– Sett label og body
– Body kan bl.a. være et COM objekt som implementerer IDispatch og IPersistStream eller IPersistStorage.
Events, motta:– Synkron: Kall MSMQQueue::Peek og så MSMQQueue::Receive
– Asynkron: Implementer MSMQEvent’s interface, og kall MSMQQueue::Receive når Arrived blir kalt
Nedkobling:– Klient og tjener kaller MSMQQueue::Close uavhengig av hverandre.
Sammenligning
Observer Synkronisitet Avstand Scriptklienter
AdviseSink
Primærtsynkron
Nær tilmiddels
Nei
ConnectionPoints
Primærtsynkron
Nær Ja
MSQM Asynkron Middels tilfjern
?
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. Connection points er velegnet til ”nære” og ”synkrone” ting (f.eks.
Controls) MSMQ er velegnet til ”fjerne” og ”asynkrone” ting.
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.