Membungkus Pustaka C

Memuat pustaka C, mengonversi tipe argumen, dan menguji binding yang dihasilkan.

Daftar isi

Pustaka C yang telah dikompilasi menjadi pustaka bersama (shared library) dapat dibungkus menjadi fungsi Julia yang aman dipakai, lengkap dengan anotasi tipe, validasi argumen, dan pengujian otomatis. Topik ini mempraktikkan alurnya secara utuh, mulai dari berkas kode sumber, kompilasi dengan gcc, pemanggilan melalui ccall, hingga pengujian wrapper dengan pustaka standar Test. Hasil akhirnya adalah binding, yaitu lapisan penghubung menuju pustaka asing, yang teruji dan siap dipakai ulang.

Berkas pustaka C#

Contoh memakai pustaka kecil bernama geometri yang berisi tiga fungsi murni, yaitu perhitungan luas lingkaran, sisi miring segitiga siku-siku, dan konversi suhu. Seluruhnya hanya bergantung pada math.h, sehingga pustaka ini dapat dikompilasi di hampir semua sistem. Berkasnya tidak memuat fungsi main karena geometri adalah sumber pustaka, bukan program yang dieksekusi sendiri, dan fungsinya diuji melalui Julia setelah dikompilasi.

c geometri.c
// tidak-dijalankan
// geometri.c: sumber pustaka C untuk contoh binding Julia
#include <math.h>

double luas_lingkaran(double r) {
    return M_PI * r * r;
}

double hipotenusa(double a, double b) {
    return hypot(a, b);
}

double fahrenheit(double celsius) {
    return celsius * 9.0 / 5.0 + 32.0;
}

Setiap fungsi menerima argumen double dan mengembalikan double, sehingga pemetaan tipenya seragam, yaitu Cdouble pada seluruh posisi. Kebiasaan berpegang pada tipe sederhana seperti ini mempermudah pembungkusan pertama sebelum pustaka berkembang menjadi lebih kompleks.

Kompilasi menjadi pustaka bersama#

Kompilasi dilakukan dengan gcc menggunakan opsi -shared untuk menghasilkan pustaka bersama dan -fPIC untuk kode posisi-independen (position-independent code), yaitu kode yang dapat dimuat pada alamat memori mana pun saat program berjalan. Kedua opsi ini lazim dipasangkan untuk pustaka yang dimuat dari bahasa lain.

bash
# mengompilasi geometri.c menjadi pustaka bersama geometri.so
gcc -shared -fPIC -o geometri.so geometri.c

Perintah yang berhasil tidak menampilkan apa pun, dan berkas geometri.so muncul di folder kerja. Ekstensi .so adalah konvensi pustaka bersama pada Linux, sedangkan macOS memakai .dylib dan Windows memakai .dll.

Pemuatan dan pemanggilan pertama#

Pustaka dimuat otomatis pada ccall pertama yang menyebutnya, dan pemanggilannya tidak memerlukan penyiapan apa pun selain jalur pustaka. Contoh berikut memanggil ketiga fungsi geometri dengan tipe argumen Cdouble yang sudah dikenal dari tabel pemetaan.

julia Contoh: pemanggilan pertama ke geometri.so
println(ccall((:hipotenusa, "./geometri.so"), Cdouble, (Cdouble, Cdouble), 3.0, 4.0))
println(ccall((:luas_lingkaran, "./geometri.so"), Cdouble, (Cdouble,), 2.0))
println(ccall((:fahrenheit, "./geometri.so"), Cdouble, (Cdouble,), 100.0))
text Keluaran
5.0
12.566370614359172
212.0

Ketiga nilai sesuai perhitungan manual, yaitu sisi miring segitiga dengan sisi tegak 3 dan 4, luas lingkaran berjari-jari 2, serta 100 derajat Celsius yang menjadi 212 derajat Fahrenheit.

Membungkus dengan fungsi Julia yang aman#

Pemanggilan ccall mentah menyebarkan nama simbol dan tuple tipe ke setiap titik pemanggilan. Wrapper mengumpulkan detail tersebut di satu tempat, yaitu fungsi Julia yang menerima tipe Julia alami, memvalidasi argumennya, melakukan konversi yang diperlukan, lalu memanggil C. Validasi penting karena fungsi C pada umumnya tidak memeriksa argumennya, sehingga masukan tak sah yang lolos berhenti di dalam kode C tanpa pesan yang jelas. Berkas berikut memuat dua wrapper beserta pengujianannya.

julia wrapper.jl
using Test

"""
    luas_lingkaran(r::Real) -> Float64

Menghitung luas lingkaran berjari-jari `r` melalui fungsi C `luas_lingkaran`.
Jari-jari negatif memicu error `ArgumentError`.
"""
function luas_lingkaran(r::Real)
    r >= 0 || throw(ArgumentError("jari-jari tidak boleh negatif"))
    return ccall((:luas_lingkaran, "./geometri.so"), Cdouble, (Cdouble,), Float64(r))
end

"""
    hipotenusa(a::Real, b::Real) -> Float64

Menghitung sisi miring segitiga siku-siku dengan sisi tegak `a` dan `b`
melalui fungsi C `hipotenusa`.
"""
function hipotenusa(a::Real, b::Real)
    a >= 0 && b >= 0 || throw(ArgumentError("panjang sisi tidak boleh negatif"))
    return ccall((:hipotenusa, "./geometri.so"), Cdouble, (Cdouble, Cdouble), Float64(a), Float64(b))
