ccall

Memanggil fungsi pustaka C dan Fortran secara langsung dari Julia menggunakan ccall.

Daftar isi

Fungsi yang sudah ada dalam pustaka C dapat dipanggil langsung dari Julia melalui ccall, tanpa menulis ulang kodenya dan tanpa berkas perantara. Topik ini membahas sintaks ccall, pemanggilan fungsi matematika bawaan sistem, pemetaan tipe C ke tipe Julia, penanganan pemanggilan yang gagal, serta konvensi tambahan untuk kode Fortran. Setelah topik ini dipraktikkan, fungsi pustaka C yang tersedia pada sistem dapat dipanggil dari REPL maupun skrip Julia dengan tanda tangan (signature) yang eksplisit.

Sintaks dasar ccall#

ccall menerima empat bagian, yaitu identitas fungsi, tipe hasil kembalian, tuple tipe argumen, dan nilai setiap argumen. Bentuk lengkapnya adalah ccall((:nama_fungsi, "nama_pustaka"), TipeHasil, (TipeArg1, TipeArg2), arg1, arg2). Identitas fungsi berupa pasangan simbol dan nama pustaka, sedangkan tuple tipe menyatakan tanda tangan fungsi C yang dituju. Julia menyusun pemanggilan fungsi asing (foreign function call) dari identitas fungsi dan tuple tipe pada saat program berjalan.

Apabila identitas fungsi hanya berupa simbol tanpa nama pustaka, Julia mencari simbol tersebut pada pustaka yang sudah termuat di dalam proses Julia, termasuk libc dan pustaka matematika libm. Bentuk singkat ini cukup untuk fungsi-fungsi standar C, sebagaimana pada contoh berikut.

julia Contoh: memanggil sqrt dan pow dari libm
println(ccall(:sqrt, Cdouble, (Cdouble,), 2.0))
println(ccall(:pow, Cdouble, (Cdouble, Cdouble), 2.0, 10.0))
text Keluaran
1.4142135623730951
1024.0

Nilai pertama identik dengan hasil sqrt(2.0) milik Julia karena fungsi sqrt pada pustaka standar juga bertumpu pada libm. Perbedaannya terletak pada tingkat pemanggilan, yaitu ccall beroperasi pada tingkat C, sehingga tipe setiap argumen harus dinyatakan eksplisit.

Pola ini berbeda dengan banyak bahasa lain yang menuntut deklarasi fungsi asing dibuat lebih dahulu pada berkas binding tersendiri, yaitu berkas yang menjembatani dua bahasa. Pada ccall, deklarasi melekat pada setiap titik panggilan dalam bentuk tuple tipe, sehingga satu fungsi C dapat dipanggil begitu saja. Untuk pustaka besar yang dipanggil di banyak tempat, deklarasi terpusat dalam wrapper tetap lebih nyaman daripada mengulang tuple tipe di setiap titik panggilan.

Pemetaan tipe C ke Julia#

Tipe pada tuple argumen ditulis dengan tipe Julia yang mewakili tipe C. Pemetaan ini menjamin lebar bit dan susunan memori argumen sama persis dengan yang dituntut fungsi C. Tabel berikut memuat tipe yang paling sering dipakai.

Tipe CTipe JuliaKeterangan
intCintbilangan bulat bertanda, 32 bit pada platform umum
doubleCdoublebilangan pecahan presisi ganda
floatCfloatbilangan pecahan presisi tunggal
size_tCsize_tbilangan bulat tak bertanda untuk ukuran dan indeks
charCcharkarakter C selebar satu byte
char*Cstringteks C yang diakhiri karakter nol
double*Ptr{Cdouble}pointer menuju satu nilai atau deret Cdouble
voidCvoidhasil kembalian yang tidak dipakai

Cstring menuntut teks yang tidak bernilai NULL dan diakhiri karakter nol, sedangkan Ptr{Cchar} adalah bentuk umum tanpa jaminan tersebut. Contoh berikut memanggil abs dari libc dengan Cint, kemudian strlen dengan Cstring dan hasil kembalian Csize_t.

julia Contoh: pemetaan Cint, Cstring, dan Csize_t
println(ccall(:abs, Cint, (Cint,), -7))
println(ccall(:strlen, Csize_t, (Cstring,), "halo dunia"))
text Keluaran
7
10

String Julia dikonversi otomatis menjadi Cstring pada argumen, sehingga tidak ada penyalinan memori yang perlu ditulis. Hasil strlen bertipe Csize_t, tetapi tetap tampil seperti bilangan biasa ketika dicetak.

Pointer dan nilai kembalian samping#

