Nim tidak berhenti pada pustaka standarnya sendiri. Melalui foreign function interface (FFI), bahasa ini dapat memanggil fungsi C secara langsung, sehingga pustaka C yang sudah matang dapat dipakai tanpa ditulis ulang. Topik ini membangun satu pustaka C kecil buatan sendiri untuk pembacaan sensor suhu, membuat binding-nya dengan importc, mencocokkan tipe di kedua sisi, dan menghubungkan callback dari C ke Nim. Seluruh proses dijalankan dengan gcc dan Nim 2.4.0, sehingga setiap keluaran yang ditampilkan dapat direproduksi ulang.
Pustaka C yang akan diikat#
Titik mulai interop adalah pustaka C itu sendiri. Contoh pada topik ini memakai pustaka buatan sendiri berupa pembacaan sensor suhu. Pustaka sistem sengaja dihindari karena sering kali tidak terpasang di lingkungan kerja pembaca, sehingga error penautan yang muncul justru menyembunyikan mekanisme interop yang sedang dipelajari. Pustaka ini terdiri atas dua berkas, yaitu sensor.h sebagai antarmuka dan sensor.c sebagai implementasinya.
Struktur SensorSuhu menyimpan identitas sensor, nilai kalibrasi, dan ambang batas, ditambah satu penunjuk fungsi (function pointer) bernama saat_ambang yang menampung callback. Fungsi sensor_mulai mengisi nilai awal, sensor_baca mengembalikan pembacaan yang sudah dikoreksi dengan kalibrasi, dan sensor_pasang_ambang mendaftarkan callback yang dipanggil C ketika pembacaan melampaui ambang. Alur callback dari C ke Nim inilah yang diuji pada bagian akhir.
// tidak-dijalankan
/* sensor.h — antarmuka pustaka sensor suhu sederhana */
#ifndef SENSOR_H
#define SENSOR_H
typedef struct {
int id;
double kalibrasi;
double ambang;
void (*saat_ambang)(int, double);
} SensorSuhu;
void sensor_mulai(SensorSuhu *s, int id, double ambang);
double sensor_baca(SensorSuhu *s, double mentah);
void sensor_pasang_ambang(SensorSuhu *s, void (*cb)(int, double));
#endif// tidak-dijalankan
/* sensor.c — implementasi pustaka sensor suhu sederhana */
#include "sensor.h"
void sensor_mulai(SensorSuhu *s, int id, double ambang) {
s->id = id;
s->kalibrasi = 0.0;
s->ambang = ambang;
s->saat_ambang = 0;
}
double sensor_baca(SensorSuhu *s, double mentah) {
double hasil = mentah + s->kalibrasi;
if (s->saat_ambang != 0 && hasil >= s->ambang) {
s->saat_ambang(s->id, hasil);
}
return hasil;
}
void sensor_pasang_ambang(SensorSuhu *s, void (*cb)(int, double)) {
s->saat_ambang = cb;
}Binding dengan importc#
Pragma .importc menyatakan bahwa sebuah proc diimplementasikan oleh fungsi C, bukan oleh kode Nim. Kompilator tidak menghasilkan isi untuk proc semacam ini, melainkan memancarkan pemanggilan langsung ke nama fungsi C yang ditulis pada pragmanya. Pragma .header melengkapinya dengan menyisipkan #include "sensor.h" pada berkas C hasil translasi, sehingga deklarasi di kedua sisi selalu konsisten.
Objek C SensorSuhu dideklarasikan ulang sebagai objek Nim yang diimpor. Hanya field yang diakses dari Nim yang perlu dideklarasikan, sehingga field penunjuk fungsi saat_ambang cukup diabaikan dan bentuk struct tetap diambil dari header C. Program berikut memakai seluruh antarmuka pustaka, termasuk pendaftaran callback sebelum pembacaan dilakukan.
## Binding Nim untuk pustaka sensor suhu (sensor.c dan sensor.h).
type
SensorSuhu {.importc: "SensorSuhu", header: "sensor.h".} = object
id: cint
kalibrasi: cdouble
ambang: cdouble
proc sensorMulai(s: ptr SensorSuhu, id: cint, ambang: cdouble) {.
importc: "sensor_mulai", header: "sensor.h".}
proc sensorBaca(s: ptr SensorSuhu, mentah: cdouble): cdouble {.
importc: "sensor_baca", header: "sensor.h".}
proc sensorPasangAmbang(s: ptr SensorSuhu,
cb: proc (id: cint, nilai: cdouble) {.cdecl.}) {.
importc: "sensor_pasang_ambang", header: "sensor.h".}
var bacaanAmbang: cdouble
proc saatAmbang(id: cint, nilai: cdouble) {.cdecl.} =
## Callback dari C: menyimpan bacaan yang melampaui ambang.
bacaanAmbang = nilai
var sensor: SensorSuhu
sensorMulai(addr sensor, 7, 30.0)
sensor.kalibrasi = -1.5
sensorPasangAmbang(addr sensor, saatAmbang)
echo "bacaan 1: ", sensorBaca(addr sensor, 24.0)
echo "bacaan 2: ", sensorBaca(addr sensor, 32.0)
if bacaanAmbang > 0.0:
echo "ambang terlampaui: ", bacaanAmbangProgram diakhiri dengan pengecekan bacaanAmbang untuk membuktikan bahwa callback benar-benar pernah dipanggil. Variabel global itu ditulis oleh C, bukan oleh kode utama Nim, sehingga berisinya menjadi bukti nyata arah panggilan balik tersebut. Apabila pendaftaran callback dihapus, baris ketiga keluaran tidak akan pernah muncul.
Pencocokan tipe antara C dan Nim#
Tipe di kedua sisi harus cocok pada tingkat application binary interface (ABI), bukan sekadar mirip dalam penulisan. Tipe bawaan Nim seperti int dan string tidak kompatibel dengan C, karena int Nim berukuran 64 bit pada platform umum sedangkan int C biasanya 32 bit, dan string Nim menyimpan panjang beserta kapasitas sedangkan char* C diakhiri karakter nol. Karena itu FFI menyediakan tipe jembatan yang ukurannya mengikuti sisi C.
| Tipe C | Tipe Nim | Catatan |
|---|---|---|
int | cint | ukurannya mengikuti int C, biasanya 32 bit |
double | cdouble | bilangan pecahan berlebar 64 bit |
char* | cstring | string C yang diakhiri karakter nol |
struct SensorSuhu * | ptr SensorSuhu | penunjuk ke object yang diimpor |
void (*)(int, double) | proc (cint, cdouble) {.cdecl.} | penunjuk fungsi dengan konvensi pemanggilan C |
Konversi antara tipe Nim dan tipe jembatan dilakukan secara eksplisit pada batas. Literal angka seperti 7 diturunkan kompilator ke cint secara otomatis pada pemanggilan, sedangkan $teks mengubah cstring menjadi string Nim apabila teks perlu diproses dengan operasi string biasa. Aturan praktisnya sederhana, yaitu tipe jembatan dipakai pada batas, dan tipe Nim dipakai di dalam kode Nim.
Kompilasi dan penautan#
Dua langkah menyatukan kedua dunia. Pertama, pustaka C dikompilasi dengan gcc menjadi berkas objek sensor.o. Kedua, nim c menerjemahkan program menjadi C lalu menyusunnya, dan --passL meneruskan sensor.o kepada linker agar simbol sensor_mulai dan kawan-kawannya ditemukan saat penautan.
# kompilasi pustaka C menjadi berkas objek
gcc -c sensor.c -o sensor.o
# kompilasi program Nim dan tautkan berkas objek pustaka
nim c --hints:off --passL:sensor.o sensor.nim
# jalankan program hasil penautan
./sensorbacaan 1: 22.5
bacaan 2: 30.5
ambang terlampaui: 30.5Bacaan pertama bernilai 22.5, yaitu 24.0 yang dikoreksi dengan kalibrasi kalibrasi = -1.5, sedangkan bacaan kedua bernilai 30.5 melampaui ambang 30.0 sehingga C memanggil callback Nim sebelum mengembalikan nilainya. Ketiga baris keluaran itu dihitung oleh fungsi C yang diikat, bukan oleh kode Nim, sehingga keluarannya sekaligus membuktikan bahwa penautan berhasil.
Callback dari C ke Nim#
Callback membalik arah pemanggilan, yaitu kode C yang memanggil proc Nim. Supaya pemanggilan itu sah, proc yang diteruskan harus berkonvensi pemanggilan (calling convention) yang sama dengan yang diharapkan C, dan pragma .cdecl. menegaskan hal itu. Tanpa pragma tersebut, program dapat dikompilasi lalu berperilaku salah, karena tanda tangan proc Nim tidak dijamin cocok dengan ekspektasi penunjuk fungsi C pada semua platform.
Isi callback sengaja dibuat seminimal mungkin, yaitu hanya menyalin nilai ke variabel global bacaanAmbang, dan hasilnya dibaca setelah sensorBaca kembali. Tanda tangan callback ditetapkan oleh C, sehingga status tambahan tidak dapat dioper sebagai parameter dan variabel tingkat modul menjadi tempat yang wajar untuk menyimpannya pada program berutas tunggal (single-threaded). Pencetakan besar atau pekerjaan berat sebaiknya juga ditunda sampai kendali kembali ke Nim.
Ringkasan#
.importcdan.headermengikat fungsi serta struct C menjadi proc dan objek Nim, dan hanya field yang diakses yang perlu dideklarasikan ulang.- Pencocokan tipe memakai tipe jembatan
cint,cdouble,cstring, danptr, karena tipe bawaan Nim tidak kompatibel dengan ABI C. - Kompilasi terdiri atas dua langkah, yaitu gcc untuk berkas objek pustaka dan
nim cdengan--passLuntuk menautkannya. - Callback dari C ke Nim wajib berpragma
{.cdecl.}dan isinya dijaga sederhana tanpa pengecualian yang lolos.
Referensi#
- Nim Compiler User Guide — Nim documentation
— Dokumentasi sakelar
--passCdan--passLyang dipakai saat penautan. - Nim Manual: Foreign function interface — Nim documentation
— Aturan
importc,header, tipe jembatan, dan pragmacdecl.