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.

c
/* 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 */
c
/* 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:

bash
gcc -std=c11 -Wall -Wextra -fPIC -shared -o libhitung.so libhitung.c
ls -la libhitung.so

Output yang Dihasilkan:

text
-rwxrwxr-x 1 z z 15176 Oct 13 08:56 libhitung.so

Mari kita telaah baris-baris kode di atas:

  1. 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_tambahii dan dlsym("hitung_tambah") gagal.
  2. 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 Rust extern "C".
  3. 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 -shared pada Linux x86_64 (atau menghasilkan library yang tidak dapat di-relokasi).
  4. Flag -shared: Menyuruh linker menghasilkan shared object, bukan executable. Output tidak memiliki entry point main dan siap dimuat oleh dlopen atau dynamic linker.
  5. ls -la libhitung.so: File berukuran ~15 KB dengan permission rwxr-xr-x. Permission execute wajib karena dynamic loader memetakan file ke memori dengan PROT_EXEC.

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.

c
/* 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:

bash
gcc -std=c11 -Wall -Wextra -o link_langsung link_langsung.c -L. -lhitung -Wl,-rpath,.
./link_langsung

Output yang Dihasilkan:

text
Versi: libhitung 1.0.0
5 + 7 = 12
5!    = 120

Mari kita telaah baris-baris kode di atas:

  1. 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 -I path.
  2. Flag -L.: Menambahkan direktori . (current directory) ke search path linker untuk mencari lib*.so/lib*.a.
  3. Flag -lhitung: Menyuruh linker mencari library bernama libhitung.so (atau libhitung.a). Konvensi: prefix lib + nama + suffix .so/.a. Linker secara otomatis menambahkan keduanya.
  4. Flag -Wl,-rpath,.: Meneruskan -rpath . ke linker (-Wl, adalah jembatan dari gcc ke ld). rpath disematkan ke executable dan dibaca dynamic loader saat start. Tanpa rpath, loader mencari di path default (/lib, /usr/lib) dan akan gagal bila library belum di-install.
  5. 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).

c
/* 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:

bash
gcc -std=c11 -Wall -Wextra -o link_dinamis link_dinamis.c -ldl
./link_dinamis

Output yang Dihasilkan:

text
Versi: libhitung 1.0.0
5 + 7 = 12
5!    = 120

Mari kita telaah baris-baris kode di atas:

  1. Baris 7-9 (typedef ... (*binop_fn)(int,int)): Mendefinisikan tipe pointer ke fungsi yang menerima dua int dan mengembalikan int. Kita perlu tipe ini karena dlsym mengembalikan void *, bukan function pointer. Konversi void * ke function pointer secara teknis melanggar strict aliasing di C standar, tetapi POSIX secara eksplisit mengizinkannya.
  2. Baris 13 (dlopen("./libhitung.so", RTLD_NOW | RTLD_LOCAL)): Memuat library. RTLD_NOW menyuruh loader menyelesaikan semua symbol segera—bila ada symbol tidak ditemukan, dlopen gagal di sini. Alternatif RTLD_LAZY menunda resolusi sampai fungsi dipanggil. RTLD_LOCAL menyembunyikan symbol library dari proses lain—tidak terlihat di global symbol table.
  3. Baris 18 (dlerror() pertama untuk reset): dlerror mengembalikan string deskripsi error terakhir sekaligus menghapus status error. Memanggilnya sebelum dlsym membersihkan error lama supaya pemeriksaan setelahnya valid.
  4. Baris 20 (dlsym(handle, "hitung_versi")): Mengembalikan alamat symbol hitung_versi di library yang dimuat. String "hitung_versi" harus persis cocok dengan nama symbol di .so—inilah mengapa extern "C" di header penting agar tidak terkena name mangling C++.
  5. Baris 22-24 (if (err) check): Karena dlsym sah mengembalikan NULL sebagai alamat valid (misalnya variable global bernilai 0), kita tidak boleh menguji if (f_versi == NULL). Cara POSIX yang benar adalah memeriksa dlerror() setelah dlsym—bila string tidak-NULL, terjadi error.
  6. Flag -ldl: Library libdl menyediakan fungsi dlopen, dlsym, dlclose, dlerror. Pada glibc modern, fungsi-fungsi ini juga ada di libc (-lc), tetapi untuk portabilitas tetap disarankan link eksplisit -ldl.

Inspeksi Symbol Export #

Kita dapat memverifikasi symbol apa yang diekspor library dengan tool nm -D:

bash
nm -D libhitung.so | grep hitung

Output yang Dihasilkan:

text
000000000000110d T hitung_faktorial
00000000000010f9 T hitung_tambah
000000000000113f T hitung_versi

Tiga symbol T (huruf besar, di section .text, global) tersedia untuk konsumsi eksternal. Inilah symbol yang dapat di-dlsym oleh siapa pun.