Tulisan ini merupakan bagian dari panduan membuat COM DLL (ATL) di Visual Studio 2010 yang mengakses MySQL atas request Albert Antonius:
Bila kamu masih bingung mempraktekkan langkah-langkah yang ada disini, baca dulu bagian 1 sampai 3. Buka Visual Studio 2010, buat sebuah proyek ATL baru dengan nama LatihanCRUDMahasiswa. Lakukan pengaturan proyek seperti yang tunjukkan di artikel bagian 2. Tambahkan sebuah ATL Simple Object dengan nama Mahasiswa
(short name). Beri nilai ProgID dengan Jocki.Mahasiswa
. Pada interface IMahasiswa
, tambahkan property seperti berikut ini:
- Property type bernilai
BSTR
dan Property name bernilai NIM
.
- Property type bernilai
BSTR
dan Property name bernilai Nama
.
- Property type bernilai
int
dan Property name bernilai TahunMasuk
.
Tambahkan 3 variabel private di CMahasiswa
, seperti berikut ini:
- Variable type bernilai
BSTR
dan Variable name bernilai nim
.
- Variable type bernilai
BSTR
dan Variable name bernilai nama
.
- Variable type bernilai
int
dan Variable name bernilai tahunMasuk
.
Buka file Mahasiswa.cpp dan lakukan modifikasi pada isi method hingga method yang ada memiliki isi seperti berikut ini:
STDMETHODIMP CMahasiswa::get_NIM(BSTR* pVal)
{
CComBSTR bstr(nim);
*pVal = bstr.Detach();
return S_OK;
}
STDMETHODIMP CMahasiswa::put_NIM(BSTR newVal)
{
CComBSTR bstr;
bstr.Empty();
bstr.AppendBSTR(newVal);
nim = bstr.Detach();
return S_OK;
}
STDMETHODIMP CMahasiswa::get_Nama(BSTR* pVal)
{
CComBSTR bstr(nama);
*pVal = bstr.Detach();
return S_OK;
}
STDMETHODIMP CMahasiswa::put_Nama(BSTR newVal)
{
CComBSTR bstr;
bstr.Empty();
bstr.AppendBSTR(newVal);
nama = bstr.Detach();
return S_OK;
}
STDMETHODIMP CMahasiswa::get_TahunMasuk(int* pVal)
{
*pVal = tahunMasuk;
return S_OK;
}
STDMETHODIMP CMahasiswa::put_TahunMasuk(int newVal)
{
tahunMasuk = newVal;
return S_OK;
}
Class Mahasiswa
cukup hanya sampai disini saja. Bila kamu kode program yang lebih praktis, kamu bisa mendeklarasikan nim dan nama dengan tipe CComBSTR
(anggap saja latihan buat kamu!) Sekarang, buat sebuah ATL Simple Object dengan nama MahasiswaService
(short name) dan beri nilai ProgID dengan Jocki.MahasiswaService
. Pada interface IMahasiswaService
, tambahkan method:
- Method name berupa
Inisialisasi
, mengandung parameter [in] BSTR user
, [in] BSTR password
, dan [in] BSTR namaDatabase
.
- Method name berupa
SimpanMahasiswa
, mengandung parameter [in] BSTR nim
, [in] BSTR nama
, dan [in] int tahunMasuk
.
- Method name berupa
HapusMahasiswa
, mengandung parameter [in] BSTR nim
.
- Method name berupa
CariMahasiswaByNIM
, mengandung parameter [in] BSTR nim
dan [out, retval] IMahasiswa** ret.
- Method name berupa
Refresh
. Tidak mengandung parameter.
Coba buka isi file LatihanCRUDMahasiswa.idl, kamu harusnya menemukan bagian seperti berikut ini:
interface IMahasiswaService : IDispatch{
[id(1)] HRESULT Inisialisasi([in] BSTR user, [in] BSTR password, [in] BSTR namaDatabase);
[id(2)] HRESULT SimpanMahasiswa([in] BSTR nim, [in] BSTR nama, [in] int tahunMasuk);
[id(3)] HRESULT HapusMahasiswa([in] BSTR nim);
[id(4)] HRESULT Refresh(void);
[id(5)] HRESULT CariMahasiswaByNIM([in] BSTR nim, [out,retval] IMahasiswa** ret);
};
Sekarang, kamu akan menambahkan property baru secara manual dengan mengetik sehingga bagian di atas akan terlihat seperti berikut ini:
interface IMahasiswaService : IDispatch{
[id(1)] HRESULT Inisialisasi([in] BSTR user, [in] BSTR password, [in] BSTR namaDatabase);
[id(2)] HRESULT SimpanMahasiswa([in] BSTR nim, [in] BSTR nama, [in] int tahunMasuk);
[id(3)] HRESULT HapusMahasiswa([in] BSTR nim);
[id(4)] HRESULT Refresh(void);
[id(5)] HRESULT CariMahasiswaByNIM([in] BSTR nim, [out,retval] IMahasiswa** ret);
[propget, id(DISPID_VALUE)] HRESULT Item([in] long n, [out, retval] IMahasiswa** ret);
[propget, id(DISPID_NEWENUM)] HRESULT _NewEnum([out, retval] IUnknown** pVal);
};
Property Item
dan _NewEnum
dibutuhkan untuk membaca setiap record Mahasiswa
yang ada.
Sekarang buka file MahasiswaService.h, tambahkan bagian berikut setelah #include "resource.h"
:
#include "Mahasiswa.h"
#include "mysql_connection.h"
#include <cppconn\driver.h>
#include <cppconn\exception.h>
#include <cppconn\prepared_statement.h>
#include <cppconn\statement.h>
using namespace sql;
Berikutnya, tambahkan private variable berikut ke dalam class CMahasiswaService
:
- Variable type:
Driver*
dan Variable name: driver
.
- Variable type:
Connection*
dan Variable name: cn
.
- Variable type:
Statement*
dan Variable name: stmt
.
- Variable type:
PreparedStatement*
dan Variable name: psInsert
.
- Variable type:
PreparedStatement*
dan Variable name: psUpdate
.
- Variable type:
PreparedStatement*
dan Variable name: psDelete
.
- Variable type:
PreparedStatement*
dan Variable name: psSelectByNIM
.
- Variable type:
PreparedStatement*
dan Variable name: psSelectAll
.
Buka file MahasiswaService.cpp, lalu modifikasi implementasi method CMahasiswaService::Inisialisasi()
sehingga terlihat seperti berikut ini:
STDMETHODIMP CMahasiswaService::Inisialisasi(BSTR user, BSTR password, BSTR namaDatabase)
{
// Bila inisialisasi sudah pernah dipanggil, hapus terlebih dahulu instance
// yang sudah pernah dibuat.
if (cn!=NULL) {
delete stmt;
delete psDelete;
delete psInsert;
delete psSelectAll;
delete psSelectByNIM;
delete psUpdate;
delete cn;
}
// Membuat koneksi ke database
char strUser[100], strPassword[100], strNamaDatabase[100];
size_t len;
wcstombs_s(&len, strUser, user, 100);
wcstombs_s(&len, strPassword, password, 100);
wcstombs_s(&len, strNamaDatabase, namaDatabase, 100);
try {
driver = get_driver_instance();
cn = driver->connect("tcp://127.0.0.1:3306", strUser, strPassword);
cn->setSchema(strNamaDatabase);
// Membuat tabel Mahasiswa bila belum dibuat
stmt = cn->createStatement();
stmt->execute("CREATE TABLE IF NOT EXISTS tblMahasiswa (nim CHAR(10), nama VARCHAR(200), tahunMasuk INT)");
// Menyiapkan PreparedStatement
psSelectAll = cn->prepareStatement("SELECT nim, nama, tahunMasuk FROM tblMahasiswa");
psSelectByNIM = cn->prepareStatement("SELECT nim, nama, tahunMasuk FROM tblMahasiswa WHERE nim = ?");
psUpdate = cn->prepareStatement("UPDATE tblMahasiswa SET nama = ?, tahunMasuk = ? WHERE nim = ?");
psDelete = cn->prepareStatement("DELETE FROM tblMahasiswa WHERE nim = ?");
psInsert = cn->prepareStatement("INSERT INTO tblMahasiswa(nim, nama, tahunMasuk) VALUES (?, ?, ?)");
// Melakukan query seluruh data di tblMahasiswa
this->Refresh();
} catch (SQLException &e) {
return E_FAIL;
}
return S_OK;
}
Method Inisialisasi()
bertanggung jawab untuk menyiapkan koneksi database sehingga dapat dipakai oleh pemanggil method-method lainnya.
Berikutnya, lakukan modifikasi pada CMahasiswaService::SimpanMahasiswa()
sehingga terlihat seperti berikut ini:
STDMETHODIMP CMahasiswaService::SimpanMahasiswa(BSTR nim, BSTR nama, int tahunMasuk)
{
// Konversi BSTR menjadi SQLString
char strNIM[11], strNama[201];
size_t len;
wcstombs_s(&len, strNIM, nim, 10);
wcstombs_s(&len, strNama, nama, 200);
// Memeriksa apakah record sudah pernah tersimpan atau tidak
try {
psSelectByNIM->setString(1, strNIM);
if (psSelectByNIM->executeQuery()->next()) {
// Record sudah tersimpan, lakukan proses update
psUpdate->setString(1, strNama);
psUpdate->setInt(2, tahunMasuk);
psUpdate->setString(3, strNIM);
psUpdate->executeUpdate();
} else {
// Record belum tersimpan, lakukan proses insert
psInsert->setString(1, strNIM);
psInsert->setString(2, strNama);
psInsert->setInt(3, tahunMasuk);
psInsert->executeUpdate();
}
} catch (SQLException &e) {
return E_FAIL;
}
return S_OK;
}
Method CMahasiswa::SimpanMahasiswa()
adalah sebuah method yang melakukan salah satu dari operasi insert atau update. Bila NIM yang diberikan sudah tersimpan di tabel, maka method ini akan memberikan query SQL UPDATE. Namun bila NIM yang diberikan belum pernah tersimpan, maka method ini akan memberikan query SQL INSERT. Cara seperti ini akan mempermudah user yang memakai DLL ini karena mereka hanya perlu “mempelajari” satu method. Kelemahannya adalah kinerja yang berkurang karena harus selalu melakukan SELECT untuk memeriksa apakah record sudah pernah tersimpan atau belum.
Berikutnya, lakukan modifikasi pada CMahasiswa::HapusMahasiswa()
sehingga kode programnya terlihat seperti berikut ini:
STDMETHODIMP CMahasiswaService::HapusMahasiswa(BSTR nim)
{
// Konversi BSTR menjadi SQLString
char strNIM[11];
size_t len;
wcstombs_s(&len, strNIM, nim, 10);
// Menghapus record berdasarkan NIM
try {
psDelete->setString(1, strNIM);
psDelete->executeUpdate();
} catch (SQLException &e) {
return E_FAIL;
}
return S_OK;
}
Method CMahasiswaService::HapusMahasiswa()
adalah method yang sederhana, hanya menghapus data di tabel berdasarkan parameter NIM yang diberikan.
Berikutnya, cari method CMahasiswaService::CariMahasiswaByNIM()
dan lakukan perubahan sehingga isinya akan terlihat seperti berikut ini:
STDMETHODIMP CMahasiswaService::CariMahasiswaByNIM(BSTR nim, IMahasiswa** ret)
{
// Konversi BSTR menjadi SQLString
char strNIM[11];
size_t len;
wcstombs_s(&len, strNIM, nim, 10);
try {
ResultSet* rs;
// Melakukan query untuk mencari record berdasarkan NIM
psSelectByNIM->setString(1, strNIM);
rs = psSelectByNIM->executeQuery();
if (rs->next()) {
// Record ditemukan. Buat instance class Mahasiswa dan
// inisialisasi nilai, serta kembalikan nilainya.
CComPtr ptrMahasiswa;
ptrMahasiswa.CoCreateInstance(CLSID_Mahasiswa);
IMahasiswa* pMahasiswa = (IMahasiswa*) ptrMahasiswa;
CComBSTR bstrNIM(rs->getString(1)->c_str());
pMahasiswa->put_NIM(bstrNIM);
CComBSTR bstrNama(rs->getString(2)->c_str());
pMahasiswa->put_Nama(bstrNama);
pMahasiswa->put_TahunMasuk(rs->getInt(3));
ptrMahasiswa.CopyTo(ret);
}
delete rs;
} catch (SQLException &e) {
return E_FAIL;
}
return S_OK;
}
Method CMahasiswaService::CariMahasiswaByNIM()
mengembalikan pointer ke IMahasiswa
. Ini berarti, di PHP nanti, kita bisa membuat kode program seperti:
<?php
$com = new COM("Jocki.MahasiswaService");
$com->Inisialisasi("root", "password", "dbLatihan");
// Cara 1
$mhs = $com->CariMahasiswaByNIM("1103030023");
print "Nama [" . $mhs->Nama . "]<br>";
// Atau, Cara 2
print "Nama [" . $com->CariMahasiswaByNIM("1103030023")->Nama . "]<br>";
?>
Programmer PHP (selaku “konsumen“) bisa memakai hasil kembalian dari operasi pencarian berdasarkan NIM secara lebih leluasa (walaupun lebih repot bagi kamu selaku “produsen” ;). Tanpa class Mahasiswa
, maka hasil kembalian dapat berupa array yang lebih tidak nyaman dipakai.
Sekarang, dari semua operasi CRUD, kamu telah mengimplementasikan Create, Update dan Delete. Langkah terakhir adalah membuat implementasi Read. Operasi yang satu ini berbeda dengan yang lainnya, karena saat kita mengembalikan isi tabel, kita tidak hanya mengembalikan satu nilai, melainkan kumpulan nilai. Istilah COM-nya adalah Collection. Kamu membutuhkan sebuah class khusus untuk menampung kumpulan nilai tersebut. Selain itu, kamu membutuhkan sebuah cara untuk mengakses Collection, yang disebut sebagai Enumerator. Beruntungnya, ATL menyediakan class yang dapat langsung kamu pakai. Masih ingat dengan property Item
dan _NewEnum
yang kamu buat sebelumnya? Ini akan dipakai untuk Enumerator. Kamu tidak akan membuat implementasinya secara manual karena kamu akan memakai CComEnumOnSTL
dan IEnumVARIANT
yang disediakan oleh ATL.
Buka file MahasiswaService.h, lalu tambahkan kode program berikut setelah baris using namespace ATL
:
#include <list>
struct _CopyVariantFromAdaptItf {
static HRESULT copy(VARIANT* p1, const CAdapt<CComPtr<IMahasiswa>>* p2) {
HRESULT hr = p2->m_T->QueryInterface(IID_IDispatch, (void**) &p1->pdispVal);
p1->vt = VT_DISPATCH;
return hr;
}
static void init(VARIANT* p) { VariantInit(p); }
static void destroy(VARIANT* p) {VariantClear(p); }
};
typedef CComEnumOnSTL<IEnumVARIANT, &IID_IEnumVARIANT, VARIANT, _CopyVariantFromAdaptItf, std::list<CAdapt<CComPtr<IMahasiswa>>>>
CComEnumVariantOnMahasiswaService;
struct _CopyItfFromAdaptItf {
static HRESULT copy(IMahasiswa** p1, const CAdapt<CComPtr<IMahasiswa>>* p2) {
if (*p1=p2->m_T) return (*p1)->AddRef(), S_OK;
return E_POINTER;
}
static void init(IMahasiswa** p) {}
static void destroy(IMahasiswa** p) {if (*p) (*p)->Release();}
};
typedef ICollectionOnSTLImpl<
IDispatchImpl<IMahasiswaService, &IID_IMahasiswaService>,
std::list<CAdapt<CComPtr<IMahasiswa>>>,
IMahasiswa*,
_CopyItfFromAdaptItf,
CComEnumVariantOnMahasiswaService>
IMahasiswaServiceCollImpl;
Kemudian, cari bagian definisi class CMahasiswaService di bawahnya, dan ubah sehingga terlihat seperti berikut ini:
class ATL_NO_VTABLE CMahasiswaService :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CMahasiswaService, &CLSID_MahasiswaService>,
public IMahasiswaServiceCollImpl
Berikutnya, cari method CMahasiswaService::Refresh()
dan lakukan perubahan sehingga isinya akan terlihat seperti berikut ini:
STDMETHODIMP CMahasiswaService::Refresh(void)
{
try {
ResultSet* rs;
// Melakukan select seluruh baris di tabel
rs = psSelectAll->executeQuery();
while (rs->next()) {
// Membuat pointer ke CMahasiswa
CComPtr ptrMahasiswa;
ptrMahasiswa.CoCreateInstance(CLSID_Mahasiswa);
IMahasiswa* pMahasiswa = (IMahasiswa*) ptrMahasiswa;
CComBSTR bstrNIM(rs->getString(1)->c_str());
CComBSTR bstrNama(rs->getString(2)->c_str());
pMahasiswa->put_NIM(bstrNIM);
pMahasiswa->put_Nama(bstrNama);
pMahasiswa->put_TahunMasuk(rs->getInt(3));
m_coll.push_back(ptrMahasiswa);
}
delete rs;
} catch (SQLException &e) {
return E_FAIL;
}
return S_OK;
}
Ok, sekarang kamu telah selesai! Pilih menu Build, Build Solution untuk membuat output DLL dari proyek kamu.
Kamu bisa membuat sebuah halaman PHP untuk menguji operasi CRUD database, misalnya dengan halaman PHP berikut ini:
<!DOCTYPE html>
<html>
<head>
<title>Latihan COM DLL - Operasi CRUD MySQL</title>
<style type="text/css">
table { border-collapse: collapse; border: 1px solid black;}
td { padding: 10px; }
th { border-bottom: 3px double black; padding: 5px;}
</style>
</head>
<?php
$aksi = $_REQUEST['aksi'];
if ($aksi=='') $aksi='lihat';
$myself = $_SERVER['PHP_SELF'];
$mahasiswaService = new COM("Jocki.MahasiswaService");
$mahasiswaService->Inisialisasi("root", "password", "namadb");
if ($aksi=='tambah') {
?>
<h1>Tambah Mahasiswa</h1>
<form action="<?php print $myself;?>" method="POST">
<fieldset>
<p>
<label for="txtNIM">NIM:</label>
<input type="text" name="txtNIM" size="10" />
</p>
<p>
<label for="txtNama">Nama:</label>
<input type="text" name="txtNama" size="50"/>
</p>
<p>
<label for="txtTahunMasuk">Tahun Masuk:</label>
<input type="text" name="txtTahunMasuk" size="5"/>
</p>
<input type="hidden" name="aksi" value="prosesTambah"/>
<input type="submit" value="Tambah Mahasiswa"/>
</fieldset>
</form>
<?php
} else if ($aksi=='prosesTambah') {
$nim = $_POST['txtNIM'];
$nama = $_POST['txtNama'];
$tahunMasuk = $_POST['txtTahunMasuk'];
$mahasiswaService->SimpanMahasiswa($nim, $nama, $tahunMasuk);
header("Location: $myself");
} else if ($aksi=='hapus') {
$nim = $_GET['txtNIM'];
$mahasiswaService->HapusMahasiswa($nim);
header("Location: $myself");
} else if ($aksi=='update') {
$nim = $_GET['txtNIM'];
$mahasiswa = $mahasiswaService->CariMahasiswaByNIM($nim);
?>
<h1>Update Mahasiswa</h1>
<form action="<?php print $myself;?>" method="POST">
<fieldset>
<p>
NIM: <?php print $nim; ?>
<input type="hidden" name="txtNIM" value="<?php print $nim; ?>" />
</p>
<p>
<label for="txtNama">Nama:</label>
<input type="text" name="txtNama" size="50" value="<?php print $mahasiswa->Nama; ?>"/>
</p>
<p>
<label for="txtTahunMasuk">Tahun Masuk:</label>
<input type="text" name="txtTahunMasuk" size="5" value="<?php print $mahasiswa->TahunMasuk; ?>"/>
</p>
<input type="hidden" name="aksi" value="prosesTambah"/>
<input type="submit" value="Update Mahasiswa"/>
</fieldset>
</form>
<?php
} else if ($aksi=='lihat') {
?>
<h1>List Mahasiswa</h1>
<table>
<thead>
<tr><th>NIM</th><th>Nama</th><th>Tahun Masuk</th><th>Operasi</th></tr>
</thead>
<tbody>
<?php
foreach ($mahasiswaService as $mahasiswa) {
$nim = $mahasiswa->NIM;
print "<tr>" .
"<td>" . $nim . "</td>" .
"<td>" . $mahasiswa->Nama . "</td>" .
"<td>" . $mahasiswa->TahunMasuk . "</td>" .
"<td><a href='$myself?aksi=hapus&txtNIM=$nim'>Hapus</a> - " .
"<a href='$myself?aksi=update&txtNIM=$nim'>Edit</a></td>" .
"</tr>";
}
?>
</tbody>
</table>
<a href="<?php print $myself; ?>?aksi=tambah">Tambah Data Mahasiswa Baru</a>
<?php
}
?>
</html>
File PHP di atas belum mencakup validasi, tetapi cukup untuk menguji apakah operasi CRUD database melalui COM DLL berfungsi dengan baik.
Anda harus log masuk untuk menerbitkan komentar.