Sebagian fungsi C melaporkan nilai tambahan melalui pointer, yaitu argumen yang memuat alamat memori untuk menampung hasil. Pada tuple tipe, posisi itu ditulis Ptr{T}, sedangkan nilai Julia dikirim dalam bentuk Ref{T} yang menyimpan alamat satu nilai. Contohnya modf dari libm yang mengembalikan bagian pecahan sebuah bilangan dan menulis bagian bulatnya melalui pointer.

julia Contoh: menampung hasil lewat pointer
bagian_bulat = Ref{Cdouble}(0.0)
pecahan = ccall(:modf, Cdouble, (Cdouble, Ptr{Cdouble}), 3.75, bagian_bulat)
println("bagian pecahan: ", pecahan)
println("bagian bulat: ", bagian_bulat[])
text Keluaran
bagian pecahan: 0.75
bagian bulat: 3.0

Isi Ref dibaca dengan kurung siku kosong setelah panggilan selesai. Pola ini sering ditemukan pada pustaka C karena banyak fungsi melaporkan kode error atau ukuran data melalui argumen pointer semacam ini.

Kesalahan saat simbol tidak ada#

Simbol yang tidak ditemukan tidak membuat program gagal saat pustakanya dimuat, melainkan saat panggilan tersebut dijalankan. Julia melempar error ErrorException yang menyebut simbol dan pustakanya, sehingga penyebabnya terbaca langsung dari blok try-catch berikut.

julia Contoh: menangkap error simbol yang tidak ada
try
    ccall((:simbol_tidak_ada, "libc.so.6"), Cint, (Cint,), 1)
catch e
    println("error tertangkap: ", split(e.msg, "\n")[1])
end
text Keluaran
error tertangkap: could not load symbol "simbol_tidak_ada":

Pesan lengkapnya terdiri atas dua baris, yaitu baris pertama yang menyebut simbol yang gagal dimuat dan baris kedua yang memuat jalur pustaka pada mesin. Contoh di atas menampilkan baris pertama saja karena baris itulah yang berlaku umum, sedangkan jalur pustaka berbeda antarsistem. Ejaan simbol dan nama pustaka dapat diperiksa langsung dari pesan ini.

Konvensi Fortran#

Kode Fortran dipanggil dengan ccall yang sama, tetapi melalui dua konvensi tambahan. Pertama, kompiler Fortran pada umumnya menambahkan garis bawah di akhir nama simbol, sehingga fungsi energi_kinetik pada kode sumber dipanggil sebagai energi_kinetik_. Kedua, seluruh argumen Fortran diteruskan dengan referensi, sehingga tiap posisi argumen ditulis Ref{Cdouble}, dan array disusun dalam urutan kolom (column-major) yang sama dengan tata letak array Julia.

julia Contoh: bentuk pemanggilan pustaka Fortran
# tidak-dijalankan
# gfortran tidak tersedia di sandbox, sehingga contoh ini hanya menampilkan bentuknya.
# Kode sumber statika.f yang menyertai contoh kira-kira seperti berikut:
#     double precision function energi_kinetik(massa, kecepatan)
#         implicit none
#         double precision massa, kecepatan
#         energi_kinetik = 0.5d0 * massa * kecepatan**2
#     end function energi_kinetik
# Kompilasinya: gfortran -shared -fPIC -o statika.so statika.f
massa = 2.0
kecepatan = 3.0
energi = ccall(
    (:energi_kinetik_, "statika.so"),
    Cdouble,
    (Ref{Cdouble}, Ref{Cdouble}),
    massa, kecepatan,
)
println("energi kinetik: ", energi)

Menurut rumus Ek=12mv2E_k = \tfrac{1}{2} m v^2, panggilan di atas menghasilkan 9.0 untuk massa 2.0 dan kecepatan 3.0. Pada pustaka ilmiah nyata, konvensi inilah yang dipakai pustaka aljabar linear seperti OpenBLAS di balik operasi matriks Julia.

Ringkasan#

  • ccall((:fungsi, "pustaka"), TipeHasil, (TipeArg,), arg) menyatakan seluruh deklarasi pemanggilan C pada titik panggilannya, dan bentuk simbol tunggal mencari pustaka yang sudah termuat di proses.
  • Tipe C dipetakan ke Cint, Cdouble, Csize_t, Cstring, dan Ptr{T}, sedangkan argumen pointer dikirim melalui Ref{T} dan dibaca kembali dengan kurung siku.
  • Simbol yang tidak ada melempar ErrorException saat panggilan dijalankan dan dapat ditangkap dengan try-catch.
  • Pemanggilan Fortran memakai ccall yang sama dengan nama simbol bergaris bawah di akhir dan argumen yang diteruskan dengan referensi.

Referensi#