Membuat Program Visual C++ Yang Menulis Ke Windows Event Log

Sistem operasi Windows memiliki fasilitas logging yang disebut sebagai Windows Event Log. Pengguna dapat melihat log yang ada dengan memilih Control Panel, Administrative Tools, Event Viewer. Hampir semua aplikasi (terutama atau hanya aplikasi yang dibuat oleh Microsoft ūüėČ ) mencatat aktifitasnya di sini. Dengan adanya mekanisme logging yang terpusat, seharusnya aplikasi tidak perlu menulis ke file tersendiri lagi dan pengguna juga cukup melihat semua catatan aplikasi di Event Viewer.

Pada sistem operasi Linux, logging ditangani oleh syslog. Untuk menulis ke syslog di sebuah program C di Linux, saya dapat memberikan perintah seperti:

#include <syslog.h>
...
openlog(identitas, LOG_NOWAIT, LOG_AUTH);
syslog(prioritas, "%s", pesan);
closelog();
...

Sangat sederhana bukan? Lalu bagaimana dengan Windows? Apa yang harus saya lakukan agar dapat menulis ke Windows Event Log? Pada Windows, logging sedikit lebih sulit karena logging merupakan bagian dari Event Tracing For Windows (ETW). Dengan demikian, aplikasi harus menyediakan provider yang bertugas mengirimkan event. Event Viewer (di Control Panel) dapat dianggap sebagai sebuah consumer yang membaca event dari aplikasi.

Saya akan mulai dengan membuat sebuah proyek Win32 Console Application di Visual Studio 2010. Pada saat membuat proyek, saya menghilangkan tanda centang pada Precompiled header. Saya meletakkan proyek ini pada lokasi C:\latihan\latihan_event_log.

Langkah awal dalam membuat aplikasi yang memakai ETW adalah membuat Instrumentation Manifest. Ini adalah sebuah file XML yang berisi informasi provider yang disediakan oleh aplikasi. Agar lebih mudah, saya akan menggunakan tool berbasis GUI yang disebut ECManGen.exe. Untuk itu, saya membuka Start Menu, memilih All Programs, Microsoft Visual Studio 2010, Visual Studio Tools, dan men-klik Visual Studio Command Prompt. Pada command prompt yang muncul, saya memberikan perintah berikut ini:

C:\> ECManGen.exe

Pada ECManGen, saya men-klik menu Edit, New, Provider untuk membuat sebuah provider baru. Saya kemudian mengisi informasi yang ada seperti yang terlihat pada gambar berikut ini:

Membuat provider baru

Membuat provider baru

Pada bagian decoding file locations, saya perlu memberikan lokasi file EXE dalam bentuk lokasi absolut. Sebagai contoh, saya mengisinya dengan C:\latihan\latihan_event_log\Debug\latihan_event_log.exe. Setelah selesai mengisi informasi yang ada, saya menyimpan perubahan dengan men-klik tombol Save yang ada di sisi kanan layar.

Langkah berikutnya adalah mendefinisikan sebuah channel dengan memilih menu Edit, New, Channel. Karena saya memilih untuk membuat sebuah channel baru, maka hasil log di Event Viewer akan muncul pada sebuah kategori tersendiri. Saya mengisi informasi channel seperti berikut ini:

Membuat channel baru

Membuat channel baru

Setelah itu, saya perlu mendefinisikan sebuah template dengan memilih menu Edit, New, Template. Sebuah template mewakili pesan atau data yang hendak dikirim ke Event Viewer. Saya mengisi informasi template seperti berikut ini:

Membuat template baru

Membuat template baru

Sebagai langkah terakhir, saya akan mendefinisikan sebuah event dengan memilih menu Edit, New, Event. Sebuah provider dapat menghasilkan lebih dari satu event. Pada latihan ini, saya hanya akan membuat sebuah event tunggal yang mewakili pesan kesalahan, seperti yang terlihat pada gambar berikut ini:

Membuat event baru

Membuat event baru

Saya kemudian menyimpan instrumentation manifest dengan memilih menu File, Save dan memilih C:\latihan\latihan_event_log\provider.man sebagai tujuan penyimpanan. Sesungguhnya sebuah instrumentation manifest adalah sebuah file XML sehingga saya dapat mengetik isinya sendiri bila tidak ingin memakai ECManGen.exe. Sebagai contoh, file provider.man yang dihasilkan oleh ECManGen.exe memiliki isi seperti berikut ini:

<?xml version="1.0" encoding="UTF-16"?>
<instrumentationManifest xsi:schemaLocation="http://schemas.microsoft.com/win/2004/08/events eventman.xsd" xmlns="http://schemas.microsoft.com/win/2004/08/events" xmlns:win="http://manifests.microsoft.com/win/2004/08/windows/events" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:trace="http://schemas.microsoft.com/win/2004/08/events/trace">
    <instrumentation>
        <events>
            <provider name="TheSolidSnake-Provider" guid="{2B1D473F-3A5B-49C5-8002-0F54E8FCF6BE}" symbol="THESOLIDSNAKE" resourceFileName="C:\latihan\latihan_event_log\Debug\latihan_event_log.exe" messageFileName="C:\latihan\latihan_event_log\Debug\latihan_event_log.exe">
                <events>
                    <event symbol="Kesalahan" value="1" version="1" channel="AplikasiTheSolidSnake" level="win:Error" template="Pesan" message="$(string.TheSolidSnake-Provider.event.1.message)">
                    </event>
                </events>
                <levels>
                </levels>
                <channels>
                    <channel name="AplikasiTheSolidSnake" chid="AplikasiTheSolidSnake" symbol="APLIKASI_THE_SOLID_SNAKE" type="Operational" enabled="true" message="$(string.TheSolidSnake-Provider.channel.APLIKASI_THE_SOLID_SNAKE.message)">
                    </channel>
                </channels>
                <templates>
                    <template tid="Pesan">
                        <data name="pesan" inType="win:UnicodeString" outType="xs:string">
                        </data>
                    </template>
                </templates>
            </provider>
        </events>
    </instrumentation>
    <localization>
        <resources culture="en-US">
            <stringTable>
                <string id="level.Error" value="Error">
                </string>
                <string id="TheSolidSnake-Provider.event.1.message" value="Terjadi Kesalahan: %s">
                </string>
                <string id="TheSolidSnake-Provider.channel.APLIKASI_THE_SOLID_SNAKE.message" value="Informasi mengenai aplikasi TheSolidSnake.">
                </string>
            </stringTable>
        </resources>
    </localization>
</instrumentationManifest>

Langkah berikutnya adalah memanggil Message Compiler (MC.exe) untuk menghasilkan kode program dan file resources berdasarkan isi provider.man. Untuk itu, saya memanggil MC.exe dengan perintah seperti berikut ini:

C:\latihan\latihan_event_log> MC.exe -um provider.man

Perintah di atas akan menghasilkan file berupa provider.h, provider.rc, providerTEMP.BIN, dan MSG00001.bin pada lokasi direktori proyek. Saya perlu menambahkan file ini agar dikenali oleh Visual Studio. Untuk menyertakan referensi ke provider.h, saya dapat men-klik kanan pada Header Files dan memilih menu Add, Existing Item. Pada dialog yang muncul, saya memilih file provider.h dan men-klik tombol Add. Saya juga melakukan hal yang sama pada Resource Files untuk menyertakan file provider.rc.

Saya kini dapat membuat men-install instrumentation manifest dengan menggunakan Wevtutil.exe. Tapi sebelumnya, saya perlu membuka Command Prompt sebagai Administrator terlebih dahulu sebelum mengetikkan perintah berikut ini:

C:\latihan\latihan_event_log> wevtutil.exe im provider.man
**** Warning: Publisher TheSolidSnake-Provider resources are not accessible.

Pesan peringatan yang muncul boleh diabaikan karena file executable memang belum dibuat. Hasil dari perintah tersebut adalah saya bisa menemukan channel baru di Event Viewer seperti yang terlihat pada gambar berikut ini:

Channel baru di Event Viewer

Channel baru di Event Viewer

Sekarang, saya siap untuk membuat kode program. Sebagai contoh, saya membuat kode program sederhana seperti berikut ini:

#include <Windows.h>
#include "stdafx.h"
#include "provider.h"

int _tmain(int argc, _TCHAR* argv[])
{
    ULONG status;
    status = EventRegisterTheSolidSnake_Provider();
    if (status == ERROR_SUCCESS) {
        wprintf(L"Berhasil melakukan registrasi provider.\n");
    }
    status = EventWriteKesalahan(L"Ini kesalahan latihan.");
    if (status == ERROR_SUCCESS) {
        wprintf(L"Berhasil mengirim event.\n");
    }
    status = EventUnregisterTheSolidSnake_Provider();
    if (status == ERROR_SUCCESS) {
        wprintf(L"Berhasil membatalkan registrasi provider.\n");
    }
    return 0;
}

Header provider.h yang dihasilkan oleh MC.exe mengandung macro seperti EventRegisterTheSolidSnake_Provider, EventWriteKesalahan dan EventUnregisterTheSolidSnake_Provider yang dapat saya pakai di kode program. Pada dasarnya macro yang ada merupakan versi singkat yang memanggil EventRegister(), EventUnregister(), dan EventWrite().

Setelah menjalankan aplikasi, saya dapat menemukan sebuah log baru di channel yang saya buat di Event Viewer seperti yang terlihat pada gambar berikut ini:

Log yang dihasilkan aplikasi

Log yang dihasilkan aplikasi

Bagaimana bila saya tidak ingin membuat sebuah channel baru? Misalnya, bagaimana bila saya ingin log muncul di bagian Application di Windows Log? Saya perlu mengubah definisi event menjadi seperti berikut ini:

Memakai channel yang sudah ada

Memakai channel yang sudah ada

Setelah menyimpan insrumentation manifest, saya perlu kembali memberikan perintah MC.exe untuk memperbaharui file resources. Bila saya menjalankan program, kali ini log akan tercatat di Windows Log, Application seperti yang terlihat pada gambar berikut ini:

Log dari aplikasi kini muncul di Windows Log

Log dari aplikasi kini muncul di Windows Log

Iklan

Membaca Change Journal File Di NTFS Dengan Visual C++

Kode program untuk artikel ini dapat dijumpai di https://gist.github.com/JockiHendry/9263890.

Pada volume yang memakai file system NTFS, Windows 7 akan mencatat setiap aktifitas perubahan pada file dan directory. Dengan demikian, pengguna bisa tahu apa saja file dimodifikasi di volume/partisi tersebut. Windows akan menyimpan informasi ini di sebuah file bernama \$Extend:$J (file bernama \$Extend di alternate data stream bernama $J). File ini tidak akan penuh karena isinya akan terus ditimpa. Lalu bagaimana cara melihat file ini? Ini adalah salah satu file internal yang dipakai oleh NTFS dan tidak dapat dilihat oleh pengguna (sama seperti file lain seperti $MFT, $MFTMirr, $Boot dan sebagainya). Cara yang paling akurat adalah dengan memeriksa isinya (membaca dari sector). Sebagai alternatifnya, Windows juga menyediakan control code FSCTL_READ_USN_JOURNAL di API DeviceIOControl(). Cara ini lebih mudah karena saya tidak perlu membaca sector secara langsung. Tapi cara ini tidak selalu efektif dalam setiap kondisi karena ia hanya bisa diterapkan di partisi yang sedang di-mount (memiliki drive letter seperti C atau D).

Pada artikel ini saya akan memakai Visual C++ karena API Windows ditulis dan dipublikasikan dengan bahasa C. Memakai bahasa tingkat tinggi seperti .NET dan Java hanya akan menambah repotnya mengurus interoperability. Mahasiswa yang baru belajar sering kali menganggap C dan C++ sudah ‘punah’ digantikan bahasa tingkat tinggi (terutama bila melihat dari kronologi perkembangan bahasa pemograman). Tapi faktanya tidak demikian, developer tetap butuh bahasa yang dekat dengan mesin agar bisa mengendalikan mesin. Mesin komputer tidak bisa membaca HTML dan tidak mengerti OOP; mesin komputer hanya mengerti bit dan byte. Bahasa C tetap merupakan bahasa pilihan saya dalam pemograman low-level hingga saat ini karena ia memang lebih dekat dengan mesin (misalnya memiliki pointer untuk membaca alamat memori secara langsung).

Saya akan mulai dengan membuat sebuah proyek Win32 Console Application di Visual Studio. Karena operasi yang dilakukan oleh program ini membutuhkan hak akses Administrator, maka perlu ada dialog UAC untuk menjalankan program sebagai Administrator (bila belum). Untuk itu saya men-klik kanan nama proyek, memilih Properties. Pada dialog yang muncul, saya memilih Configuration Properties, Linker, Manifest File. Setelah itu, saya mengubah nilai UAC Execution Level menjadi requireAdministrator (/level=’requireAdministrator’).

Langkah pertama yang saya lakukan adalah membuka volume NTFS yang akan dibaca berdasarkan kode hurufnya dengan memanggil Win32 API CreateFile(), seperti yang terlihat pada kode program berikut ini:

#include "stdafx.h"
#include <Windows.h>
#include <WinIoCtl.h>

int _tmain(int argc, _TCHAR* argv[])
{

    // Membaca volume C:
    HANDLE h = CreateFile(L"\.c:", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 
        NULL, OPEN_EXISTING, 0, NULL);
    if (h == INVALID_HANDLE_VALUE) {
        printf("Gagal membaca drive C. Kode Kesalahan: %dn", GetLastError());
        return;
    }

    // Lanjut disini...

    return 0;
}

Setelah itu, saya akan memanggil DeviceIOControl() dengan code berupa FSCTL_QUERY_USN_JOURNAL untuk mendapatkan nilai USN pertama. Saya membutuhkan nilai ini sebagai parameter untuk FSCTL_READ_USN_JOURNAL nanti. Untuk itu, saya menambahkan kode program seperti berikut ini:

...
// Membaca informasi Change Journal dengan FSCTL_QUERY_USN_JOURNAL
USN_JOURNAL_DATA journal;
DWORD jumlahByte;
if (!DeviceIoControl(h, FSCTL_QUERY_USN_JOURNAL, NULL, 0, &journal, sizeof(journal), &jumlahByte, NULL)) {
    printf("Gagal membaca Change Journal.  Kode kesalahan: %dn", GetLastError());
    return -1;
}
printf("USN_JOURNAL_DATA.UsnJournalID    = %#020llxn", journal.UsnJournalID);
printf("USN_JOURNAL_DATA.FirstUsn        = %#020llxn", journal.FirstUsn);
printf("USN_JOURNAL_DATA.NextUsn         = %#020llxn", journal.NextUsn);
printf("USN_JOURNAL_DATA.LowestValidUsn  = %#020llxn", journal.LowestValidUsn);
printf("USN_JOURNAL_DATA.MaxUsn          = %#020llxn", journal.MaxUsn);
printf("USN_JOURNAL_DATA.MaximumSize     = %#020llxn", journal.MaximumSize);
printf("USN_JOURNAL_DATA.AllocationDelta = %#020llxn", journal.AllocationDelta);
...

Pada kode program diatas, saya memakai %llx pada printf() untuk mencetak versi heksadesimal dari nilai __int64 yang juga dikenal sebagai long long (tipe USN adalah alias dari __int64). Bila saya menjalankan kode program di atas, saya akan memperoleh hasil yang sama dengan perintah fsutil usn readdata c:.

Nilai dari journal.FirstUsn adalah record pertama yang dapat dibaca di Change Journal. Oleh sebab itu, saya akan mulai membaca mulai dari record tersebut dengan menggunakan FSCTL_READ_USN_JOURNAL seperti yang terlihat pada kode program berikut ini:

...
// Membaca isi Change Journal
READ_USN_JOURNAL_DATA cariUSN;
CHAR hasil[4096];

cariUSN.StartUsn = journal.FirstUsn;
cariUSN.UsnJournalID = journal.UsnJournalID;
memset(hasil, 0, 4096);
if (!DeviceIoControl(h, FSCTL_READ_USN_JOURNAL, &cariUSN, sizeof(cariUSN), &hasil, 4096, &jumlahByte, NULL)) {
    printf("Gagal membaca record di Change Journal.  Kode kesalahan: %dn", GetLastError());
    return -1;
}

printf("nDaftar Record di Change Journal:nn");

PUSN_RECORD record = (PUSN_RECORD)(((PUCHAR)hasil) + sizeof(USN));
printf("USN        : %#020llxn", record->Usn);
printf("Nama File  : %Sn", record->FileName);
printf("Reason     : %#lxnn", record->Reason);

record = (PUSN_RECORD)(((PCHAR) record) + record->RecordLength);
printf("USN        : %#020llxn", record->Usn); 
printf("Nama File  : %Sn", record->FileName);
printf("Reason     : %#lxnn", record->Reason);
...

Pada kode program di atas, saya menampilkan dua record pertama. Hasil kembalian dari FSCTL_READ_USN_JOURNAL ditampung dalam variabel hasil. Delapan (8) byte pertama dari hasil adalah nilai USN berikutnya setelah USN yang terakhir kali dikembalikan. Ukuran hasil hanya 4096 bytes sehingga ada kemungkinan besar tidak seluruh record tertampung sehingga saya perlu melakukan perulangan dengan kembali memanggil FSCTL_READ_USN_JOURNAL. Selain itu, saya juga harus memperhatikan nilai variabel jumlahByte berisi jumlah byte yang terpakai dari 4096 bytes yang saya sediakan.

Kode program di atas hanya menampilkan dua record saja. Ini tidak begitu berguna! Oleh sebab itu, saya akan menghapus dan menggantinya menjadi sebuah perulangan yang menampilkan seluruh record di Change Journal seperti yang terlihat berikut ini:

...
// Membaca isi Change Journal
printf("nDaftar Record di Change Journal:nn");

READ_USN_JOURNAL_DATA cariUSN;
PUSN_RECORD record;
CHAR hasil[4096];
USN nextUSN;

cariUSN.StartUsn = journal.FirstUsn;
cariUSN.ReasonMask = 0xFFFFFFFF;
cariUSN.ReturnOnlyOnClose = 0;  
cariUSN.BytesToWaitFor = 0;
cariUSN.UsnJournalID = journal.UsnJournalID;

while (1) {
    memset(hasil, 0, 4096);
    if (!DeviceIoControl(h, FSCTL_READ_USN_JOURNAL, &cariUSN, sizeof(cariUSN), &hasil, 4096, &jumlahByte, NULL)) {
        printf("Gagal membaca record di Change Journal.  Kode kesalahan: %dn", GetLastError());
        return -1;
    }           

    record = (PUSN_RECORD)(((PUCHAR)hasil) + sizeof(USN));
    jumlahByte -= sizeof(USN);      

    while (jumlahByte > 0) {
        printf("USN        : %#020llxn", record->Usn);
        printf("Nama File  : %Sn", record->FileName);
        printf("Reason     : %#lxnn", record->Reason);

        jumlahByte -= record->RecordLength;
        record = (PUSN_RECORD)(((PCHAR) record) + record->RecordLength);
    }

    nextUSN = *(USN*) &hasil;       
    if (nextUSN==cariUSN.StartUsn) break;       
    cariUSN.StartUsn = nextUSN;
}
...

