Intro to COM Part 2 - IUnknown, CoCreateInstance, CLSID

OK, sekarang kita akan terjun ke dunia COM.

 

Bagi yg masih belum mengerti kenapa “Kita tidak bisa mengakses fungsi Rotate() walaupun Rotate() terdefinisikan di class yg sama!” , silakan baca ulang artikel Intro To COM Part 1 – Interface Based Programming.

 

Kenapa COM muncul?

 

COM bersifat Language Independent

Yay yay, .NET developer tahu bahwa kita bisa bikin DLL dengan C# dan kemudian menggunakannya dari kode Visual Basic .NET.

 

Sebelum .NET nongol, hal ini sudah di-support oleh COM. Jadi language independent bukanlah hal yang baru di Microsoft development platform.

 

Membuat COM Server tidak harus dengan C++, bisa dengan C, Visual Basic, Delpho, Java, FoxPro, dsb. Satu-satunya requirement bahasa pemrograman untuk bikin COM Server hanyalah dapat membuat binary layout (vTable) sebuah COM object. Binary layout ini secara high level dapat Anda lihat di artikel saya sebelumnya.

 

Membuat COM Client juga bisa dengan bahasa pemrograman apa saja. COM meng-enkapsulasikan implementasi bahasa pemrograman sebuah objek, yang client lihat hanyalah interfaces yang nongol dari object tersebut.

 

 

COM menyediakan Location Transparency

COM Server dan COM Client dapat digunakan dalam 3 skenario berikut:

 

  1. Hubungan In-Process

Sering disingkat sebagain in-proc. Artinya COM Server di-load ke dalam process yg sama dengan si Client.

 

Bagusnya? Speed.

 

Jeleknya? Kalo ada bug di COM Server yg mengakibatkan crash, berarti semua aplikasi yg ada di process tersebut akan crash (yg pastinya client akan ikutan mati). Selain itu, InProc Server selalu mengambil security context si Client.

 


 

  1. Hubungan Out-of-Process

Juga dikenal dengan sebutan local. Artinya COM Server dan COM Client ada di mesin yg sama, tapi dia dua process yg berbeda.

 

Bagusnya? Kalo COM Server crash, COM Client tidak akan ikutan crash (selama ada kode error-handling).

Jeleknya? Lebih lambat dari in-proc, karena akan terjadi marshalling informasi antar proses.

 

Untuk berkomunikasi antar proses, COM menggunakan LRPC (Lightweight Remote Procedure Call).

 


 

 

  1. Hubungan Remote

Ah… bapaknya .NET Remoting. COM Server dan COM Client ada dalam 2 mesin yg berbeda.

 

Jeleknya? Paling lambat.

 

Bagusnya? Ini yg biasanya dibutuhkan enterprise.

 


 

Untuk remote dan local, COM Object bisa di-house (dirumahkan) dalam sebuah .EXE atau .DLL server. Untuk .DLL server, akan di-load dengan sebuah surrogate (pembantu) bernama DLLHOST.EXE. Ah, sekarang Anda tahu kenapa ada proses dllhost.exe di Task Manager – berarti ada COM object yg sedang di-akses.

 

Tentunya dalam sebuah aplikasi enterprise, dibutuhkan surrogate yg handal dalam menangani masalah threading, concurrency, atomic transactions, dsb. Dan super-surrogate tersebut bernama Microsoft Transaction Server (MTS).

 

Jadi dengan location transparency, kode COM Client dan Server tidak usah diubah jika konfigurasinya berubah dari in-proc ke remote.

 

 

IID (Interface Identifiers)

Di artikel sebelumnya, ada kode “enum INTERFACEID”. Namun untuk mendukung location transparency dan name-clash, identifier {0, 1, 2, 3, … } tidak cukup. Bagi kita mungkin 1 = IDraw, tapi bagi programmer X, 1 = IClipboard. Memberi nama pun tidak cukup, bayangkan berapa developer yg tertarik dengan nama IDatabase?

 

Solusi COM adalah dengan men-tagging sebuah Globally Unique Identifier (GUID) ke tiap interface. GUID dijamin unik tiap saat, karena dibuat dari network address ditambah waktu minta GUID baru (sampai detik 100 nanodetik).

 

Untuk artikel ini, GUID yg nempel di interface kita namakan IID (Interface Identifiers). Di artikel selanjutnya, kita akan tahu bahwa COM juga menggunakan GUID untuk COM Classes (CLSIDs), type libraries (LIBIDs), COM Exe (AppIDs), dan lainnya.

 

