Shared library (file .so di Linux, .dylib di macOS, .dll di Windows) adalah kode mesin yang dimuat ke memori proses saat runtime, bukan saat link time. Shared library memungkinkan beberapa program berbagi satu kode di memori, mengurangi ukuran binary, dan memungkinkan update tanpa recompile aplikasi. Lebih penting lagi bagi topik FFI: shared library adalah wadah utama yang dipanggil lintas bahasa—Python ctypes, Rust extern "C", dan Go cgo semuanya memuat .so/.dll untuk memanggil fungsi C. Pada chapter ini kita akan membangun libhitung.so dan mengonsumsinya dengan dua cara: link langsung saat build time, dan dlopen/dlsym untuk loading dinamis saat runtime.
Membangun Shared Library #
Shared library memerlukan Position Independent Code (PIC) supaya kode dapat dimuat di alamat memori mana pun tanpa relokasi. GCC menyalakan PIC lewat flag -fPIC. Output .so dihasilkan oleh flag -shared. Library biasanya terdiri dari pasangan header (libhitung.h) yang menyatakan kontrak publik, dan implementasi (libhitung.c) yang berisi kode.
/* File: libhitung.h - kontrak publik */
#ifndef LIBHITUNG_H
#define LIBHITUNG_H
#ifdef __cplusplus
extern "C" {
#endif
int hitung_tambah(int a, int b);
int hitung_faktorial(int n);
const char *hitung_versi(void);
#ifdef __cplusplus
}
#endif
#endif /* LIBHITUNG_H *//* File: libhitung.c - implementasi yang akan jadi libhitung.so */
#include "libhitung.h"
int hitung_tambah(int a, int b) {
return a + b;
}
int hitung_faktorial(int n) {
int hasil = 1;
for (int i = 2; i <= n; i++) {
hasil *= i;
}
return hasil;
}
const char *hitung_versi(void) {
return "libhitung 1.0.0";
}Compile menjadi shared library:
gcc -std=c11 -Wall -Wextra -fPIC -shared -o libhitung.so libhitung.c
ls -la libhitung.soOutput yang Dihasilkan:
-rwxrwxr-x 1 z z 15176 Oct 13 08:56 libhitung.soMari kita telaah baris-baris kode di atas:
- Baris 4-6 (
#ifdef __cplusplus extern "C"): Sama seperti chapter C ABI, block ini menonaktifkan name mangling C++. Penting karena konsumen library bisa berupa program C++. Tanpa block ini, C++ akan mengubah nama symbol menjadi seperti_Z14hitung_tambahiidandlsym("hitung_tambah")gagal. - Baris 8-10 (
hitung_tambah,hitung_faktorial,hitung_versi): Tiga fungsi publik. Tipe parameter dan return value semuanya tipe ABI-friendly (int,const char *) yang dapat diakses langsung dari Python ctypes atau Rustextern "C". - Flag
-fPIC: Menyuruh GCC menghasilkan Position Independent Code. Setiap akses ke global variable dan call function lewat tabel GOT/PLT, bukan absolute address. Tanpa-fPIC, linker menolak-sharedpada Linux x86_64 (atau menghasilkan library yang tidak dapat di-relokasi). - Flag
-shared: Menyuruh linker menghasilkan shared object, bukan executable. Output tidak memiliki entry pointmaindan siap dimuat olehdlopenatau dynamic linker. ls -la libhitung.so: File berukuran ~15 KB dengan permissionrwxr-xr-x. Permission execute wajib karena dynamic loader memetakan file ke memori dengan PROT_EXEC.
Konsumsi Cara 1: Link Langsung #
Cara pertama menggunakan library adalah dengan link langsung saat build time. Kompiler butuh -I. untuk menemukan libhitung.h, dan linker butuh -L. -lhitung untuk menemukan libhitung.so. Pada runtime, dynamic loader harus tahu di mana file .so berada—kita gunakan -Wl,-rpath,. untuk menyemat path relatif ke executable.
/* File: link_langsung.c - link langsung ke libhitung.so */
#include <stdio.h>
#include "libhitung.h"
int main(void) {
printf("Versi: %s\n", hitung_versi());
printf("5 + 7 = %d\n", hitung_tambah(5, 7));
printf("5! = %d\n", hitung_faktorial(5));
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -o link_langsung link_langsung.c -L. -lhitung -Wl,-rpath,.
./link_langsungOutput yang Dihasilkan:
Versi: libhitung 1.0.0
5 + 7 = 12
5! = 120Mari kita telaah baris-baris kode di atas:
- Baris 3 (
#include "libhitung.h"): Compiler membaca deklarasi fungsi dari header. Tanda kutip ganda"..."menyuruh compiler mencari di direktori source file dulu; bila tidak ada, mencari di-Ipath. - Flag
-L.: Menambahkan direktori.(current directory) ke search path linker untuk mencarilib*.so/lib*.a. - Flag
-lhitung: Menyuruh linker mencari library bernamalibhitung.so(ataulibhitung.a). Konvensi: prefixlib+ nama + suffix.so/.a. Linker secara otomatis menambahkan keduanya. - Flag
-Wl,-rpath,.: Meneruskan-rpath .ke linker (-Wl,adalah jembatan dari gcc ke ld).rpathdisematkan ke executable dan dibaca dynamic loader saat start. Tanparpath, loader mencari di path default (/lib,/usr/lib) dan akan gagal bila library belum di-install. - Baris 6-8 (pemanggilan): Kode terlihat persis seperti memanggil fungsi biasa. Perbedaannya: pada runtime, setiap call pertama ke fungsi library memicu resolusi PLT (Procedure Linkage Table) yang mengarahkan call ke alamat fungsi di
libhitung.so.
Konsumsi Cara 2: dlopen/dlsym #
Cara kedua memuat library saat runtime tanpa link time. Aplikasi menyebut nama file library (./libhitung.so) dan nama symbol (hitung_tambah) sebagai string. Pendekatan ini dipakai oleh plugin system, JIT, dan sebagian besar FFI tingkat tinggi (termasuk Python ctypes).
/* File: link_dinamis.c - muat libhitung.so lewat dlopen/dlsym */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
typedef int (*binop_fn)(int, int);
typedef int (*unop_fn)(int);
typedef const char *(*versi_fn)(void);
int main(void) {
void *handle = dlopen("./libhitung.so", RTLD_NOW | RTLD_LOCAL);
if (!handle) {
fprintf(stderr, "dlopen gagal: %s\n", dlerror());
return 1;
}
dlerror(); /* reset error */
versi_fn f_versi = (versi_fn) dlsym(handle, "hitung_versi");
const char *err = dlerror();
if (err) { fprintf(stderr, "dlsym gagal: %s\n", err); dlclose(handle); return 1; }
binop_fn f_tambah = (binop_fn) dlsym(handle, "hitung_tambah");
unop_fn f_fakt = (unop_fn) dlsym(handle, "hitung_faktorial");
printf("Versi: %s\n", f_versi());
printf("5 + 7 = %d\n", f_tambah(5, 7));
printf("5! = %d\n", f_fakt(5));
dlclose(handle);
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -o link_dinamis link_dinamis.c -ldl
./link_dinamisOutput yang Dihasilkan:
Versi: libhitung 1.0.0
5 + 7 = 12
5! = 120Mari kita telaah baris-baris kode di atas:
- Baris 7-9 (
typedef ... (*binop_fn)(int,int)): Mendefinisikan tipe pointer ke fungsi yang menerima duaintdan mengembalikanint. Kita perlu tipe ini karenadlsymmengembalikanvoid *, bukan function pointer. Konversivoid *ke function pointer secara teknis melanggar strict aliasing di C standar, tetapi POSIX secara eksplisit mengizinkannya. - Baris 13 (
dlopen("./libhitung.so", RTLD_NOW | RTLD_LOCAL)): Memuat library.RTLD_NOWmenyuruh loader menyelesaikan semua symbol segera—bila ada symbol tidak ditemukan,dlopengagal di sini. AlternatifRTLD_LAZYmenunda resolusi sampai fungsi dipanggil.RTLD_LOCALmenyembunyikan symbol library dari proses lain—tidak terlihat di global symbol table. - Baris 18 (
dlerror()pertama untuk reset):dlerrormengembalikan string deskripsi error terakhir sekaligus menghapus status error. Memanggilnya sebelumdlsymmembersihkan error lama supaya pemeriksaan setelahnya valid. - Baris 20 (
dlsym(handle, "hitung_versi")): Mengembalikan alamat symbolhitung_versidi library yang dimuat. String"hitung_versi"harus persis cocok dengan nama symbol di.so—inilah mengapaextern "C"di header penting agar tidak terkena name mangling C++. - Baris 22-24 (
if (err)check): Karenadlsymsah mengembalikanNULLsebagai alamat valid (misalnya variable global bernilai 0), kita tidak boleh mengujiif (f_versi == NULL). Cara POSIX yang benar adalah memeriksadlerror()setelahdlsym—bila string tidak-NULL, terjadi error. - Flag
-ldl: Librarylibdlmenyediakan fungsidlopen,dlsym,dlclose,dlerror. Pada glibc modern, fungsi-fungsi ini juga ada dilibc(-lc), tetapi untuk portabilitas tetap disarankan link eksplisit-ldl.
Inspeksi Symbol Export #
Kita dapat memverifikasi symbol apa yang diekspor library dengan tool nm -D:
nm -D libhitung.so | grep hitungOutput yang Dihasilkan:
000000000000110d T hitung_faktorial
00000000000010f9 T hitung_tambah
000000000000113f T hitung_versiTiga symbol T (huruf besar, di section .text, global) tersedia untuk konsumsi eksternal. Inilah symbol yang dapat di-dlsym oleh siapa pun.