Pada kode program di atas, saya selalu memakai sebuah variabel yang sama untuk menampung entry yang dibaca yaitu hasil (sebuah array 4096 bytes). Setiap kali akan membaca di perulangan berikutnya, saya mengosongkan nilai hasil dengan memset(). Dengan demikian, saya dapat membaca Change Journal yang memiliki ukuran besar (dalam satuan GB) tanpa harus takut kehabisan memori. Untuk menentukan kapan harus berhenti, saya memeriksa apakah USN berikutnya yang dikembalikan oleh FSCTL_READ_USN_JOURNAL sama dengan nilai cariUSN.StartUsn yang saya berikan diawal. Bila sama, ini berarti tidak ada lagi yang dapat dibaca dan FSCTL_READ_USN_JOURNAL tidak boleh dipanggil lagi.

Bila saya menjalankan program, saya akan memperoleh banyak output. Alangkah baiknya bila program berhenti setiap kali mencapai batas layar. Oleh sebab itu, saya kemudian menambahkan kode program seperti berikut ini:

...
int _tmain(int argc, _TCHAR* argv[])
{
    // Mengatur console
    CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo;
    HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTitle(L"Change Journal Viewer by TheSolidSnake");   
    ...
    while (1) {
        ...

        while (jumlahByte > 0) {

            ...

            GetConsoleScreenBufferInfo(hStdOut, &screenBufferInfo);                 
            if (screenBufferInfo.dwCursorPosition.Y + 4 > screenBufferInfo.srWindow.Bottom) {                                
                printf("Tekan sembarang tombol untuk melanjutkan...");
                getchar();
                system("cls");
            }
        }

        ...
    }
    return 0;
}
...

Sekarang, hasil dari program sudah mulai bisa dimengerti. Tapi informasi yang ditampilkan rasanya terlalu banyak. Oleh sebab itu, saya akan membatasi hanya menampilkan Reason yang berupa USN_REASON_FILE_CREATE, USN_REASON_FILE_DELETE dan USN_REASON_RENAME_NEW_NAME. Selain itu, saya akan menampilkan Reason yang saat ini dalam bentuk bilangan heksadesimal menjadi tulisan yang lebih mudah dimengerti. Untuk itu, saya melakukan perubahan kode program seperti berikut ini:

...
cariUSN.ReasonMask = USN_REASON_FILE_CREATE | USN_REASON_FILE_DELETE | USN_REASON_RENAME_NEW_NAME;
...
printf("USN        : %#020llxn", record->Usn);
printf("Nama File  : %Sn", record->FileName);
printf("Reason     : ");
if (record->Reason & USN_REASON_CLOSE) {
    printf(" CLOSE");
}
if (record->Reason & USN_REASON_FILE_CREATE) {
    printf(" FILE_CREATE");
}
if (record->Reason & USN_REASON_FILE_DELETE) {
    printf(" FILE_DELETE");
}
if (record->Reason & USN_REASON_RENAME_NEW_NAME) {
    printf(" RENAME_NEW_NAME");
}
printf("nn");
...

Sekarang, bila saya menjalankan program, saya akan memperoleh hasil berupa riwayat file yang dibuat atau dihapus pada volume C: seperti yang terlihat pada gambar berikut ini:

Tampilan program saat dijalankan

Tampilan program saat dijalankan

Pengenalan C++ Modern

Salah satu matakuliah yang sering diajarkan pada semester-semester awal perkuliahan adalah pemograman C++.   Ini adalah tradisi yang dibawa sejak kurikulum tradisional hingga sekarang, dimana saat itu, bahasa yang lebih gampang seperti Java, Python, Ruby atau Groovy belum lahir.   Mengapa harus mengajarkan bahasa yang lebih sulit terlebih dahulu?   Mengapa tidak mulai mengubah tradisi lama dan menyesuaikannya dengan perkembangan zaman?   Sebagai perbandingan, banyak universitas di luar yang sudah mengadopsi Java sebagai bahasa yang diajarkan pertama kali.   Yang lebih memprihatinkan lagi adalah bahasa C++ yang diajarkan di beberapa kampus setempat tidak pernah di-update; saya masih menjumpai mereka yang memakai compiler Borland Turbo C++ yang jalan di DOS (kini sudah tidak diproduksi dan digantikan oleh seri produk C++Builder).

C++ adalah bahasa yang dikembangkan oleh Bjarne Stroustrup sejak tahun 1979.¬†¬† Saat itu, tujuan utamanya adalah menambahkan fasilitas OOP pada bahasa C (C++ sebagai C With Classes).¬†¬† Tapi Bjarne Stroustrup tidak diam saja hingga sekarang.¬†¬† Ia dan anggota komite ISO lainnya terus memperbaharui standar bahasa C++ sehingga bahasa tersebut dapat bersaing dengan bahasa modern lainnya.¬†¬† Standar terakhir adalah C++11 yang diterbitkan pada tahun 2011.¬†¬† C++11 merupakan bahasa C++ yang sangat berbeda dari bahasa C++ versi sebelumnya sehingga ia kerap dikenal sebagai “Modern C++”.¬†¬† Saat ini saya masih belum menjumpai universitas setempat yang mengajarkan Modern C++.

Microsoft Visual C++ di Visual Studio 2010 sudah mendukung C++11, walaupun masih terbatas.   Fitur C++11 terus ditingkatkan pada Visual Studo 2012 dan Visual Studio 2013.   Daftar fitur yang sudah dan belum di-implementasi-kan di Visual C++ dapat ditemukan di http://msdn.microsoft.com/en-us/library/vstudio/hh567368.aspx.   Pada kesempatan ini, saya akan mencoba beberapa fitur C++11 di Visual C++ 2010.

Salah penambahan pada C++11 adalah rvalue reference.   Reference yang biasa seperti X& dapat menerima lvalue sementara itu const X& dapat menerima baik lvalue maupun rvalue.   C++11 menambahkan referensi yang hanya dapat menerima rvalue yang bentuknya seperti X&&.   Agar lebih jelas, perhatikan kode program berikut ini:

int a = 10; // pada ekspresi ini, a adalah contoh lvalue dan 10 adalah rvalue

int& t1 = a;  // hanya boleh mereferensi lvalue
int& t2 = 10; // !!ERROR!! karena tidak boleh mereferensi rvalue

const int& t3 = a; // boleh mereferensi lvalue
const int& t4 = 10; // boleh mereferensi rvalue

int&& t5 = a; // !!ERROR!! karena tidak boleh merefensi lvalue
int&& t6 = 10; // hanya boleh mereferensi rvalue

Dengan rvalue reference, seseorang dapat membuat move constructor atau move assignment dimana programmer kini dapat mengakses objek sementara.   Hal ini berguna untuk mengurangi duplikasi objek sehingga dapat meningkatkan kinerja pada objek yang menghabiskan banyak memori seperti vector.

Fitur lain di C++11 yang menarik adalah keyword auto dimana tipe data dapat ditentukan berdasarkan ekspresi, seperti pada contoh berikut ini:

...
#include <typeinfo>
...
auto x = 10;
auto y = "The Solid Snake";
auto z = 1.234;
printf("Tipe data x adalah %s\n", typeid(x).name());
printf("Tipe data y adalah %s\n", typeid(y).name());
printf("Tipe data z adalah %s\n", typeid(z).name());

// output:
// Tipe data x adalah int
// Tipe data y adalah char const *
// Tipe data z adalah double

Saya dapat menggunakan decltype (declared type) untuk memperoleh tipe data dari sebuah ekspresi di C++11, seperti yang terlihat pada contoh berikut ini:

auto x = 10;
decltype(x) y;
decltype(10*20.0) z;
printf("Tipe data y adalah %s\n", typeid(y).name());
printf("Tipe data z adalah %s\n", typeid(z).name());

// output:
// Tipe data x adalah int
// Tipe data y adalah char const *
// Tipe data z adalah double

C++11 juga memperkenalkan apa yang disebut sebagai lambda expression.   Ini adalah sebuah syntax yang mirip seperti anonymous function.   Contohnya terlihat pada kode program berikut ini:

template<typename Fn>
int proses(int angka1, int angka2, Fn operasi) {
	return operasi(angka1, angka2);
}

...

int hasiTambah = proses(10, 20, [](int a, int b) { return a + b; });
int hasilKali = proses(10, 20, [](int a, int b) { return a * b; });

Tambahan lain adalah adanya sebuah nilai untuk mewakili null pointer, yaitu nullptr.   Sebelumnya, sebuah null pointer selalu diwakili dengan angka 0.   Pada C++11, baik nullptr maupun angka 0 tetap mewakili null pointer seperti yang terlihat pada kode program berikut ini:

char* a = nullptr;
printf("a = %s\n", a);
char* b = 0;
printf("b = %s\n", b);

// output:
// a = (null)
// b = (null)

Sayang sekali fitur range-for loop untuk melakukan looping terhadap range masih belum di-implementasi-kan di Visual Studio 2010; fitur tersebut baru ada di Visual Studio 2012.   Contoh dari range-for loop adalah for (int& x: array) statement;.   Hampir semua bahasa modern sudah memiliki fasilitas serupa.

Tipe data baru yang diperkenalkan di C++11 adalah tipe data long long.   Penamaan ini dipakai untuk mewakili tipe data angka 64-bit. Contoh penggunaannya terlihat pada kode program berikut ini:

long x = 2147483648L;
long long y = 2147483648LL;
printf("%ld\n%lld\n", x, y);

// output:
// -2147483648
// 2147483648

