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 #

JalurDiperlukanCocok untuk
ctypes (bawaan Python)Library ber-C ABI; tanpa alat tambahanMemakai .so/.dll yang sudah ada
CPython APIHeader Python + compilerIntegrasi dalam; memakai tipe PyObject langsung
pybind11Compiler + pip install pybind11Binding 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:

cpp
// 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:

bash
g++ -std=c++23 -Wall -Wextra kalkulator-ctypes.cpp -o kalkulator-ctypes
./kalkulator-ctypes

Output yang Dihasilkan:

text
tambah(8, 5) = 13
kali(8, 5)   = 40
jarak(3, 4)  = 5.0

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

bash
g++ -std=c++23 -Wall -Wextra -fPIC -shared kalkulator-ctypes.cpp -o libkalkulator.so
python3 panggil-ctypes.py

Output yang Dihasilkan:

text
tambah(19, 23) = 42
kali(12, 12)   = 144
jarak(3, 4)    = 5.0

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

python panggil-ctypes.py
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 p

Bedah mekanismenya:

  1. Baris 3 (ctypes.CDLL("./libkalkulator.so")) — Python memuat library saat runtime lewat dynamic loader: mekanisme yang sama yang bekerja ketika utama-dinamis memanggil libmatematika.so.
  2. Baris 5-8 (argtypes, restype) — deklarasi tanda tangan fungsi: argumen dikonversi sesuai daftar argtypes, dan nilai balik diinterpretasikan sebagai restype. Tanpa ini, Python mengirim int secara naif dan menganggap semua nilai balik int — sumber bug senyap.
  3. Baris 11-12 (class Titik(ctypes.Structure)) — struct C direplikasi di sisi Python: urutan dan tipe field harus persis sama dengan definisi C++, karena ctypes menyusun byte mengikuti deklarasi ini.
  4. Baris 21 (ctypes.byref(p)) — mengirim alamat p, bukan salinannya, cocok dengan parameter const Titik* di sisi C++.

Pemetaan Tipe antara Dua Dunia #

ctypes hanya memahami tipe C; tabel ini adalah peta terjemahan yang paling sering dipakai:

PythonctypesC++
intc_int, c_longint, long
floatc_doubledouble
boolc_boolbool
bytesc_char_pconst char*
str.encode() duluconst char* (UTF-8)
list/arraypointer + panjang terpisahconst 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 .