Chapter sebelumnya mengajarkan cara mengonsumsi library; sekarang arahnya kita balik. Python punya ekosistem terluas untuk data dan machine learning, sedangkan C++ unggul saat performa dan kontrol memori menjadi tarif utama — dan kombinasi keduanya ada di hampir semua proyek serius: NumPy menjalankan perhitungan array di C/C++, TensorFlow dan PyTorch mengeksekusi graf komputasinya lewat kernel C++. Pada Chapter ini kita bangun jembatan versi paling mendasar: menulis fungsi C++ ber-C ABI, mengemasnya menjadi .so, lalu memanggilnya dari Python dengan modul ctypes bawaan — semuanya diuji sungguhan di lingkungan course ini.
Mengapa Python Membutuhkan C++ #
Interpreter Python menjalankan bytecode satu per satu; loop yang panjang di Python bisa belasan kali lebih lambat daripada padanannya di C++. Ketika hot path — bagian yang dijalankan jutaan kali — terdeteksi, pola yang lazim adalah memindahkan bagian itu ke C++ dan menyisakan orkestrasi di Python. Kuncinya kembali ke Chapter C ABI dan C++ ABI: Python tidak mengerti mangling C++ maupun std::string, tetapi Python (dan ctypes-nya) sangat paham C ABI. Maka resepnya selalu: bungkus logika C++ dengan antarmuka C.
Tiga Jalur Menuju Python #
| Jalur | Diperlukan | Cocok untuk |
|---|---|---|
ctypes (bawaan Python) | Library ber-C ABI; tanpa alat tambahan | Memakai .so/.dll yang sudah ada |
| CPython API | Header Python + compiler | Integrasi dalam; memakai tipe PyObject langsung |
| pybind11 | Compiler + pip install pybind11 | Binding modern; konversi tipe otomatis |
Ada juga generator antarmuka seperti SWIG yang membaca file deklarasi dan menghasilkan binding untuk banyak bahasa sekaligus — pendekatan yang lebih tua dan masih dipakai proyek besar, tetapi cenderung tergantikan oleh pybind11 untuk Python. Kita mulai dari ctypes karena paling telanjang: apa yang terjadi benar-benar terlihat, dan pengetahuannya dipakai ulang oleh semua jalur lain.
Langkah 1: Siapkan Fungsi C++ dengan C ABI #
Program berikut sengaja ditulis ganda: punya main() agar bisa diuji sebagai program C++, dan berisi fungsi extern "C" yang nanti diekspor sebagai library:
// kalkulator-ctypes.cpp — fungsi dengan C ABI yang siap dipanggil dari Python.
// File ini juga punya main() agar bisa diuji sebagai program C++ biasa;
// saat dibangun menjadi .so, keberadaan main() tidak mengganggu apa pun.
#include <cmath>
#include <cstdio>
struct Titik {
double x;
double y;
};
// Semua fungsi yang diekspor ber-linkage C: simbolnya tidak dimangled,
// sehingga Python (dan bahasa lain) menemukannya dengan nama polos.
extern "C" {
int tambah(int a, int b) { return a + b; }
int kali(int a, int b) { return a * b; }
double jarak(const Titik* p) {
return std::sqrt(p->x * p->x + p->y * p->y);
}
}
int main() {
std::printf("tambah(8, 5) = %d\n", tambah(8, 5));
std::printf("kali(8, 5) = %d\n", kali(8, 5));
Titik p{3.0, 4.0};
std::printf("jarak(3, 4) = %.1f\n", jarak(&p));
return 0;
}Uji dulu sebagai program biasa:
g++ -std=c++23 -Wall -Wextra kalkulator-ctypes.cpp -o kalkulator-ctypes
./kalkulator-ctypesOutput yang Dihasilkan:
tambah(8, 5) = 13
kali(8, 5) = 40
jarak(3, 4) = 5.0Tiga hal penting dari kodenya. Baris 12-20 (extern "C" { ... }): ketiga fungsi diekspor dengan nama polos — persis syarat yang dibahas pada Chapter ABI. Baris 8-11 (struct Titik): struct C polos, tata letaknya diatur C ABI sehingga aman melewati batas. Baris 17-19 (jarak): menerima const Titik* — pointer ke struct adalah cara paling umum mengoper data terstruktur ke antarmuka C.
Langkah 2: Bangun .so dan Panggil dari ctypes #
Sekarang kemas fungsi-fungsi itu menjadi dynamic library — opsi yang sama persis dengan Chapter Static dan Dynamic Libraries:
g++ -std=c++23 -Wall -Wextra -fPIC -shared kalkulator-ctypes.cpp -o libkalkulator.so
python3 panggil-ctypes.pyOutput yang Dihasilkan:
tambah(19, 23) = 42
kali(12, 12) = 144
jarak(3, 4) = 5.0Perhatikan: program Python memanggil kode C++ sungguhan — nilai tambah(19, 23), kali(12, 12), dan jarak(3, 4) dihitung di sisi C++, dikembalikan ke Python sebagai nilai balik. Skrip Python-nya:
import ctypes
lib = ctypes.CDLL("./libkalkulator.so")
lib.tambah.argtypes = [ctypes.c_int, ctypes.c_int] # tipe argumen
lib.tambah.restype = ctypes.c_int # tipe nilai balik
lib.kali.argtypes = [ctypes.c_int, ctypes.c_int]
lib.kali.restype = ctypes.c_int
class Titik(ctypes.Structure): # replika struct C di sisi Python
_fields_ = [("x", ctypes.c_double), ("y", ctypes.c_double)]
lib.jarak.argtypes = [ctypes.POINTER(Titik)]
lib.jarak.restype = ctypes.c_double
print("tambah(19, 23) =", lib.tambah(19, 23))
print("kali(12, 12) =", lib.kali(12, 12))
p = Titik(3.0, 4.0)
print("jarak(3, 4) =", lib.jarak(ctypes.byref(p))) # kirim alamat pBedah mekanismenya:
- Baris 3 (
ctypes.CDLL("./libkalkulator.so")) — Python memuat library saat runtime lewat dynamic loader: mekanisme yang sama yang bekerja ketikautama-dinamismemanggillibmatematika.so. - Baris 5-8 (
argtypes,restype) — deklarasi tanda tangan fungsi: argumen dikonversi sesuai daftarargtypes, dan nilai balik diinterpretasikan sebagairestype. Tanpa ini, Python mengirimintsecara naif dan menganggap semua nilai balikint— sumber bug senyap. - Baris 11-12 (
class Titik(ctypes.Structure)) — struct C direplikasi di sisi Python: urutan dan tipe field harus persis sama dengan definisi C++, karenactypesmenyusun byte mengikuti deklarasi ini. - Baris 21 (
ctypes.byref(p)) — mengirim alamatp, bukan salinannya, cocok dengan parameterconst Titik*di sisi C++.
Pemetaan Tipe antara Dua Dunia #
ctypes hanya memahami tipe C; tabel ini adalah peta terjemahan yang paling sering dipakai:
| Python | ctypes | C++ |
|---|---|---|
int | c_int, c_long | int, long |
float | c_double | double |
bool | c_bool | bool |
bytes | c_char_p | const char* |
str | .encode() dulu | const char* (UTF-8) |
list/array | pointer + panjang terpisah | const T*, int n |
Baris terakhir itulah titik lemah ctypes: tidak ada cara elegan mengoper list Python ke std::vector, string Python ke std::string, atau menerima object C++ kembali sebagai object Python yang hidup. Semua itu kerja manual — dan justru di sinilah Chapter berikutnya masuk: pybind11 mengotomatiskan seluruh tabel terjemahan itu dengan tipe parameternya sendiri.
Referensi: dokumentasi ctypes Python , pybind11 documentation .