Anda dapat meminta GUID baru dengan menggunakan tool guidgen.exe yg dapat di-akses dari menu Tools – Create GUID dari Visual Studio 2005.

 


 

 

IUnknown

Semua interface di COM harus inherit interface yg bernama IUnknown. Interface ini berisi 3 methods:

-          QueryInterface()

-          AddRef()

-          Release()

 

QueryInterface memungkinkan Client mendapatkan interface pointer yang berbeda-beda dari sebuah COM Object. Contohnya, dari COM Object Kotak, saya bisa mendapatkan pointer IDraw, ITransform, dan IUnknown itu sendiri dgn menggunakan QueryInterface.

 

AddRef dan Release digunakan untuk reference counting, sebuah mekanisme simple untuk object life management. Karena jika sebuah Client selesai dengan Server, kita ingin meng-klaim memory yg digunakan oleh Client tadi, agar bisa digunakan oleh Client berikutnya.

 

 

CoClass dan COM Object

Mungkin di literatur COM Anda akan bertemu dengan istilah CoClass. CoClass = COM Class, yaitu definisi sebuah User-Defined-Type (class di C++) yang minimal memiliki implementasi IUnknown.

 

Sedangkan COM Object adalah sebuah instance dari CoClass.

 

 

HRESULT

Interface yg bisa diakses secara remote harus mengembalikan HRESULT, oleh karenanya biasakan membuat return type sebuah COM interface dengan HRESULT.

 

Dengan HRESULT, sukses tidaknya sebuah invokasi method dapat dicek menggunakan makro SUCCEEDED dan FAILED.

 

 

COM Macros

Di literatur COM dan contoh kode C++, kita akan sering melihat kode yg menggunakan STDMETHOD, DECLARE_INTERFACE, dan STDMETHODIMP.

 

// Gunakan ketika definisi method

// mengembalikan HRESULT

#define STDMETHOD(method)     virtual HRESULT STDMETHODCALLTYPE method

 

// Gunakan DECLARE_INTERFACE untuk definisi interface

#define DECLARE_INTERFACE_(iface, baseiface) interface iface : public baseiface

 

// Gunakan STDMETHODIMP di CoClass

#define STDMETHODIMP    HRESULT STDMETHODCALLTYPE

 

 

COM String (BSTR)

String di C/C++ menggunakan “null-terminated character array”. Masih ingat menulis ‘\0’ di akhir sebuah string? String “Halo” di C direpresentasikan dengan “Halo\0”.

 

VB, Java, VBScript, JScript dan beberapa bahasa pemrograman lainnya merepresentasikan string “Halo” dengan “4Halo\0”.

 

Karena COM bersifat language-independent, maka semua bahasa pemrograman yg mengerti COM harus menggunakan BSTR sebagai representasi string. BSTR bersifat Unicode, ada string length prefix, dan null-terminated.

 

IClassFactory

Di C++ kita bisa create object baru dengan keyword new. Di Visual Basic 6.0 juga ada New, tapi apakah “new” di C++ sama dengan “new” di VB? Bisa jadi implementasinya beda.

 

Oleh karenanya kita butuh suatu cara yg language-neutral dan location-neutral untuk membuat sebuah COM object. Eng-ing-eng… jawabannya dengan menggunakan standar COM interface bernama IClassFactory.

 

IClassFactory berisi 2 method:

-          CreateInstance() untuk membuat coclass mewakili si client.

-          LockServer() untuk mengunci server walaupun tidak ada instance coclass.

 

 

Kode Sample COM Server (C++) 

Buatlah sebuah Empty Win32 DLL Project di Visual Studio 2005.

 


 

Buat beberapa file dengan nama di bawah, dan ketik kode berikut:

-          CoKotak.h & CoKotak.cpp

-          CoKotakClassFactory.h & CoKotakClassFactory.cpp

-          Iid.h & Iid.cpp

-          Interfaces.h

-          Main.cpp

 

 

// interfaces.h

#pragma once

#include <windows.h>

 

DECLARE_INTERFACE_(IDraw, IUnknown)

{

   STDMETHOD(Draw)() PURE;

};

 

DECLARE_INTERFACE_(ITransform, IUnknown)

{

   STDMETHOD(Rotate)(float degrees) PURE;

   STDMETHOD(Scale)(float size) PURE;

};

 

 

// CoKotak.h

#pragma once

