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.
println(ccall(:sqrt, Cdouble, (Cdouble,), 2.0))
println(ccall(:pow, Cdouble, (Cdouble, Cdouble), 2.0, 10.0))1.4142135623730951
1024.0Nilai 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 C | Tipe Julia | Keterangan |
|---|---|---|
int | Cint | bilangan bulat bertanda, 32 bit pada platform umum |
double | Cdouble | bilangan pecahan presisi ganda |
float | Cfloat | bilangan pecahan presisi tunggal |
size_t | Csize_t | bilangan bulat tak bertanda untuk ukuran dan indeks |
char | Cchar | karakter C selebar satu byte |
char* | Cstring | teks C yang diakhiri karakter nol |
double* | Ptr{Cdouble} | pointer menuju satu nilai atau deret Cdouble |
void | Cvoid | hasil 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.
println(ccall(:abs, Cint, (Cint,), -7))
println(ccall(:strlen, Csize_t, (Cstring,), "halo dunia"))7
10String 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.
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[])bagian pecahan: 0.75
bagian bulat: 3.0Isi 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.
try
ccall((:simbol_tidak_ada, "libc.so.6"), Cint, (Cint,), 1)
catch e
println("error tertangkap: ", split(e.msg, "\n")[1])
enderror 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.
# 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 , 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, danPtr{T}, sedangkan argumen pointer dikirim melaluiRef{T}dan dibaca kembali dengan kurung siku. - Simbol yang tidak ada melempar
ErrorExceptionsaat panggilan dijalankan dan dapat ditangkap dengan try-catch. - Pemanggilan Fortran memakai
ccallyang sama dengan nama simbol bergaris bawah di akhir dan argumen yang diteruskan dengan referensi.
Referensi#
- Calling C and Fortran Code — Julia Documentation
— Panduan resmi sintaks
ccall, pemetaan tipe C, dan konvensi pemanggilan Fortran.