Batas atas angka signed integer 32-bit adalah 2.147.483.647.   Tipe data long tidak dapat menampung lebih banyak lagi sehingga nilainya berputar kembali ke awal (menjadi negatif); sementara tipe data long long tidak mengalami masalah menampung angka ini (batas atas angka 64-bit adalah 9.223.372.036.854.775.807).

COM DLL Akses MySQL ‚Äď Bagian 4: Membuat Operasi CRUD Untuk Tabel Mahasiswa

Tulisan ini merupakan bagian dari panduan membuat COM DLL (ATL) di Visual Studio 2010 yang mengakses MySQL atas request Albert Antonius:

Pada artikel sebelumnya, kamu sudah membuat operasi insert ke tabel melalui Connector/C++ di COM ATL. ¬†Kali ini, kamu akan membuat sebuah proyek baru yang menyediakan operasi CRUD (Create, Read, Update, Delete) untuk tabel Mahasiswa tersebut. ¬†Kamu juga akan memakai cara yang lebih OOP dibandingkan dengan cara kemarin. ¬†“Apa bedanya? Yang penting ‘kan sama2 bisa jalan?” ¬†tanyamu. ¬† Meningkatkan kualitas kode program, mempermudah perawatan dan perubahan, memudahkan seseorang memahami kode program.

Seperti apa rancangan program yang akan kamu buat?  Kata-kata akan sulit menjelaskannya, jadi lebih baik kamu melihat Component Diagram-nya sendiri:

Component Diagram

Component Diagram

Pada diagram di atas, terlihat bahwa DLL kita mengandung dua class, masing-masing dengan ProgID Jocki.Mahasiswa dan Jocki.MahasiswaService. ¬†PHP akan berinteraksi dengan Jocki.MahasiswaService melalui interface IDispatch. ¬† “Lalu buat apa Jocki.MahasiswaService menyediakan bulat-bulat (baca: provided interface) bernama IMahasiswaService yang tidak pernah dipakai?” kamu pasti bertanya demikian. ¬†Pada komunikasi yang ideal, kamu akan memakai interface ini. ¬†Sayangnya, bila kamu ingin memakai COM di bahasa seperti VB & PHP, maka kamu harus menyediakan IDispatch, karena itulah satu-satunya harapan mereka. ¬†Bila misalnya, suatu saat nanti, ¬†kamu membuat client di Visual C++, maka IMahasiswaService ini akan terpakai. ¬†Kamu tidak perlu khawatir takut kerepotan karena selama ini Visual C++ ATL telah membuatkan dua interface ini secara otomatis bagi kita.

Kembali ke diagram di atas, class Jocki.MahasiswaService akan memakai class lain yaitu Jocki.Mahasiswa. ¬†Walaupun class ini menyediakan IDispatch, pengguna tidak seharusnya membuat class ini langsung dari PHP, karena tujuan utama class ini adalah dipakai oleh Jocki.MahasiswaService. ¬†“Kenapa harus repot-repot membuat dua class? ¬†Bukankah selama ini semua contoh selalu hanya satu class saja?” ¬†demikian pikirmu. ¬†Ini berkaitan dengan bagaimana mengembalikan isi tabel ke klien PHP. ¬† Saat PHP memanggil getAllMahasiswa (ini hanya contoh!), kamu harus mengembalikan daftar list yang berisi … ? ¬†array? Ok, tapi pengguna PHP harus ingat urutan index di array, misalnya index 0 adalah NIM, index 1 adalah nama, dan sebagainya. ¬† Apa tidak ada cara lain yang lebih rapi sedikit? ¬†Yup, itu sebabnya kita mengembalikan class Jocki.Mahasiswa.

Sekarang kamu akan merancang secara lebih detail lagi.  Coba lihat class diagram berikut ini:

Class Diagram MahasiswaService

Class Diagram MahasiswaService

CMahasiswaService adalah class yang mengimplementasikan IMahasiswaService.   Hierarki ini secara otomatis sudah dibuat oleh Visual C++ pada saat kamu wizard new ATL Simple Object.

Method Inisialisasi() akan dipakai untuk melakukan koneksi database.  Pengguna harus menyertakan user database, password, serta nama database yang dipakai saat memanggil method ini.   Bila method lain dipanggil sebelum method ini, maka method lain akan mengembalikan nilai kesalahan.  Pada perancangan yang lebih baik, kamu bisa menyediakan nilai default untuk user, password, dan nama db.  Lalu setiap kali method lain dipanggil dan ternyata koneksi belum dibuat, maka method Inisialiasi() akan dikerjakan dengan nilai default tersebut.

Selain melakukan koneksi database, method Inisialisasi juga akan menyiapkan PreparedStatement yang dibutuhkan.  PreparedStatement adalah class dari Connector/CPP untuk men-cache statement SQL sehingga pemanggilan query yang sama akan lebih cepat nantinya.  Selain itu, PreparedStatement akan mencegah terjadinya SQL Injection.  Pada diagram di atas, saya membuat beberapa PreparedStatement seperti psInsert, psUpdate, psDelete, psSelectByNIM, dan psSelectAll.

Setelah melakukan inisialisasi PreparedStatement, method Inisialisasi() juga akan melakukan query untuk mendapatkan seluruh isi tabel Mahasiswa melalui method Refresh() guna dipakai oleh _NewEnum() dan Item() nantinya.    Pada kasus yang lebih realistis dimana isi tabel mahasiswa sangat banyak sekali, kamu seharusnya hanya perlu men-select hanya bagian tertentu saja pada satu waktu tertentu, tidak semuanya sekaligus.

Setelah select seluruh data dilakukan pertama kali saat method Inisialisasi() dipanggil, mungkin saja user akan melakukan perubahan, seperti setelah pemanggilan HapusMahasiswa() atau SimpanMahasiswa().  Hal ini menyebabkan hasil query pertama kali menjadi tidak valid lagi.  Oleh sebab itu, user wajib memanggil method Refresh() untuk memperbaharui query.  Pada design yang lebih realistis, kamu perlu melakukan caching dan melakuan refresh secara otomatis sehingga user tidak perlu repot.

Method SimpanMahasiswa() akan dipakai untuk insert dan update.  Bila nim mahasiswa belum ada di tabel, maka program harus menambah (insert) record baru.  Tetapi bila nim sudah ada di tabel, maka program tinggal mengubah (update) record yang sudah ada.

Method HapusMahasiswa() dipakai untuk menghapus record berdasarkan nim.

“Lalu apa itu _NewEnum()? ¬†Sungguh nama method yang aneh,” kamu bertanya dalam hati. ¬†Ini adalah method spesial yang memungkinkan kamu memakai class kamu di PHP dengan syntax seperti berikut ini:

<?php
  $com = new COM("Jocki.MahasiswaService");
  foreach ($com as $i) {
     ...
  }
?>

“Pasti Item() juga pasti ada kaitannya?” tanyamu. ¬†Iya, benar. ¬†Item()memungkinkan kode program di PHP seperti berikut ini:

<?php
  $com = new COM("Jocki.MahasiswaService");
  $data1 = $com[1];
  $data2 = $com[2];
  $data3 = $com[3];
?>

Pada contoh di atas, kamu dapat melakukan perulangan setiap record yang ada dengan foreach di PHP berkat _NewEnum() dan kamu bisa mengakses record sebagai array berkat Item().   O ya, kamu tidak perlu membuat kode proram untuk kedua-duanya, karena kamu akan memakai class bawaan ATL yang bernama CComEnumOnSTL.

Berikutnya, seperti apa isi Jocki.Mahasiswa?  Coba perhatikan class diagram berikut ini:

Class Diagram Mahasiswa

Class Diagram Mahasiswa

Class CMahasiswa hanyalah class sederhana yang berisi property sehingga program PHP dapat mengakses setiap record di tabel mahasiswa seperti berikut ini:

<?php
  $com = new COM("Jocki.MahasiswaService");
  print "<h1>Daftar Mahasiswa</h1>";
  for($com as $mhs) {
    // tanpa class Mahasiswa, misalnya dengan array, akan menjadi: 
    // $mhs[0] untuk NIM, $mhs[1] untuk Nama, dsb.
    print "NIM: " . $mhs->NIM . "<br>";
    print "Nama: " . $mhs->Nama . "<br>";
    print "Tahun Masuk: " . $mhs->TahunMasuk . "<br>";
    print "<hr>";
  }
?>

Ok, setelah melihat dan memahami perancangan COM latihan kita, pada artikel berikutnya, kamu akan  membuat implementasinya.

COM DLL Akses MySQL ‚Äď Bagian 5: Implementasi Kode Program

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.

COM DLL Akses MySQL ‚Äď Bagian 3: Operasi Database Insert

Tulisan ini merupakan bagian dari panduan membuat COM DLL (ATL) di Visual Studio 2010 yang mengakses MySQL atas request Albert Antonius:

Mari mulai dengan membuat sebuah ATL Object. ¬†Caranya adalah dengan membuka Class View. ¬†Bila tab Class View tidak muncul, pilih menu View, Class View. ¬†Klik kanan pada LatihanCOM, dan pilih Add, Class… Pada dialog yang muncul, pilih ATL Simple Object kemudian klik tombol Add. Isi dialog ATL Simple Object Wizard dengan memberi nama Mahasiswa pada Short name serta Latihan.Mahasiswa pada ProgID seperti yang terlihat di gambar berikut ini:

Membuat ATL Object Baru

Membuat ATL Object Baru

Klik tombol Finish. Bila kamu masih bingung dengan langkah ini, coba baca kembali artikel pengenalan di sini.

Sekarang kita akan menambahkan property DBUser untuk menampung user name yang dipakai untuk akses database nantinya. Masih di Class View, klik kanan pada interface IMahasiswa, pilih Add, Add Property… Isi Property Type dengan BSTR dan isi Property name dengan DBUser seperti yang terlihat pada gambar berikut:

Membuat Property DBUser

Membuat Property DBUser

Klik tombol Finish.

Lakukan hal yang sama untuk menambahkan property DBPassword yang menampung password untuk akses database MySQL, seperti yang terlihat pada gambar berikut:

Membuat Property DBPassword

Membuat Property DBPassword

Klik tombol Finish.

Kembali lakukan hal yang sama untuk menambahkan property DBNama yang menampung nama database MySQL yang akan dipakai, seperti yang terlihat pada gambar berikut:

Membuat Property DBNama

Membuat Property DBNama

Klik tombol Finish.

Apa itu BSTR? Tebakanmu benar, BSTR adalah tipe data String versi COM. Ingat bahwa COM adalah teknologi yang tidak terikat oleh bahasa. Padahal, hampir setiap bahasa memiliki tipe data String tersendiri, misalnya di bahasa C memakai char*, bahasa C++ memakai class string, bahasa Java memakai class java.lang.String, dan sebagainya.

Sekarang kita akan membuat deklarasi method, yaitu method Inisialisasi. Method ini akan melakukan pemeriksaan database dan membuat tabel yang dibutuhkan. Caranya adalah dengan klik kanan pada interface IMahasiswa, pilih Add, Add Methods… Pada dialog yang muncul, isi Method name dengan Inisialisasi, seperti yang terlihat pada gambar berikut ini:

Membuat Method Inisialisasi

Membuat Method Inisialisasi

Klik tombol Finish.

Lalu buat method lain, yaitu method TambahMahasiswa. Caranya adalah dengan klik kanan pada interface IMahasiswa, lalu pilih Add, Add Method… Pada dialog yang muncul, isi Method name dengan TambahMahasiswa. Berikan tiga parameter [in] berupa BSTR nim, BSTR nama, dan int tahunMasuk seperti yang terlihat pada gambar berikut ini:

Membuat Method TambahMahasiswa

Membuat Method TambahMahasiswa

Klik tombol Finish.

Bila kamu melakukan langkah-langkah yang ada dengan benar, di file LatihanCOM.idl, kamu akan menemukan bagian berikut ini:

interface IMahasiswa : IDispatch{
	[propget, id(1)] HRESULT DBUser([out, retval] BSTR* pVal);
	[propput, id(1)] HRESULT DBUser([in] BSTR newVal);
	[propget, id(2)] HRESULT DBPassword([out, retval] BSTR* pVal);
	[propput, id(2)] HRESULT DBPassword([in] BSTR newVal);
	[propget, id(3)] HRESULT DBNama([out, retval] BSTR* pVal);
	[propput, id(3)] HRESULT DBNama([in] BSTR newVal);
	[id(4)] HRESULT TambahMahasiswa([in] BSTR nim, [in] BSTR nama, [in] int tahunMasuk);
	[id(5)] HRESULT Inisialisasi(void);
};

Seperti yang kamu ketahui, interface hanya sebuah kontrak tanpa isi. Interface adalah sebuah janji tanpi aksi. Oleh sebab itu, kamu perlu membuat implementasi dari interface IMahasiswa.

Tapi sebelumnya, masih ingat kamu membuat tiga property, yaitu DBUser, DBPassword, dan DBNama? Kamu perlu membuat tiga variabel untuk menampung masing-masing nilai tersebut. Kamu bisa mengubah definisi class secara langsung di file Mahasiswa.h, tetapi biar gampang, kamu dapat menambahkan variabel melalui wizard. Pastikan kamu masih berada di Class View. Klik kanan pada class CMahasiswa, pilih Add, Add Variable… Pada Access, plih private. Pada Variable type, ketik char[100]. Pada Variable name, isi dengan m_dbUser, sehingga dialog akan terlihat seperti:

Membuat private member variable

Membuat private member variable

Klik tombol Finish.

Lakukan hal yang sama untuk membuat variabel m_dbPassword dan m_dbNama. Kedua variabel tersebut juga sama-sama bertipe char[100]. Yup, kita membatasi hingga maksimal 100 karakter.

Setelah interface dan variabel selesai dibuat, pertanyaannya adalah dimana kamu meletakkan implementasi kode program?  Buka Solution Explorer (bila kamu tersesat, klik menu View, Solution Explorer). Kemudian double click pada file Mahasiswa.cpp. Disini adalah tempat kamu meletakkan isi dari method untuk class CMahasiswa.

Kita akan mulai dengan get/put untuk property yang ada. Ganti method yang bersangkutan sehingga isinya terlihat seperti berikut ini:

size_t len;

STDMETHODIMP CMahasiswa::get_DBUser(BSTR* pVal)
{
	CComBSTR bstr(m_dbUser);	
	*pVal = bstr.Detach();
	return S_OK;
}

STDMETHODIMP CMahasiswa::put_DBUser(BSTR newVal)
{	
	wcstombs_s(&len, m_dbUser, newVal, 99);	
	return S_OK;
}

STDMETHODIMP CMahasiswa::get_DBPassword(BSTR* pVal)
{
	CComBSTR bstr(m_dbPassword);
	*pVal = bstr.Detach();
	return S_OK;
}

STDMETHODIMP CMahasiswa::put_DBPassword(BSTR newVal)
{	
	wcstombs_s(&len, m_dbPassword, newVal, 99);
	return S_OK;
}

STDMETHODIMP CMahasiswa::get_DBNama(BSTR* pVal)
{
	CComBSTR bstr(m_dbNama);
	*pVal = bstr.Detach();
	return S_OK;
}

STDMETHODIMP CMahasiswa::put_DBNama(BSTR newVal)
{	
	wcstombs_s(&len, m_dbNama, newVal, 99);
	return S_OK;
}

Saya menambahkan sebuah deklarasi size_t len karena variabel yang sama ini akan dipakai pada saat pemanggilan fungsi wcstombs_s(). Btw, di sini saya menggunakan class CComBSTR milik framework ATL untuk melakukan konversi char* (C string) menjadi BSTR. Sebaliknya, untuk melakukan konversi BSTR menjadi char* (C string), saya menggunakan fungsi wcstombs_s().
Berikutnya, saya akan menambahkan deklarasi method Inisialisasi. Tapi karena akan memakai Connector/CPP, maka saya menambahkan baris berikut ini di bagian atas di class Mahasiswa.cpp:

#include "mysql_connection.h"
#include <cppconn\driver.h>
#include <cppconn\exception.h>
#include <cppconn\prepared_statement.h>
#include <cppconn\statement.h>

using namespace sql;

Lalu, saya mengubah method Inisialisasi sehingga terlihat seperti berikut ini:

STDMETHODIMP CMahasiswa::Inisialisasi(void)
{
	Driver* driver;
	Connection* con;
	Statement* st;

	driver = get_driver_instance();
	con = driver->connect("tcp://127.0.0.1:3306", m_dbUser, m_dbPassword);
	con->setSchema(m_dbNama);

	st = con->createStatement();
	st->execute("CREATE TABLE IF NOT EXISTS tblMahasiswa (nim CHAR(10), nama VARCHAR(200), tahunMasuk INT)");
	delete st;
	delete con;

	return S_OK;
}

Method ini akan secara otomatis membuat sebuah tabel dengan nama tblMahasiswa bila tabel tersebut belum ada. Biar cepat, saya mengandaikan bahwa server MySQL berada di komputer yang sama (IP 127.0.0.1). Saya juga mengabaikan penanganan kesalahan disini.

Sekarang, ubah method TambahMahasiswa sehingga terlihat seperti berikut ini:

STDMETHODIMP CMahasiswa::TambahMahasiswa(BSTR nim, BSTR nama, int tahunMasuk)
{
	Driver* driver;
	Connection* con;
	PreparedStatement* ps;
	char pNim[11];
	char pNama[201];
	wcstombs_s(&len, pNim, nim, 10);
	wcstombs_s(&len, pNama, nama, 200);

	driver = get_driver_instance();
	con = driver->connect("tcp://127.0.0.1:3306", m_dbUser, m_dbPassword);
	con->setSchema(m_dbNama);

	ps = con->prepareStatement("INSERT INTO tblMahasiswa(nim, nama, tahunMasuk) VALUES (?,?,?)");
	ps->setString(1, pNim);
	ps->setString(2, pNama);
	ps->setInt(3, tahunMasuk);
	ps->executeUpdate();

	delete ps;
	delete con;

	return S_OK;
}

Langkah terakhir untuk COM kita adalah membuatnya dengan memilih menu Build, Build Solution. Jika tidak ada pesan kesalahan, maka kita siap untuk lanjut ke tahap berikutnya.

Yang perlu kamu lakukan berikutnya adalah membuat sebuah database baru di MySQL Server. Kamu juga boleh memakai database yang sudah ada. Kamu dapat membuat database baru dengan menggunakan PHPMyAdmin, dan jika kamu terbiasa memakai MySQL Console, maka kamu dapat memberikan perintah:

CREATE DATABASE nama_database;
GRANT ALL ON nama_database.* to nama_user@`127.0.0.1`;

Wow.. Perjalanan yang panjang,  bukan? Tapi percayalah, dibalik sesuatu yang rumit selalu ada kesederhanaan. Kamu sudah membuat bagian rumit-nya, sekarang saatnya menikmati hasil jerih payah kamu.

Buat sebuah halaman PHP dengan seperti berikut ini:

<?php
 if (isset($_GET['txtNIM']) && isset($_GET['txtNama']) && 
 isset($_GET['txtTahunMasuk'])) {

 $com = new COM("Latihan.Mahasiswa");
 $com->DBUser = "root";
 $com->DBPassword = "password";
 $com->DBNama = "latihan";
 $com->Inisialisasi();

 $nim = $_GET['txtNIM'];
 $nama = $_GET['txtNama'];
 $tahunMasuk = $_GET['txtTahunMasuk'];
 $com->TambahMahasiswa($nim, $nama, $tahunMasuk);
}
?>
<html>
<head><title>Latihan Insert</title></head>
<body>
<form action="<?php print $_SERVER['PHP_SELF'] ?>" method="GET">
 <p>NIM: <input type="text" name="txtNIM" size="10" /></p>
 <p>Nama: <input type="text" name="txtNama" size="20" /></p>
 <p>Tahun Masuk: <input type="text" name="txtTahunMasuk" size="10" /></p>
 <p><input type="submit" value="Simpan" /></p>
</form>
</body>
</html>

Pada kode program di atas, jangan lupa menyesuaikan user, password, dan nama database.

Sekarang,  buka halaman PHP tersebut di browser, isi data pada form yang muncul dan klik tombol Simpan. Kemudian periksa isi tabel tblMahasiswa dengan menggunakan PHPMyAdmin ataupun MySQL Console. Jika kamu mengikuti langkah yang ada dengan benar, maka data yang kamu masukkan akan tersimpan ke tabel tblMahasiswa.

Kode program PHP yang mengakses database terlihat sederhana & singkat, bukan? Tapi membuat COM DLL-nya terasa penuh perjuangan, bukan? Lain kali bila kamu melihat sesuatu yang sederhana, kamu mungkin telah sadar bahwa dibaliknya pasti ada yang bekerja keras menyembunyikan kerumitannya.  Sebenarnya, banyak kerumitan bisa dihindari, misalnya bila kamu memakai ADO atau memakai DLL .NET, yang nantinya tidak beda bila memakai Java dengan Web Service (SOAP).  Tapi tidak setidaknya, kamu sudah merasakan sesuatu yang low-level (semakin low-level semakin rumit, sehingga muncullah teknologi high-level untuk menyembunyikan kerumitan dan membuat orang-orang lupa bahwa low-level sebenarnya eksis).

COM DLL Akses MySQL – Bagian 2: Membuat Project di Visual Studio 2010

Tulisan ini merupakan bagian dari panduan membuat COM DLL (ATL) di Visual Studio 2010 yang mengakses MySQL atas request Albert Antonius:

Langkah paling awal tentu saja membuka Visual Studio 2010.  Klik pada New Projects.   Kemudian di pilihan template,  cari yang namanya Visual C++, ATL.  Lalu pilih ATL Project, misalnya LatihanCOM.  Berikan nama pada project tersebut, lalu klik tombol OK.  Visual Studio 2010 akan menampilkan ATL Project Wizard.  Kita tidak perlu melakukan perubahan disini sehingga cukup klik tombol Finish.

Setelah proyek dibuat, langkah penting yang perlu kamu lakukan adalah mengubah Solution Configurations dari Debug menjadi Release seperti yang terlihat pada gambar berikut ini:

Solutions Configuration

Solutions Configuration

Jika kamu lupa melakukan langkah ini dan merubah configuration belakangan, maka kamu perlu mengulang langkah-langkah setelah ini dari awal!!

Sekarang, klik kanan pada proyek LatihanCOM, lalu pilih Properties.  Kita akan melakukan beberapa perubahan disini.

Di Configuration Properties, pilih C/C++, ¬†General. ¬†Kemudian klik drop-down di Additional Include Directories dan pilih <Edit…> seperti yang terlihat pada gambar berikut ini:

Mengatur Additional Include Directories

Mengatur Additional Include Directories

Akan muncul dialog Additional Include Directories.  Klik pada tombol New Line (Ctrl-Insert).  Akan muncul sebuah baris untuk mengisi lokasi direktori.  Klik tombol di kanannya  untuk mempermudah browse file seperti yang terlihat di gambar berikut ini:

Menambah Directori Include

Menambah Directori Include

Pada dialog browse yang muncul, pilih direktori include yang berada di direktori ConnectorCPP (output pada bagian 1), kemudian klik Select Folder.

Lakukan hal yang sama kembali.  Klik tombol New Line (Ctrl-Insert), kali ini pilih direktori include yang berada di lokasi instalasi MySQL.  Misalnya, jika MySQL kamu ada di C:\xampp\mysql, maka folder ini adalah C:\xampp\mysql\include.

Kembali lakukan hal yang sama.  Klik tombol New Line (Ctrl-Insert), dan pilih direktori yang berisi Boost (lihat bagian 1).

Jika kamu melakukannya dengan benar, dialog-mu akan terlihat seperti ini (direktori ini mungkin bisa berbeda tergantung pada drive dan nama yang kamu pakai):

Include Directories

Include Directories

Klik tombol OK.

Sebenarnya apa fungsi direktori tersebut? ¬†Kalau kamu lihat, direktori tersebut berisi file header (diakhiri dengan *.h). ¬†Kecuali untuk boost_1_50_0, di dalam subfolder boost¬†terdapat file hpp (precompiled header). ¬†Semua file ini dipakai dengan cara #include. ¬†Kamu masih ingat bukan dengan #include <iostream> atau #include <stdio.h> di pelajaran C++ ? ¬†Yup, benar… iostream.h dan stdio.h adalah file header yang sudah ada di Visual C++. ¬†Tapi file header yang akan kamu pakai adalah file header yang tidak berada pada lokasi direktori standard. ¬†Itu sebabnya kamu perlu memberitahu Visual C++ untuk mencari ke direktori mana, seandainya kamu memberikan #include “mysql_connection.h”.

Sekarang kita akan beralih ke Preprocessor (masih di bawah¬†C/C++). ¬† Pada baris pertama terdapat Preprocessor Definitions. ¬†Kita perlu menambahkan sebuah definisi baru karena kita akan memakai static library (ini adalah persyaratan dari Connector/C++ bukan dari Visual C++). ¬†Klik tombol drop-down di bagian kanan, kemudian klik pada <Edit…>. ¬†Tambahan definisi CPPCONN_PUBLIC_FUNC= sehingga dialog yang muncul akan terlihat seperti pada gambar:

Preprocessor Definitions

Preprocessor Definitions

Klik tombol OK.

Berikutnya, cari bagian Precompiled Headers.  Pada bagian Precompiled Header, ganti menjadi Not Using Precompiled Headers.  Ini adalah rekomendasi dari Boost, karena kita tidak akan memakai  precompiled header yang kita buat sendiri.

Ok, pengaturan untuk compiler C++ sudah selesai.  Sekarang kita akan melakukan pengaturan pada linker.

Buka Linker, General, kemudian cari bagian Per-user Redirection.  Ganti menjadi Yes, sehingga registry yang seharusnya ditulis ke HKEY_CLASSES_ROOT akan ditulis ke HKEY_CURRENT_USER.  Kenapa begitu?  Karena jika kamu menjalankan Visual Studio 2010 sebagai user biasa, maka kamu tidak akan punya akses untuk menulis ke HKEY_CLASSES_ROOT.

Sekarang kita beralih ke bawahnya, ke bagian Linker, General, Additional Library Directories. ¬†Yang ini hampir mirip seperti di Addition Include Directories. ¬†Klik pada drop-down, kemudian pilih <Edit…>. ¬†

Pada dialog Additional Library Directories yang muncul, klik pada tombol New Line (Ctrl-Insert), kemudian browse ke folder lib yang ada di ConnectorCPP (output pada bagian 1).

Lakukan hal yang sama kembali.  Klik pada tombol New Line (Ctrl-Insert), tapi kali ini browse ke folder lib yang terletak di lokasi instalasi MySQL kamu.

Jika kamu melakukannya dengan benar, dialog-mu akan terlihat seperti pada gambar berikut ini:

Additional Library Directories

Additional Library Directories

Sebenarnya buat apa melakukan pengaturan Additional Library Directories?  Karena kamu memakai library tambahan yang bukan bawaan dari Visual C++.  Dan kamu perlu memberitahu pada Visual C++ dimana harus mencari library tambahan yang kamu pakai.

Ok, jadi apa nama library yang kamu pakai? ¬†Kamu belum memberitahukannya pada Visual C++. ¬†Jadi, pilih Linker, Input, kemudian klik pada drop-down di Additional Dependencies. ¬†Kemudian klik pada <Edit…>. ¬†Akan muncul sebuah dialog. ¬†Tambahkan mysqlcppconn-static.lib dan libmysql.lib seperti yang terlihat di gambar berikut ini:

Additional Dependencies

Additional Dependencies

Klik tombol OK.

Kamu tentu masih ingat dengan mysqlcppconn-static.lib yang kamu hasilkan di bagian 1 dari artikel ini.  Ini adalah sebuah static library.  Pada jenis library ini, kode program library akan digabungkan ke dalam DLL  hasil akhir-mu nanti.  Hal ini menyebabkan ukuran DLL-mu bertambah besar, tetapi lebih cepat.  Kebalikan dari static library adalah dynamic library.  Ingat file mysqlcppconn.dll yang juga kamu hasilkan?  Ini adalah dynamic library.  Bila kamu memakai dynamic library, kamu harus memastikan file dll ini selalu ada di sistem pengguna.  Keuntungannya dibanding static library adalah ukuran program lebih kecil karena kode program library tidak digabungkan dengan DLL kamu melainkan ada di file DLL lain yang terpisah.

Pada artikel ini, kamu akan memakai static library sehingga kamu mengisi mysqlcppconn-static.lib.