#include <windows.h>

#include "interfaces.h"

 

// COM Class Kotak

class CoKotak : public IDraw,

                public ITransform

{

private:

   ULONG m_refCount;

 

public:

   CoKotak();

   virtual ~CoKotak();

 

   // IUnknown

   STDMETHODIMP QueryInterface(REFIID riid, void** pIFace);

   STDMETHODIMP_(ULONG) AddRef();

   STDMETHODIMP_(ULONG) Release();

 

   // IDraw

   STDMETHODIMP Draw();

  

   // ITransform

   STDMETHODIMP Rotate(float degrees);

   STDMETHODIMP Scale(float size);

};

 

 

// CoKotak.cpp

#include "CoKotak.h"

#include "iid.h"

#include <cstdio>

 

extern ULONG g_objCount;

 

CoKotak::CoKotak()

{

     m_refCount = 0;

     ++g_objCount;

}

CoKotak::~CoKotak()  { --g_objCount; }

 

STDMETHODIMP CoKotak::QueryInterface(const IID &riid, void **pIFace)

{

  // Maunya apa sih?

  if (riid == IID_IUnknown)

     *pIFace = (IUnknown*)(IDraw*)this;

  else if (riid == IID_IDraw)

     *pIFace = (IDraw*)this;

  else if (riid == IID_ITransform)

     *pIFace = (ITransform*)this;

  else {

     *pIFace = NULL;

     return E_NOINTERFACE;

  }

 

  ((IUnknown*)(*pIFace))->AddRef();

  return S_OK;

}

 

STDMETHODIMP_(ULONG) CoKotak::AddRef() { return ++m_refCount; }

STDMETHODIMP_(ULONG) CoKotak::Release()

{

  // Buat Debug

  //wchar_t buff[50];

  //wsprintf(buff, L"CoKotak count %d", m_refCount);

  //MessageBox(NULL, buff, L"CoKotak:Release", MB_OK | MB_SETFOREGROUND);

 

  if (--m_refCount == 0) {

     delete this;

     return 0;

  }

  return m_refCount;

}

 

STDMETHODIMP CoKotak::Draw()

{

  MessageBox(NULL, L"Draw Dipanggil", L"Draw", MB_OK | MB_SETFOREGROUND);

  return S_OK;

}

  

STDMETHODIMP CoKotak::Rotate(float degrees)

{

  wchar_t buff[50];

  swprintf(buff, L"Rotasi dengan %f derajat", degrees);

  MessageBox(NULL, buff, L"Rotate", MB_OK | MB_SETFOREGROUND);

  return S_OK;

}

 

STDMETHODIMP CoKotak::Scale(float size)

{

  wchar_t buff[50];

  swprintf(buff, L"Scale dengan %f kali", size);

  MessageBox(NULL, buff, L"Scale", MB_OK | MB_SETFOREGROUND);

  return S_OK;

}

 

 

// CoKotakClassFactory.h

#pragma once

#include <windows.h>

 

// Class Factory

class CoKotakClassFactory : public IClassFactory

{

public:

   CoKotakClassFactory();

   virtual ~CoKotakClassFactory();

 

   // IUnknown

   STDMETHODIMP QueryInterface(REFIID riid, void** ppv);

   STDMETHODIMP_(ULONG) AddRef();

   STDMETHODIMP_(ULONG) Release();

 

   // IClassFactory

   STDMETHODIMP LockServer(BOOL fLock);

   STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter,

                              REFIID riid, void** ppv);

private:

   ULONG m_refCount;

};

 

 

// CoKotakClassFactory.cpp

#include "CoKotakClassFactory.h"

#include "CoKotak.h"

 

#include <cstdio>

using namespace std;

 

extern ULONG g_objCount;

extern ULONG g_lockCount;

 

CoKotakClassFactory::CoKotakClassFactory()

{

     m_refCount = 0;

     ++g_objCount;

}

CoKotakClassFactory::~CoKotakClassFactory()  { --g_objCount; }

 

STDMETHODIMP CoKotakClassFactory::QueryInterface(REFIID riid, void** ppv)

{

  // maunya apa ye?

  if (riid == IID_IUnknown)

     *ppv = (IUnknown*)this;

  else if (riid == IID_IClassFactory)

     *ppv = (IClassFactory*)this;

  else {

     *ppv = NULL;

     return E_NOINTERFACE;

  }

  ((IUnknown*)(*ppv))->AddRef();

  return S_OK;

}

 

