Cpp COM alkeet

Mureakuha

Loikkaa: valikkoon, hakuun

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, &regkey)
    == 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, &regkey)
    == 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;
}
Henkilökohtaiset työkalut