Lalu apa itu libmysql.lib?  Ini adalah library Connector/C.  Yup, Connector/C++ dibangun berdasarkan Connector/C sehingga tetap dibutuhkan library versi bahasa C.  Anggap saja Connector/C++ memakai Connector/C tetapi dengan menyediakan sebuah lapisan abstraksi tersendiri yang OOP dan lebih mudah dipakai sehingga pengguna tidak perlu memanggil Connector/C secara langsung.  Kamu bisa meng-install Connector/C secara terpisah, tetapi beruntungnya, library C ini  sudah ter-install bersama dengan MySQL Server.

Klik tombol OK untuk menutup dialog properties.  Setelah pengaturan proyek selesai, kita siap untuk membuat kode program.

COM DLL Akses MySQL – Bagian 1: Membuat Library

Tulisan ini merupakan bagian dari panduan membuat COM DLL (ATL) di Visual Studio 2010 yang mengakses MySQL atas request Albert Antonius:

Library yang saya build dengan Visual Studio 2010 dan MySQL 5.5.9 dapat ditemukan di https://docs.google.com/open?id=0B-_rVDnaVRCbQVlfbmktcDZXOEE.  Kamu boleh melewati bagian ini dan memakai library yang saya build atau yang disediakan di situs MySQL, tetapi library yang ada belum tentu cocok untuk sistem kamu.

Langkah pertama sebelum kamu mulai membuat kode program adalah mencari library untuk mengakses MySQL di Windows.  Kenapa?  Karena Visual C++ secara bawaan tidak menyediakan API untuk memanggil MySQL.  Kalau kamu mau, kamu bisa menggunakan ADO.  Tapi di artikel ini, kamu akan menggunakan API yang lebih cepat yaitu MySQL Connector/C++.  Pada saat tulisan ini dibuat, versi terakhir yang tersedia adalah versi 1.1.

Buka halaman¬†http://dev.mysql.com/downloads/connector/cpp/¬†untuk download library yang dibutuhkan. ¬†Tunggu… “Mana yang harus saya pilih?” demikian kamu bertanya padaku. ¬†Sebaiknya kamu download¬†mysql-connector-c++-1.1.0.zip. ¬†Setelah kamu extract, kamu akan menemukan kode program dan header, yang TIDAK bisa langsung kamu pakai!!

“Apa? ¬†Menyuruh saya men-download sesuatu yang tidak bisa langsung saya pakai?” kamu pasti keberatan, “Saya sibuk dan saya perlu cara instan!!!” ¬†Jika kamu berpikir demikian, maka kamu perlu ingat kembali bahwa kita berada di dunia C++, sebuah dunia yang sangat terikat pada platform. ¬†Kamu pasti sudah sering mendengar cerita tentang Java yang multi platform, ¬†kamu bahkan sudah merasakan bagaimana memakai library hanya dengan menambahkan file JAR, ¬†dan kamu akan jarang sekali perlu menghasilkan JAR ini secara manual dari source code. ¬†Tapi Java tidak bisa segalanya, dan kadang-kadang kita butuh C++ yang ¬†TIDAK¬†multi platform. ¬†Sedikit perbedaan versi sistem operasi dan perbedaan versi compiler bisa membuat sebuah library C++ menjadi usang dan penuh pesan kesalahan. ¬†Oleh sebab itu, lebih baik kita menghasilkan library dari source code sehingga kita bisa yakin library tersebut cocok dengan sistem kita. ¬† Pada halaman download terdapat peringatan seperti ini “One problem that can occur is when the tools you use to build your application are not compatible with the tools used to build the binary versions of MySQL Connector/C++. Ideally, you need to build your application with the same tools that were used to build the MySQL Connector/C++ binaries.

Ok, setelah download source Connector/C++, kamu perlu men-download CMake di http://cmake.org/cmake/resources/software.html. ¬†Pastikan kamu men-download installer cmake-2.8.8-win32-x86.exe.¬†“Apa lagi itu?” tanyamu. ¬†Mungkin kamu akan menemukan banyak hal baru yang tidak ada di kuliah C++, tapi jika kamu suka petualangan, kamu akan menemukan banyak yang menarik. ¬†CMake adalah sebuah tools untuk kompilasi proyek dan aktifitas serupa, mirip seperti make di Linux atau nmake.exe di Visual Studio. ¬†Perbedaannya adalah CMake tersedia di banyak platform. ¬†“Dengan CMake, source code jadi multi platform seperti di Java?” tanyamu. ¬†Tidak, bukan! ¬†Bukan source code-nya, tapi tool untuk kompilasi-nya. ¬†Dengan CMake, kamu ga perlu ganti ke Linux hanya gara2 proyek ¬†memakai make.

Pastikan pada saat kamu meng-install CMake, kamu memilih “Add CMake to the system PATH” (terserah, ke all users atau current user). ¬†Hal ini akan mempermudah kamu dalam memberikan perintah di command prompt nanti tanpa harus memusingkan direktori instalasi CMake. ¬†Walaupun ada versi GUI dengan nama cmake-gui, kamu akan memakai versi command prompt,¬†bukan agar terlihat keren, tetapi untuk mempermudah copy paste dari artikel ini.

Sekarang buka kembali folder mysql-connector-c++-1.1.0 yang telah di-extract.  Disini kamu akan menjumpai sebuah file dengan nama CMakeLists.txt.  Yup, sesuai dengan tebakanmu, ini adalah file yang akan dikerjakan oleh CMake.

Mari jalankan CMake.  Mulai dengan buka sebuah command prompt di Windows 7.  Kemudian pindah ke direktori mysql-connector-c++-1.1.0.  Sebagai contoh, jika direktori tersebut berada di Desktop, maka saya mengetikkan perintah:

CD C:\Users\Jocki\Desktop\mysql-connector-c++-1.1.0

Berikutnya, kamu perlu memberitahu dimana kamu meng-install MySQL. Kamu pasti tahu bukan? Jika kamu memakai XAMPP, kemungkinan besar MySQL ada di C:\xampp\mysql.  Berikan perintah berikut ini:

SET MYSQL_DIR=C:\xampp\mysql

Langkah berikutnya, karena Connector/C++ memakai Boost, maka kamu perlu men-download Boost di http://www.boost.org/users/download/.   Apa itu Boost?  Boost adalah kumpulan library untuk C++ yang sangat lengkap mulai dari fitur foreach hingga dukungan akses jaringan yang portable.  Kamu bisa melihat fitur lengkapnya di http://www.boost.org/doc/libs/1_50_0/.  Jika kamu memakai Boost di ujian mata kuliah C++, mungkin dosenmu akan terkagum-kagum dan memberimu nilai A++ (oops.. A++ adalah A=A+1, nilainya B donk?!)

Setelah download Boost, extract source-nya ke sebuah folder.  Jika folder tersebut adalah C:\Users\Jocki\Desktop\boost_1_50_0, maka berikan perintah seperti berikut ini:

SET BOOST_ROOT=C:\Users\Jocki\Desktop\boost_1_50_0

Sekarang, kamu siap akan melakukan kompilasi kode program yang ada, dengan memberikan perintah:

cmake -G "Visual Studio 10" -DCMAKE_INSTALL_PREFIX=C:\Users\Jocki\Desktop\ConnectorCPP

Pada perintah di atas, kamu akan menggunakan Visual Studio 2010.  Kamu juga memberitahu bahwa nantinya, hasil output yang paling final akan ada di folder C:\Users\Jocki\Desktop\ConnectorCPP.  Kamu mungkin perlu menyesuaikan lokasi ini sesuai selera.

Setelah CMake selesai bekerja, akan ada tulisan Configuring done, Generating done, serta Build files have been written to.

Bila kamu melihat lokasi extract source Connector/C++, kamu akan menemukan file baru, seperti MYSQLCPPCONN.sln, ALL_BUILD.vcxproj, INSTALL.vcxproj, PACKAGE.vcxproj, dan ZERO_CHECK.vcxproj.  Sekarang kamu telah memiliki file-file yang dapat langsung dibuka di Visual Studio 2010.

Double klik file MYSQLCPPCONN.sln.  Visual Studio 2010 akan diluncurkan.  Ini adalah sebuah Solution yang terdiri atas 35 projects!!  Sebelum kamu mulai melakukan sesuatu, ganti Solutions Configuration dari Debug menjadi Release, seperti yang terlihat pada gambar berikut ini:

Solutions Configuration

Solutions Configuration

Klik kanan pada nama proyek ALL_BUILD, kemudian pilih menu BUILD. ¬†Visual C++ akan melakukan proses compile & linking setiap source code yang ada. ¬†Bila kamu terbiasa mem-program proyek open source C++, maka kamu tidak akan terkejut dengan prosesnya yang memakan waktu. ¬†Tapi bila ini pertama kalinya, kamu masih sempat ke dapur untuk membuat kopi. ¬†“Gila.. Kode program C++ paling panjang yang pernah gw temui selama kuliah sekali klik masih bisa langsung muncul hasilnya.. Pertama kalinya gw mesti nunggu lama untuk jalanin program,” mungkin kamu berpikir demikian, tapi hidup memang rumit dan harus berlanjut! ¬†Ngomong-ngomong ini baru build connector-nya, kamu masih belum build MySQL dari kode program (yup, MySQL dibuat pakai C++, tapi kamu biasanya download installer-nya yang sudah ‘jadi’).

Setelah proses build selesai,  klik kanan pada proyek bernama INSTALL.   Lalu pilih Build.  Proses kali ini tidak lama karena pada dasarnya hanya men-copy file hasil linking ke folder yang telah ditentukan.