STDMETHODIMP_(ULONG) CoKotakClassFactory::AddRef() { return ++m_refCount; }

STDMETHODIMP_(ULONG) CoKotakClassFactory::Release()

{

  // Buat Debug

  //wchar_t buff[50];

  //wsprintf(buff, L"ClassFactory count %d", m_refCount);

  //MessageBox(NULL, buff, L"ClassFactory:Release", MB_OK | MB_SETFOREGROUND);

 

  if (--m_refCount == 0) {

     delete this;

     return 0;

  }

 

  return m_refCount;

}

 

STDMETHODIMP CoKotakClassFactory::LockServer(BOOL fLock)

{

  if (fLock) ++g_lockCount;

  else --g_lockCount;

  return S_OK;

}

 

STDMETHODIMP CoKotakClassFactory::CreateInstance(LPUNKNOWN pUnkOuter,

                              REFIID riid, void** ppv)

{

  // ga support aggregation

  if (pUnkOuter != NULL)

     return CLASS_E_NOAGGREGATION;

 

  CoKotak *pKotakObj = NULL;

  HRESULT hr;

 

  // Bikin Kotak!

  pKotakObj = new CoKotak;

 

  // Tanya interface

  hr = pKotakObj->QueryInterface(riid, ppv);

 

  // ada problem?

  if (FAILED(hr))

     delete pKotakObj;

 

  return hr;

}

 

 

// iid.h

#pragma once

 

// Gunakan Tools|Create GUID|DEFINE_GUID

// untuk generate GUID berikut

 

// {DAC7166D-E6E0-4214-95A6-6E6DBF9A5F6F}

DEFINE_GUID(IID_IDraw,

0xdac7166d, 0xe6e0, 0x4214, 0x95, 0xa6, 0x6e, 0x6d, 0xbf, 0x9a, 0x5f, 0x6f);

 

// {075F0CB6-8F7E-4e9e-BC32-65C2A72BB4DD}

DEFINE_GUID(IID_ITransform,

0x75f0cb6, 0x8f7e, 0x4e9e, 0xbc, 0x32, 0x65, 0xc2, 0xa7, 0x2b, 0xb4, 0xdd);

 

// {9458DD92-D2AD-4914-9D8B-D78D9A2F404C}

DEFINE_GUID(CLSID_CoKotak,

0x9458dd92, 0xd2ad, 0x4914, 0x9d, 0x8b, 0xd7, 0x8d, 0x9a, 0x2f, 0x40, 0x4c);

 

 

// iid.cpp

 

// Betulin external link error

#include <windows.h>

#include <initguid.h>

#include "iid.h"

 

 

 

// main.cpp

#include "CoKotakClassFactory.h"

#include "iid.h"

 

ULONG g_lockCount = 0;  // client locks

ULONG g_objCount  = 0;  // object yg hidup di rumah

 

 

// DLL Component Housing

 

// Expose Class Factories to ServiceControlManager

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv)

{

   MessageBox(NULL, L"DllGetClassObject dipanggil", L"COM Kotak", MB_OK | MB_SETFOREGROUND);

 

   HRESULT hr;

   CoKotakClassFactory *pKFact = NULL;

 

   // Cuman tahu bikin kotak di House ini

   if (rclsid != CLSID_CoKotak)

      return CLASS_E_CLASSNOTAVAILABLE;

 

   pKFact = new CoKotakClassFactory;

   hr = pKFact->QueryInterface(riid, ppv);

   if (FAILED(hr))

      delete pKFact;

  

   return hr;

}

 

// Kapan DLL ini boleh di unload dari memory?

STDAPI DllCanUnloadNow()

{

  if (g_lockCount == 0 && g_objCount == 0) {

       MessageBox(NULL, L"COM Server telah di-UNLOAD", L"COM Kotak", MB_OK | MB_SETFOREGROUND);

      return S_OK;   // boleh koq

   }

   else

      return S_FALSE; // gw masih mau hidup

}

 

Module.DEF untuk Export DLL Functions

Gunakan Add-New Item, pilih Module Definition File (.def) dan tambahkan kode berikut ke COMServer.def:

 

LIBRARY   "COMServer"

 

EXPORTS

     DllGetClassObject  @1   PRIVATE

     DllCanUnloadNow    @2   PRIVATE

 

 

Registry Setting

Setelah semua kode di-compile dan tidak ada error, langkah terakhir membuat COM Server adalah membuat .REG file. Buatlah sebuah file dengan extensi .reg dan tambahkan kode berikut:

