Cpp COM alkeet
Mureakuha
Seuraavassa käydään läpi C:n ja C++:n avulla COM:n alkeita lähinnä Windows -ympäristössä.
Sisällysluettelo |
Perustietoa
COM tulee sanoista 'Component Object Model'. Se on Microsoftin määritys ohjelmakomponenttien luontia varten. COMia on mahdollista käyttää helpommin oliokielillä, mutta minimivaatimuksena on:
- pystyttävä tekemään tietueita, joiden jäsenet ovat osoittimia
- pystyttävä kutsumaan tietueissa määritettynä olevia funktioita
Sovelluksia on tehty Microsoft, MacOs ja Unix -ympäristöihin. COM / OLE:lla tehdyt ikkunat eroavat Win32:n vastaavista sillä, ettei viestien välitys tapahdu SendMessage / GetMessage:n kautta. COM / OLE:ssa on sen sijaan osoittimia funktioihin, joita voi kutsua. Lisäksi tarvitaan erikoisfunktiot, joita ikkuna voi puolestaan kutsua.
Edellä ollut OLE on ohjelmien yhdistämistekniikka, jonka avulla voi jakaa tietoja ohjelmien välillä. Sitä käytetään mm. InternetExplorer -selaimeen ja Office -sovellutuksiin.
VTable
COM olio muodostuu osoittimesta tietueeseen, jossa on puolestaan osoittimia funktioihin. Tietuetta kutsutaan VTable:ksi tai liittymäksi (Interface). Jokaisessa VTable:ssa on oltava alussa vähintään IUnknown -liittymän tiedot oikeassa järjestyksessä.
IUnknown sisältää
- QueryInterface, jolla voi kysellä yhteyttä toiseen liittymään (eli toisen osoitetta)
- AddRef, jossa lisätään olioon liittyvien referenssien määrää (mahdollisesti alustetaan muuttujia)
- Release, jolla vähennetään olioon liittyvien referenssien määrää (mahdollisesti vapautetaan edellisessä kohdassa varattu muisti)
Muut VTable:n jäsenien nimet, osoitteet, tehtävät ja määrät riippuvat liittymän käyttötarkoituksesta.
COM:n liittymä (Interface):
- ei ole C++ luokka
- ei ole olio
- jokaiseen liittyy tunnus, GUID, joka on jokaiselle liittymälle eri numero (ei ole riippuvainen nimistä)
- on muuttumaton yhteys eli jokainen uusi versio saa uuden numeron
COM oliolla on puolestaan erilainen numerotunnus, CLSID, joka kertoo mikä serveri toteuttaa palvelun ja missä serveri sijaitsee. Jotta COMia voitaisiin käyttää Windowsissa, on kutsuttava CoInitialize tai CoInitializeEx -funktioita ja lopuksi CoUninitialize -funktiota. Vaihtoehtoisesti, jos käytetään OLE:a, kutsutaan aluksi OleInitialize:a ja lopuksi OleUninitialize:a.
C -kielellä
typedef struct defOMAOLIO { struct OMAOLIO_VTBL *lpVtbl; //Mahdolliset muut jäsenet } OMAOLIO; typedef struct defOMAOLIO_VTBL { HRESULT (STDMETHODCALLTYPE* QueryInterface)(OMAOLIO* This, REFIID riid, LPVOID* ppvObject); HRESULT (STDMETHODCALLTYPE* AddRef)(OMAOLIO* This); HRESULT (STDMETHODCALLTYPE* Release)(OMAOLIO* This); //Mahdolliset muut jäsenet } OMAOLIO_VTBL; HRESULT STDMETHODCALLTYPE XQueryInterface(OMAOLIO FAR* This, REFIID riid, LPVOID FAR* ppvObj) { //Koodi on poistettu tästä välistä return(S_OK); } static unsigned refcount; //Referenssien lukumäärä HRESULT STDMETHODCALLTYPE XAddRef(OMAOLIO FAR* This) { return(++refcount); //Palautetaan referenssin numero } HRESULT STDMETHODCALLTYPE XRelease(OMAOLIO FAR* This) { unsigned i = refcount; if(i) refcount = --i; return i; //Palautetaan referenssien uusi määrä } OMAOLIO_VTBL vtbl = {XQueryInterface, XAddRef, XRelease}; OMAOLIO olio = {&vtbl};
C++ -kielellä
//CSomeObject on koostettu IUnknown:sta class CSomeObject : public IUnknown { private: DWORD mRefCount; //Referenssien lukumäärä IUnknown* mpUnk; //IUnknown:n osoite public: CSomeObject(IUnknown* pUnk) { mRefCount = 0; mpUnk = pUnk; }; ~CSomeObject(void) {}; STDMETHODIMP QueryInterface(REFIID riid, void** ppv) { *ppv = NULL; if(riid == IID_IUnknown) *ppv = this; if(NULL == *ppv) return ResultFromScode(E_NOINTERFACE); ((IUnknown*)*ppv)->AddRef(); return NOERROR; }; STDMETHODIMP_(DWORD) AddRef(void) { return ++mRefCount; }; STDMETHODIMP_(DWORD) Release(void) { if(--mRefCount) return mRefCount; delete this; return 0; }; };
Tietueita
COM käyttää yleensä tietotyyppinä tietuetta VARIANT, joka sisältää
- voimassa olevan tietotyypin (VT_BSTR, VT_DECIMAL, ...)
- itse tiedon tai osoittimen
BSTR on tekstin tietue, joka sisältää:
- tekstin pituuden
- tekstin UNICODE:na eli kaksi tavua per merkki
Esimerkki
Seuraava esimerkki luo työpöydälle linkin C:\WINDOWS -kansioon käyttäen COMia.
C -kielellä
#include <windows.h> #include <shlobj.h> //Pääohjelma (parametreja ei käytetä) int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmd, int nShow) { char hakemisto[] = "C:\\WINDOWS"; unsigned short txtWindows[] = L"\\WINDOWS.LNK"; unsigned short vname[] = L"Desktop"; char subkey[]="Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"; HKEY regkey; char buffer[256]; LONG vast = ERROR_MORE_DATA; //Alustetaan jollain virheellä memset(buffer, 0, sizeof(buffer)); //Tyhjennetään teksti varmuuden vuoksi //Avataan Windowsin rekisteri if(RegOpenKeyEx(HKEY_CURRENT_USER, subkey, 0, KEY_QUERY_VALUE, ®key) == ERROR_SUCCESS){ DWORD valuetype; DWORD buffersize = sizeof(buffer) - sizeof(txtWindows); //Haetaan rekisteristä työpöydän hakemisto vast = RegQueryValueExW(regkey, vname, 0, &valuetype, (unsigned char*)&buffer[0], &buffersize); RegCloseKey(regkey); //Suljetaan rekisteri if(vast == ERROR_SUCCESS){ //Tehdään vain, jos edellinen onnistui //Kopioidaan linkkiteksti hakemiston perään memcpy(&buffer[buffersize-2], txtWindows, sizeof(txtWindows)); } } if(vast == ERROR_SUCCESS) { //Vain jos edellinen kohta onnistui if(CoInitialize(0) == S_OK) { //Aloitetaan COM IShellLink* pishell; //ShellLink liittymän osoite IPersistFile* piperf; //PersistFile liittymän osoite //Haetaan osoitin IShellLink:iin if(CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkA, (void**)&pishell) == S_OK){ //Asetetaan hakemisto pishell->lpVtbl->SetPath(pishell, hakemisto); //ja kansion kuvaus pishell->lpVtbl->SetDescription(pishell, hakemisto); //Pyydetään IShellLink:ltä IPersistFile:n osoite pishell->lpVtbl->QueryInterface(pishell, &IID_IPersistFile, (void**)&piperf); //Tallennetaan linkki työpöydälle piperf->lpVtbl->Save(piperf, (const unsigned short *)(void*)&buffer[0], 1); //Vapautetaan IPersistFile piperf->lpVtbl->Release(piperf); //Vapautetaan IShellLink pishell->lpVtbl->Release(pishell); } CoUninitialize(); //Lopetetaan COMin käyttö } } return 0; }
C++ -kielellä
#include <windows.h> #include <shlobj.h> //Pääohjelma (parametreja ei käytetä) int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmd, int nShow) { char hakemisto[] = "C:\\WINDOWS"; unsigned short txtWindows[] = L"\\WINDOWS.LNK"; unsigned short vname[] = L"Desktop"; char subkey[]="Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"; HKEY regkey; char buffer[256]; LONG vast = ERROR_MORE_DATA; //Alustetaan jollain virheellä memset(buffer, 0, sizeof(buffer)); //Tyhjennetään teksti varmuuden vuoksi //Avataan Windowsin rekisteri if(RegOpenKeyEx(HKEY_CURRENT_USER, subkey, 0, KEY_QUERY_VALUE, ®key) == ERROR_SUCCESS){ DWORD valuetype; DWORD buffersize = sizeof(buffer) - sizeof(txtWindows); //Haetaan rekisteristä työpöydän hakemisto vast = RegQueryValueExW(regkey, vname, 0, &valuetype, (unsigned char*)&buffer[0], &buffersize); RegCloseKey(regkey); //Suljetaan rekisteri if(vast == ERROR_SUCCESS){ //Tehdään vain, jos edellinen onnistui //Kopioidaan linkkiteksti hakemiston perään memcpy(&buffer[buffersize-2], txtWindows, sizeof(txtWindows)); } } if(vast == ERROR_SUCCESS){ //Vain jos edellinen kohta onnistui if(CoInitialize(0) == S_OK) { //Aloitetaan COM IShellLink* pishell; IPersistFile* piperf; //Haetaan ShellLink -liittymän osoite if(CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkA, (void**)&pishell) == S_OK){ //Asetetaan hakemisto pishell->SetPath(hakemisto); //ja kansion kuvaus pishell->SetDescription(hakemisto); //Pyydetään IShellLink:ltä PersistFile -liittymän osoite pishell->QueryInterface(IID_IPersistFile, (void**)&piperf); //Tallennetaan linkki työpöydälle piperf->Save((const unsigned short *)(void*)&buffer[0], 1); //Vapautetaan IPersistFile piperf->Release(); //Vapautetaan IShellLink pishell->Release(); } CoUninitialize(); //Lopetetaan COMin käyttö } } return 0; }
