Odin dapat memanggil fungsi dari pustaka C secara langsung melalui sistem foreign, tanpa membaca satu pun berkas header C. Topik ini membahas sepasang deklarasi yang membentuk mekanisme tersebut, yaitu foreign import untuk menautkan pustaka biner dan blok foreign untuk mendeklarasikan fungsi di dalamnya. Tipe C dari paket core:c juga dibahas karena menjaga kesesuaian nilai yang melintasi batas bahasa. Praktiknya dijalankan utuh: pustaka C kecil dibangun dengan gcc, program dua berkas memanggil ketiga fungsinya, dan keluaran aslinya ditempel setelah kode. Bagian akhir memperlihatkan penautan ke pustaka sistem melalui awalan system: beserta kesalahan yang lazim terjadi pada topik ini.
Pustaka C tanpa berkas header#
Dalam C, compiler mengetahui tanda tangan fungsi yang dipanggil dari deklarasi prototipe, dan prototipe tersebut lazimnya berada di berkas header yang dibaca melalui #include. Tanpa deklarasi itu, compiler C tidak mengetahui jumlah parameter, tipe parameternya, maupun tipe kembalian fungsi yang akan ditautkan. Odin mengambil jalan berbeda karena compiler-nya tidak membaca berkas header C sama sekali. Peran prototipe digantikan deklarasi yang ditulis langsung dalam sintaks Odin, sehingga pustaka cukup disediakan dalam bentuk biner hasil kompilasi.
Cara ini mengubah kebutuhan integrasi secara mendasar. Berkas header tidak perlu dikirim bersama pustaka, perintah #include tidak perlu ditulis, dan tidak ada tahap penerjemahan header ke bahasa lain. Yang diperlukan hanyalah berkas pustaka, misalnya libtambah.so hasil kompilasi gcc, ditambah daftar tanda tangan fungsi yang diketik ulang di sisi Odin. Konsekuensinya, compiler Odin memercayai deklarasi tersebut sepenuhnya dan tidak memeriksa kesesuaiannya dengan kode C yang sesungguhnya, sehingga kebenaran tanda tangan menjadi tanggung jawab penulis deklarasinya. Rincian perilaku di sisi C dibahas lebih dalam pada topik Detail ABI.
Deklarasi foreign import dan blok foreign#
Penautan pustaka dan pendeklarasian fungsinya dinyatakan oleh sepasang deklarasi yang saling berpasangan. Deklarasi foreign import memiliki semantik yang sama dengan import biasa, tetapi yang diimpor berupa pustaka biner, dan nama yang diberikannya dipakai blok foreign untuk mengaitkan entitas. Bentuk kurung kurawal digunakan pada topik ini karena bentuk tersebut menerima lebih dari satu pustaka sekaligus, sedangkan bentuk tunggal menuliskan nama berkas sebagai string setelah nama foreign import. Jalur pustakanya dapat ditulis relatif terhadap berkas, absolut, atau berawalan system:.
// tidak-dijalankan
// menautkan pustaka biner dan memberinya nama tambah_lib
foreign import tambah_lib {
"libtambah.so",
}
// mendeklarasikan satu fungsi C pada pustaka tersebut
foreign tambah_lib {
jumlah :: proc "c" (a, b: c.int) -> c.int ---
}Nama tambah_lib pada kedua deklarasi harus sama karena blok foreign merujuk nama foreign import untuk mengetahui pustaka tempat fungsi-fungsinya berada. Di dalam blok, setiap baris mendeklarasikan satu fungsi bertanda proc "c", yaitu konvensi panggilan (calling convention) cdecl yang dipakai kebanyakan pustaka C. Dokumentasi resmi menyatakan konvensi tersebut sebagai nilai bawaan deklarasi foreign, sehingga menuliskannya secara eksplisit tidak wajib. Simbol --- di akhir menandai proc literal tanpa tubuh, dan tanda itu wajib ada agar deklarasi tidak disalahpahami compiler sebagai tipe proc.
Tanpa tubuh, deklarasi hanya menyampaikan nama, tipe parameter, dan tipe kembalian kepada compiler. Informasi itu dipakai untuk memeriksa setiap pemanggilan di sisi Odin, kemudian linker mencari simbol bernama sama pada pustaka ketika program ditautkan. Apabila simbol tidak ditemukan, error muncul bukan pada saat deklarasi diketik, melainkan pada tahap penautan, sehingga program tidak terbentuk.
Tipe C dari core:c#
Tipe parameter dan kembalian pada deklarasi foreign tidak ditulis asal karena nilainya melintasi batas bahasa dan diterima sisi C menurut kontrak tingkat biner. Kontrak itu disebut ABI (application binary interface), yaitu kesepakatan tentang ukuran tipe dan cara pemanggilan fungsi antarkode yang dikompilasi terpisah. Paket core:c menyediakan alias tipe yang meniru tipe C per platform, dan alias tersebut dipakai setelah import "core:c" ditambahkan. Ukuran sebagian tipe C memang berubah menurut platform, misalnya c.long yang lebarnya berbeda antara Windows dan Linux 64-bit, sehingga alias yang tepat menjaga ukuran dan tata letak nilai tetap sesuai.
| Tipe pada C | Alias di core:c | Padanan di Odin (Linux amd64) |
|---|---|---|
int | c.int | i32 |
float | c.float | f32 |
double | c.double | f64 |
char | c.char | u8 |
long long | c.longlong | i64 |
Selain lima alias pada tabel, paket yang sama menyediakan c.long, c.size_t, dan alias lebar tetap seperti c.int64_t, semuanya terbaca langsung pada sumber core:c di folder instalasi compiler. Nilai literal tanpa tipe menyesuaikan diri dengan parameter tujuannya, sehingga pemanggilan pangkat(1.5, 4) pada program berikut sah tanpa konversi eksplisit ke c.float dan c.int. Nilai yang sudah bertipe Odin tetap ditulis dengan konversi eksplisit, misalnya c.int(x) untuk variabel bertipe int.
Proyek dua berkas: pustaka dan program#
Contoh utuh topik ini terdiri atas dua berkas sumber yang saling menopang. Berkas C memuat tiga fungsi bilangan sederhana, sedangkan program Odin mengimpor pustakanya dan memanggil setiap fungsi satu kali. Struktur foldernya seperti berikut, dengan satu berkas pustaka sebagai hasil build yang tidak ditulis manual.
blok-foreign/
├── tambah.c ← sumber C tiga fungsi
├── main.odin ← program Odin yang memanggilnya
├── libtambah.so ← hasil gcc, tidak ditulis manual
└── sistem-m/
└── main.odin ← program dengan penautan system:// tidak-dijalankan
// pustaka bersama C: dibangun dengan gcc -shared -fPIC, bukan dijalankan langsung
int jumlah(int a, int b) {
return a + b;
}
int kali(int a, int b) {
return a * b;
}
float pangkat(float dasar, int pangkat_n) {
float hasil = 1.0f;
for (int i = 0; i < pangkat_n; i++) {
hasil *= dasar;
}
return hasil;
}Ketiga fungsi sengaja ditulis sederhana agar keluarannya mudah dihitung ulang. jumlah dan kali menghitung hasil dari dua bilangan bulat bertipe int, sedangkan pangkat mengalikan basis bertipe float sebanyak pangkat_n kali. Berkas tersebut dikompilasi menjadi pustaka bersama (shared library) dengan satu perintah gcc. Opsi -shared memerintahkan gcc menghasilkan pustaka dinamis, sedangkan -fPIC membuat kode position-independent sehingga pustakanya dapat dimuat pada alamat memori mana pun.
# bangun pustaka bersama dari tambah.c di folder proyek
gcc -shared -fPIC tambah.c -o libtambah.soProgram Odin berikut mendeklarasikan ketiga fungsi tersebut lalu memanggilnya di main. Deklarasi foreign-nya menautkan libtambah.so yang baru dihasilkan, dan pemanggilan fungsi C ditulis seperti pemanggilan proc biasa karena compiler sudah mengetahui tipe parameternya dari deklarasi.
package main
import "core:c"
import "core:fmt"
// menautkan pustaka bersama hasil build gcc dari tambah.c
foreign import tambah_lib {
"libtambah.so",
}
// deklarasi tiga fungsi C tanpa tubuh, setiap baris diakhiri ---
foreign tambah_lib {
jumlah :: proc "c" (a, b: c.int) -> c.int ---
kali :: proc "c" (a, b: c.int) -> c.int ---
pangkat :: proc "c" (dasar: c.float, pangkat_n: c.int) -> c.float ---
}
main :: proc() {
fmt.printfln("jumlah(12, 30) = %v", jumlah(12, 30))
fmt.printfln("kali(6, 7) = %v", kali(6, 7))
fmt.printfln("pangkat(1.5, 4) = %v", pangkat(1.5, 4))
}# sumberkan lingkungan sandbox jalur odin bila belum tersedia
[ -f /home/kal/my-project/tools/env.sh ] && source /home/kal/my-project/tools/env.sh
# jalankan program dari folder proyek
odin run .jumlah(12, 30) = 42
kali(6, 7) = 42
pangkat(1.5, 4) = 5.0625Keluaran memperlihatkan ketiga pemanggilan berhasil melewati batas bahasa. Nilai 42 muncul dua kali, dari jumlah(12, 30) dan kali(6, 7), sedangkan pangkat(1.5, 4) menghasilkan 5.0625 sesuai dengan 1.5 dipangkatkan empat kali. Berkas pustaka terlibat pada dua momen, yaitu ketika linker memasangkannya saat program dibangun dan ketika sistem operasi memuatnya ke memori saat program dijalankan. Karena jalurnya relatif terhadap folder proyek, libtambah.so perlu tetap berada di tempatnya pada kedua momen tersebut.
Penautan pustaka sistem dengan system:#
Awalan system: pada jalur foreign import memerintahkan linker mencari pustaka pada jalur pustaka sistem, sehingga pustaka yang sudah terpasang di mesin dapat ditautkan tanpa berkas .so di folder proyek. Contoh yang lazim adalah system:m untuk menautkan libm, yaitu pustaka matematika C. Deklarasinya persis sama dengan proyek sebelumnya, hanya nama pustakanya yang diganti, dan tidak ada langkah build tambahan sebelum program dijalankan.
Tanda tangan kedua fungsi mengikuti prototipe libm yang sesungguhnya, yaitu double sqrt(double) dan double pow(double, double), sehingga parameternya ditulis c.double. Programnya diberi nama paket sistem_m dan ditempatkan pada subfolder sistem-m di dalam folder proyek, karena satu paket Odin hanya boleh memuat satu proc main. Perintah eksekusinya menyebut folder subprogram tersebut dari folder proyek.
package sistem_m
import "core:c"
import "core:fmt"
// menautkan libm, pustaka matematika C yang sudah terpasang di sistem
foreign import m {
"system:m",
}
foreign m {
sqrt :: proc "c" (x: c.double) -> c.double ---
pow :: proc "c" (dasar: c.double, pangkat_n: c.double) -> c.double ---
}
main :: proc() {
fmt.printfln("sqrt(144) = %v", sqrt(144))
fmt.printfln("pow(2, 10) = %v", pow(2, 10))
}# jalankan program penautan sistem dari folder proyek
odin run sistem-msqrt(144) = 12
pow(2, 10) = 1024Keluarannya sederhana tetapi bermakna penting. Nilai 12 dari sqrt(144) dan 1024 dari pow(2, 10) dihitung oleh kode C pihak lain, yaitu implementasi libm di mesin ini, tanpa satu pun berkas header maupun berkas binding yang ditulis sendiri. Pola yang sama digunakan berkas binding di koleksi vendor ketika menautkan pustaka sistem seperti pthread dan X11, sehingga pemahaman system: langsung berguna ketika membaca binding pihak ketiga.
Kesalahan umum#
Kesalahan pertama adalah memakai alias c.int tanpa mengimpor paketnya. Deklarasi yang menyebut c.int tanpa import "core:c" ditolak compiler dengan pesan Undeclared name: c pada setiap pemakaiannya. Perbaikannya berupa penambahan baris import tersebut di bagian atas berkas, sebagaimana kedua program pada topik ini.
Kesalahan kedua adalah menjalankan program ketika berkas pustakanya belum ada. Proyek lokal yang menautkan libtambah.so gagal pada tahap penautan dengan pesan linker cannot find -l:...libtambah.so apabila perintah gcc belum dijalankan atau berkasnya terhapus. Perbaikannya berupa pembangunan ulang pustaka sebelum odin run, dan blok perintah pada topik ini menempatkan gcc terlebih dahulu karena alasan itu.
Kesalahan ketiga adalah menyalin tanda tangan secara tidak persis. Deklarasi jumlah yang kembaliannya ditulis c.float padahal fungsi C mengembalikan int tetap lolos kompilasi karena compiler tidak membaca kode C. Namun, pemanggilannya menghasilkan nilai kacau karena register kembalian dibaca dengan tipe yang salah. Perbaikannya berupa penyalinan prototipe C kata demi kata, diikuti uji cepat dengan nilai yang mudah dihitung, misalnya 12 dan 30 yang jumlahnya 42.
Ringkasan#
foreign import nama { "pustaka.so" }menautkan pustaka biner tanpa berkas header, sedangkan blokforeign nama { ... }mendeklarasikan fungsi-fungsinya dengan simbol---menggantikan tubuh proc.- Tipe C seperti
c.int,c.float,c.double,c.char, danc.longlongdari paketcore:cmenjaga ukuran dan tata letak nilai tetap sesuai dengan sisi C per platform. - Pustaka sendiri dibangun dengan
gcc -shared -fPIC sumber.c -o libnama.so, sedangkan awalansystem:menautkan pustaka yang sudah terpasang, misalnyasystem:muntuk libm. - Compiler tidak memeriksa kesesuaian deklarasi dengan isi pustaka, sehingga prototipe C disalin persis dan diuji dengan nilai yang mudah dihitung ulang.
Referensi#
- Overview — Odin Documentation — Bagian Foreign system menjelaskan foreign import, blok foreign, konvensi panggilan c, simbol —, dan jalur system:.
- Paket core:c — Referensi paket Odin — Dokumentasi resmi alias tipe C seperti c.int, c.float, dan c.double yang dipakai pada deklarasi foreign.