REGEDIT

HKEY_CLASSES_ROOT\COMServer.CoKotak\CLSID = {9458DD92-D2AD-4914-9D8B-D78D9A2F404C}

HKEY_CLASSES_ROOT\CLSID\{9458DD92-D2AD-4914-9D8B-D78D9A2F404C} = COMServer.CoKotak

HKEY_CLASSES_ROOT\CLSID\{9458DD92-D2AD-4914-9D8B-D78D9A2F404C}\

InprocServer32 = C:\temp\COMServer\debug\COMServer.dll

 

-          Sesuaikan CLSID dengan CLSID Anda yg ada di interfaces.h

-          Baris pertama file .reg harus berisi REGEDIT

-          Harus ada satu spasi di sebelah kiri dan kanan simbol = (sama dengan)

-          Ada 3 baris HKEY_CLASSES_ROOT, kode diatas mungkin akan word-wrap ke bawah.

 

Setelah selesai, dobel-klik file .reg ini untuk memasukkan infonya ke dalam registry.

 

 

COM Client di C++

Kita akan membuat COM Client untuk CoKotak dgn C++ dahulu. Di artikel berikutnya, kita akan membuat COM Client dengan bahasa .NET seperti C#.

 

BootStrapping

Tiap thread yg akan memanggil COM library harus meng-initialize COM Subsystem agar bisa ngobrol dengan COM Runtime. Ini mirip Constructor & Destructor. Setelah selesai, harap mematikan COM Subsystem. Jadi kode yg perlu ditambahkan di awal adalah CoInitialize() dan kode sebelum keluar adalah CoUnitialize()

 

Aktivasi COM Object

Untuk me-load COM Server dan membuat COM Object baru, kita bisa gunakan CoGetClassObject() atau CoCreateInstance().

 

Singkatnya, jika kita memanggil CoCreateInstance 10 kali, berarti kita akan meng-create dan men-destroy ClassFactory 10 kali. Beda dengan CoGetClassObject yg akan mengembalikan handle ke sebuah ClassFactory, sehingga dengan 1 ClassFactory kita bisa memanggil ClassFactory->CreateInstance() sebanyak 10 kali.

 

Kode COM Client

Dgn Visual Studio 2005, buat sebuah Win32 Console Application dengan tipe Empty Project.

 

Kita butuh file berikut dari folder COM Server:

-          iid.h & iid.cpp

-          interfaces.h

 

Masukkan ketiga file diatas ke dalam Project Solution COM Client.

 

Lalu buat file main.cpp dan ketik kode berikut:

// main.cpp -COM Client

#include "interfaces.h"

#include "iid.h"

 

int main()

{

     CoInitialize(NULL);

     HRESULT hr;

     IClassFactory *pCF = NULL;

     IDraw *pIDraw = NULL;

     ITransform *pITransform = NULL;

 

     // Minta ClassFactory untuk CoKotak

     hr = CoGetClassObject(CLSID_CoKotak,

          CLSCTX_INPROC_SERVER, NULL,

          IID_IClassFactory, (void**)&pCF);

 

     // Buat COM Object CoKotak dan IDraw

     hr = pCF->CreateInstance(NULL, IID_IDraw, (void**)&pIDraw);

 

     // selesai dgn ClassFactory

     pCF->Release();

 

     // Gunakan IDraw

     if (SUCCEEDED(hr)) {

          pIDraw->Draw();

     }

 

     // Gw mau ITransform sekarang

     hr = pIDraw->QueryInterface(IID_ITransform, (void**)&pITransform);

     pIDraw->Release();

     if (SUCCEEDED(hr)) {

          pITransform->Rotate(90);

          pITransform->Scale(2);

          pITransform->Release();

     }

 

     CoUninitialize();

     return 0;

}

 

 

What Next?

Sekarang Anda tahu maksud registry entry InProcServer32, CLSID, dan HKEY_CLASSES_ROOT. Ini cikal bakal Global Assembly Cache di .NET.

 

Di artikel selanjutnya, kita akan melihat sisi Language-Independent COM dgn mengakses C++ CoKotak kita dari C# dan VB6.

Share this post: | | | |
Published Tuesday, February 20, 2007 8:47 AM by zeddy

Comments

No Comments

Leave a Comment

(required) 
(required) 
(optional)
(required) 

Enter the numbers above:
Powered by Community Server (Commercial Edition), by Telligent Systems