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:
- 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.

- 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).

- 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.