end

hasil = redirect_stdout(devnull) do
    @testset "Wrapper geometri" begin
        @test luas_lingkaran(2.0) == Float64(pi) * 2.0^2
        @test hipotenusa(3.0, 4.0) == hypot(3.0, 4.0)
        @test_throws ArgumentError luas_lingkaran(-1.0)
    end
end
println("ujian lulus: ", hasil.n_passed)

Eksekusi berkas tersebut dilakukan setelah langkah kompilasi, karena wrapper membutuhkan geometri.so yang sudah ada di folder yang sama.

bash
# menjalankan pengujian wrapper terhadap pustaka hasil kompilasi
julia wrapper.jl
text Keluaran
ujian lulus: 3

Pengujian membandingkan hasil C dengan perhitungan Julia murni yang setara, yaitu Float64(pi) * 2.0^2 dan hypot(3.0, 4.0), sehingga kesalahan konversi tipe langsung terlihat. Ujian @test_throws memastikan validasi argumen bekerja sebelum nilai diteruskan ke C. Ringkasan @testset sengaja dialihkan ke devnull karena memuat kolom waktu, dan yang dicetak hanyalah jumlah ujian yang lulus.

Simbol yang tidak ada dan pembersihan#

Kesalahan ejaan simbol baru terasa saat panggilan dijalankan, bukan saat pustaka dimuat. Error yang muncul menyebut simbol yang dicari dan pustakanya, sehingga penyebabnya terbaca dari pesan tersebut tanpa alat tambahan.

julia Contoh: menangkap simbol yang tidak ada lalu membersihkan folder
try
    ccall((:volume_bola, "./geometri.so"), Cdouble, (Cdouble,), 1.0)
catch e
    println("error tertangkap: ", e.msg)
end
rm("geometri.so")
println("berkas geometri.so sudah dihapus: ", !isfile("geometri.so"))
text Keluaran
error tertangkap: could not load symbol "volume_bola":
./geometri.so: undefined symbol: volume_bola
berkas geometri.so sudah dihapus: true

Pesan lengkapnya aman ditampilkan karena jalur yang disebut bersifat relatif. Blok ini sekaligus menghapus hasil kompilasi agar folder contoh kembali hanya memuat kode sumber, sedangkan pada proyek sungguhan pustaka hasil kompilasi justru dipertahankan dan dimuat sekali di awal sesi.

Distribusi melalui JLL#

Wrapper buatan sendiri bergantung pada pustaka hasil kompilasi yang harus tersedia di mesin setiap pengguna, lengkap dengan perbedaan antarsistem operasi. Ekosistem Julia menyelesaikan masalah tersebut melalui package JLL, yaitu package yang hanya berisi pustaka biner hasil build silang untuk banyak platform. Pustaka biner itu dibangun oleh proyek BinaryBuilder dari resep pada repositori Yggdrasil. Package yang membutuhkan pustaka C cukup menjadikan JLL terkait sebagai dependensinya, kemudian memanggil pustakanya melalui ccall dengan jalur yang diberikan oleh artefak tersebut. Pola inilah yang dipakai banyak package ilmiah, sehingga pengguna tidak perlu memasang compiler ataupun pustaka sistem apa pun.

Kesalahan umum#

Kesalahan pertama adalah memanggil pustaka dengan nama tanpa garis miring, misalnya "geometri.so", yang berujung pada pesan could not load library karena pencariannya diarahkan ke jalur pustaka sistem. Perbaikannya adalah menulis ./geometri.so selama folder kerja masih sama dengan letak pustakanya.

Kesalahan kedua adalah menyusun tuple tipe yang tidak sesuai. Konversi yang tidak sah, misalnya nilai pecahan pada posisi Cint, melempar InexactError, sedangkan ketidakcocokan pada tipe pointer dapat berjalan tanpa pesan tetapi merusak memori. Tuple tipe sebaiknya disalin langsung dari dokumentasi fungsi C, kemudian divalidasi melalui pengujian seperti pada bagian wrapper.

Kesalahan ketiga adalah menyunting kode C tanpa mengompilasi ulang, sehingga geometri.so versi lama tetap dimuat dan perubahan tampak tidak berdampak. Kompilasi ulang dilakukan setiap kali kode sumber berubah, dan pustaka yang diperbarui terbaca pada sesi Julia yang baru.

Ringkasan#

  • Alur pembungkusan terdiri atas berkas .c, kompilasi gcc -shared -fPIC, lalu pemanggilan ccall dengan jalur pustaka.
  • Wrapper mengumpulkan nama simbol dan tuple tipe di satu fungsi Julia yang memvalidasi argumen dan mengonversi tipe secara eksplisit.
  • Pengujian membandingkan hasil C dengan perhitungan Julia murni yang setara, termasuk ujian @test_throws atas validasi argumennya.
  • Distribusi pustaka biner di ekosistem Julia memakai package JLL hasil build silang BinaryBuilder, sehingga pengguna tidak perlu mengompilasi sendiri.

Referensi#