Bila kamu melakukannya dengan benar, akan terbentuk sebuah folder ConnectorCPP di Desktop (btw, lokasi ini kamu yang tentukan!). Di dalamnya ada folder include dan lib.  Di dalam folder lib terdapat file mysqlcppconn.dll, mysqlcppconn.lib, dan mysqlcppconn-static.lib.  Ini adalah library yang kamu butuhkan.  Dan karena kamu membuatnya dari nol, maka library ini pasti kompatibel dengan sistem-mu.

Memanggil DLL dari PHP

Pada suatu hari, seorang mahasiswa yang sedang menyusun skripsi menanyakan pada saya apakah mungkin mengakses DLL dari kode program PHP.¬† Sebuah pertanyaan yang tidak terduga, karena saya sama sekali tidak pernah dan tidak berpikiran untuk melakukan hal tersebut.¬† Hal ini karena PHP adalah bahasa pemograman yang berada di banyak platform, gratis, dan sangat populer di Linux.¬† Sementara teknologi DLL adalah teknologi yang sangat terikat pada Microsoft Windows serta Visual Studio-nya.¬† Menurut saya, pertanyaan ‘membaca DLL‘ akan lebih selaras bila dipadukan dengan teknologi ASP.NET dengan bahasa C# ataupun VB, dibandingkan dengan PHP.

Kembali ke pertanyaan semula, apakah mungkin memanggil DLL dari PHP? Setelah melakukan pencarian di PHP Manual, saya menemukan bagian COM and .Net (Windows) yang dapat diakses di www.php.net/manual/en/book.com.php.  PHP memang mendukung Component Object Model (COM) dan .NET.  Lalu apa hubungan COM dan DLL?

  • DLL (Dynamic-link Library) adalah implementasi shared-library di platform Windows.¬† Programmer membuat fungsi yang dapat dipanggil ulang di program berbeda.
  • COM (Component Object Model) adalah sebuah metodologi yang mengatur bagaimana menerapkan komponen program yang dapat dipakai ulang di program berbeda.
  • COM tidak mengatur struktur bahasa yang dipakai (menurut referensi MSDN, COM sering salah kaprah diangagp sebagai OOP).
  • Implementasi COM umumnya dalam bentuk file DLL.
  • Tidak semua DLL dibuat berdasarkan aturan COM, atau dengan kata lain tidak semua DLL adalah COM.

Jadi  jawaban untuk mahasiswa tersebut adalah: bila DLL dibuat dengan menggunakan teknologi COM, maka DLL tersebut dapat diakses di PHP.  Bila DLL tersebut adalah DLL sederhana, maka DLL tersebut tidak dapat diakses di PHP.

Lalu bagaimana bila ingin tetap mengakses DLL sederhana di PHP?  Ada sebuah extension PHP yang dikhususkan untuk Windows yang bernama WinBinder.  Dengan WinBinder, programmer PHP dapat memanggil semua APIs Windows dengan PHP, membuat program GUI berbasis PHP, bahkan memanggil DLL sederhana (tanpa COM) dengan PHP.

Pada artikel ini, saya akan menggunakan teknologi sebagai berikut:

  • Visual C++ di Visual Studio 2010 untuk menghasilkan DLL yang mengikuti spesifikasi COM.
  • Memakai ATL (Active Template Library) di Visual C++ untuk membuat COM.¬† ATL bukan bagian dari COM ataupun bahasa C++ melainkan sebuah framework sebagai bagian dari Visual C++ untuk mempermudah pembuatan COM.
  • PHP untuk mengakses DLL yang dihasilkan oleh Visual C++

Untuk membuat COM di Visual C++ dengan bantuan ATL, pilih menu File, New, Project di Visual Studio 2010.  Kemudian pada Visual C++, pilih ATL Project.  Beri nama pada project tersebut, misalnya LatihanCOM.  Tentukan juga lokasi folder untuk penyimpanan project tersebut.  Kemudian klik tombol OK.

Membuat Project ATL Baru

Membuat Project ATL Baru

Akan muncul ATL Project Wizard yang terdiri atas dua langkah.  Klik tombol Next pada wizard tersebut.  Pastikan pada Application Type, pilihan Dynamic-link library (DLL) terpilih.  Kemudian klik tombol Finish untuk membuat project.

ATL Project Wizard

ATL Project Wizard

Setelah project selesai dibuat, buka panel Class View.¬† Bila panel ini tertutup, pilih menu View, Class View (Ctrl+Shift+C) untuk menampilkannya.¬† Klik kanan pada nama project, LatihanCOM, kemudian pilih Add, Class…¬† Pada window Add Class yang muncul, pilih ATL Simple Object.¬† Kemudian klik tombol Add untuk melanjutkan.

Tampilan Dialog Add Class

Tampilan Dialog Add Class

Pada tampilan ATL Simple Object Wizard yang muncul, ketik nama Perhitungan di Short name.  Nama lain akan di-isi secara otomatis.  Pastikan bahwa di bagian COM, nama Interface adalah IPerhitungan.  Kemudian isi ProgID dengan nama Jocki.Perhitungan.  Window tersebut harus terlihat seperti berikut ini:

ATL Simple Object Wizard

ATL Simple Object Wizard

Klik tombol Finish untuk menyelesaikan wizard.

Buka panel Class View dan cari COM Interface yang bernama IPerhitungan.¬† Interface ini dibuat secara otomatis oleh Visual C++ sehingga yang perlu kita lakukan hanya menambahkan method yang akan dipanggil oleh program lain.¬† Klik kanan pada IPerhitungan, kemudian pilih Add, Add Method…

Menambah Method Pada COM Interface

Menambah Method Pada COM Interface

Pada dialog Add Method Wizard yang muncul, kita akan membuat sebuah method sederhana.  Method ini akan menerima parameter berupa dua buah bilangan bulat, kemudian mengembalikan hasil jumlah dua bilangan tersebut.  Pada method name, beri nama HitungJumlah.  Kemudian tambahkan dua parameter input dengan mengikuti langkah ini:

  1. Beri centang pada checkbox in di Parameter attributes.
  2. Ketik int di Parameter type.
  3. Ketik nilai1 di Parameter name.
  4. Klik tombol Add.
  5. Beri centang pada checkbox in di Parameter attributes.
  6. Ketik int di Parameter type.
  7. Ketik nilai2 di Parameter name.
  8. Klik tombol Add.

Setelah menambahkan parameter, tambahkan nilai kembalian dengan mengikuti langkah ini:

  1. Ketik int* di Parameter type.  Jangan lupa menambahkan * setelah int karena variabel ini akan merujuk ke hasil kembalian (pointer).  Bila tidak ada * setelah int, wizard tidak akan memberikan pilihan untuk mencentang checkbox out dan retval di langkah berikutnya.
  2. Beri tanda centang checkbox out dan checkbox retval.
  3. Ketik hasil pada Parameter name.
  4. Klik tombol Add.

Tampilan wizard akan terlihat seperti berikut ini:

Tampilan Add Method Wizard

Tampilan Add Method Wizard

Klik tombol Finish untuk menyelesaikan wizard.

Cari baris kode program seperti berikut ini di file Perhitungan.cpp:

STDMETHODIMP CPerhitungan::HitungJumlah(int nilai1, int nilai2, int* hasil)
{
// TODO: Add your implementation code here

return S_OK;
}

Kode program ini merupakan implementasi dari method yang akan dipanggil oleh PHP nantinya.  Ganti baris yang diawali dengan //TODO sehingga kode programnya akan terlihat seperti berikut ini:

STDMETHODIMP CPerhitungan::HitungJumlah(int nilai1, int nilai2, int* hasil)
{ *hasil = nilai1 + nilai2;

return S_OK;
}

Langkah terakhir sebelum menghasilkan DLL adalah melakukan sedikit perubahan pada settingan project.  Buka panel Solution Explorer.  Bila panel ini tertutup, pilih menu View, Solution Explorer (Ctrl+Alt+L).  Klik kanan pada nama project, LatihanCOM, kemudian pilih Properties.  Pada baris Per-user Redirection, ganti nilai No menjadi Yes.  Hal ini dilakukan untuk menghindari hal-hal yang berkaitan dengan masalah hak akses user.  Dengan mengaktifkan Per-user Redirection, DLL yang dihasilkan hanya akan tersedia oleh user Windows yang sedang aktif saat ini saja.

Untuk membuat DLL, pilih menu Build, Build Solution (Ctrl+Shift+B).  File DLL yang dihasilkan terletak di folder Debug di lokasi penyimpanan project.  Sebagai contoh, jika saya menyimpan project di Desktop, maka file DLL yang dihasilkan adalah C:\Users\JockiHendry\Desktop\LatihanCOM\Debug\LatihanCOM.dll.  Visual C++ 2010 telah melakukan registrasi DLL secara otomatis sehingga kita tidak perlu repot-repot lagi.

Sekarang, kita akan melakukan pengujian apakah DLL tersebut dapat dipanggil dengan baik.  Buat sebuah file PHP dengan isi seperti berikut ini:

<?php
   $perhitungan = new COM("Jocki.Perhitungan");
   $hasil = $perhitungan->HitungJumlah(11,22);
   print "Hasil dari method di COM DLL adalah $hasil";
?>

Bila kode program PHP di atas dijalankan, hasilnya adalah:

Hasil dari method di COM DLL adalah 33

Apa langkah berikutnya?

  • Ingin memanggil COM yang berada di komputer lain?¬† Pelajari Distributed COM (DCOM) lebih lanjut.
  • Tidak ingin memakai teknologi COM melainkan ingin memakai simple DLL?¬† Lihat solusi yang ditawarkan oleh Winbinder.
  • Walaupun Microsoft tidak menghentikan dukungan atas COM, teknologi tersebut sudah kadaluarsa dan kini digantikan oleh penerusnya.¬† Ingin beralih ke .NET?¬† Pelajari lebih lanjut tentang .NET Component.