Antarmuka C

Antarmuka ke C dengan pragma Import dan Export serta Interfaces.C.

Daftar isi

Program Ada jarang berdiri sendiri. Sebagian besar lingkungan target menyediakan pustaka yang ditulis dalam C, mulai dari pembungkus driver perangkat hingga pustaka kompresi, sehingga kemampuan memanggil fungsi C dan menyediakan fungsi untuk C merupakan kebutuhan praktis pada pemrograman sistem. Topik ini membahas dua pragma kunci untuk keperluan itu, yaitu pragma Import dan pragma Export, bersama package Interfaces.C yang menyediakan tipe yang cocok dengan tipe C. Setelah topik ini diselesaikan, program gabungan Ada dan C dapat dikompilasi serta dijalankan dengan pemanggilan dari kedua arah.

Pragma Import untuk memanggil fungsi C#

Pragma Import menyatakan bahwa implementasi sebuah subprogram Ada ditulis dalam bahasa lain, sehingga badan subprogram tersebut tidak ditulis dalam Ada. Kompilator tidak menghasilkan kode untuk subprogram itu, dan simbolnya diselesaikan ketika program penghubung (linker) menyatukan berkas-berkas objek. Konvensi bahasa ditetapkan pada parameter pertama pragma, yaitu C untuk antarmuka yang kompatibel dengan C. Nama simbol yang benar-benar dicari linker dapat ditetapkan pada parameter ketiga, sedangkan tanpa parameter itu kompilator menggunakan nama subprogram Ada apa adanya.

Contoh berikut memanggil dua fungsi C dari prosedur utama Ada. Berkas pembantu.c berisi fungsi aritmetika sederhana, sedangkan panggil_c.adb mendeklarasikan kedua fungsi tersebut dengan tipe dari package Interfaces.C.

c pembantu.c
// tidak-dijalankan (dibangun bersama program Ada melalui blok bash)
#include <stddef.h>
#include <string.h>

int kuadrat (int n)
{
   return n * n;
}

double rata_dua (double a, double b)
{
   return (a + b) / 2.0;
}

size_t panjang (const char *s)
{
   return strlen (s);
}
ada panggil_c.adb
with Ada.Text_IO;
with Ada.Strings.Fixed;
with Ada.Strings;
with Interfaces.C;

procedure Panggil_C is
   function Kuadrat (N : Interfaces.C.int) return Interfaces.C.int;
   pragma Import (C, Kuadrat, "kuadrat");

   function Rata_Dua (A, B : Interfaces.C.double) return Interfaces.C.double;
   pragma Import (C, Rata_Dua, "rata_dua");

   function Gambar (Nilai : Interfaces.C.double) return String is
   begin
      return Ada.Strings.Fixed.Trim (Interfaces.C.double'Image (Nilai),
         Ada.Strings.Both);
   end Gambar;
begin
   Ada.Text_IO.Put_Line ("kuadrat 12 = " & Interfaces.C.int'Image (Kuadrat (12)));
   Ada.Text_IO.Put_Line ("rata dua 7.0 dan 12.0 = " & Gambar (Rata_Dua (7.0, 12.0)));
end Panggil_C;
bash
# kompilasi berkas C menjadi berkas objek
gcc -c pembantu.c
# kompilasi program Ada dan hubungkan dengan berkas objek C
gnatmake -q panggil_c.adb -largs pembantu.o
# jalankan program
./panggil_c
text Keluaran
kuadrat 12 =  144
rata dua 7.0 dan 12.0 = 9.50000000000000E+00

Opsi -largs meneruskan argumen yang ditulis setelahnya kepada tahap penghubungan, sehingga berkas objek pembantu.o ikut disatukan. Alur kompilasinya sederhana, yaitu berkas C dikompilasi lebih dahulu dengan gcc, kemudian gnatmake mengompilasi berkas Ada dan menghubungkan keduanya. Nama fungsi pada pragma Import harus sama persis dengan nama fungsi di C, karena linker hanya mencocokkan teks nama tanpa mengetahui tipe parameternya.

Tipe data pada batas antarmuka#

Tipe pada deklarasi Ada harus cocok dengan tipe pada prototipe C agar data dibaca dengan benar. Package Interfaces.C menyediakan tipe yang ukuran dan maknanya sudah diselaraskan dengan C, sehingga pemilihan tipe pada batas antarmuka tidak perlu didasarkan pada tebakan. Tabel berikut merangkum pasangan yang paling sering digunakan.

Tipe CTipe pada Interfaces.C
intInterfaces.C.int
doubleInterfaces.C.double
size_tInterfaces.C.size_t
char* (string)Interfaces.C.char_array

String merupakan kasus khusus karena C memperlakukannya sebagai deretan karakter yang diakhiri karakter nol. Fungsi To_C mengubah tipe String Ada menjadi char_array yang siap dikirim, sedangkan fungsi To_Ada mengubah char_array tersebut kembali menjadi String. Contoh berikut menggunakan fungsi panjang pada berkas C yang sudah ada untuk menghitung panjang sebuah teks.

ada uji_string.adb
with Ada.Text_IO;
with Interfaces.C;
use Interfaces.C;

procedure Uji_String is
   function Panjang (S : char_array) return size_t;
   pragma Import (C, Panjang, "panjang");

   Teks : constant char_array := To_C ("pemrograman sistem");
begin
   Ada.Text_IO.Put_Line ("panjang teks = " & size_t'Image (Panjang (Teks)));
end Uji_String;
bash
# kompilasi ulang berkas C yang sudah tersedia
gcc -c pembantu.c
# kompilasi program dan hubungkan dengan berkas objek C
gnatmake -q uji_string.adb -largs pembantu.o
# jalankan program
./uji_string
text Keluaran
panjang teks =  18

Nilai 18 sesuai dengan jumlah karakter pada teks pemrograman sistem, dan fungsi C menghitungnya dengan strlen dari pustaka standar tanpa mengetahui bahwa pemanggilnya ditulis dalam Ada. Pada batas antarmuka, kedua bahasa melihat memori yang sama, sehingga kesesuaian tipe menjadi syarat utama pertukaran data yang benar.

Pragma Export untuk dipanggil dari C#

Arah sebaliknya menggunakan pragma Export. Subprogram Ada yang diekspor diberi nama simbol C, sehingga fungsi C dapat memanggilnya seolah-olah fungsi tersebut ditulis dalam C. Kompilator tetap melakukan pemeriksaan tipe di sisi Ada, sedangkan sisi C hanya melihat prototipe biasa. Deklarasi yang diekspor diletakkan pada spesifikasi package agar sisi Ada dan sisi C merujuk satu deklarasi yang sama.

ada utilitas.ads
package Utilitas is
   procedure Tambah (A : Integer; B : Integer; Hasil : out Integer);
   pragma Export (C, Tambah, "tambah");
end Utilitas;
ada utilitas.adb
package body Utilitas is
   procedure Tambah (A : Integer; B : Integer; Hasil : out Integer) is
   begin
      Hasil := A + B;
   end Tambah;
end Utilitas;
c utama.c
// tidak-dijalankan (dibangun bersama unit Ada melalui blok bash)
#include <stdio.h>

extern void tambah (int a, int b, int *hasil);

int main (void)
{
   int hasil;
   tambah (12, 30, &hasil);
   printf ("tambah (12, 30) = %d\n", hasil);
   return 0;
}
bash
# kompilasi program utama C
gcc -c utama.c
# kompilasi package Ada
gnatmake -q utilitas.adb
# ikat seluruh unit Ada tanpa program utama Ada
gnatbind -n utilitas.ali
# hubungkan berkas objek C dengan hasil ikatan Ada
gnatlink utilitas.ali utama.o -o panggil_ada
# jalankan program hasil penghubungan
./panggil_ada
text Keluaran
tambah (12, 30) = 42

Ketika program utama ditulis dalam C, tahap ikatan (bind) dan penghubungan dilakukan secara terpisah karena gnatmake menganggap program utama selalu berbentuk prosedur Ada. Opsi -n pada gnatbind memerintahkan ikatan tanpa program utama Ada, kemudian gnatlink menyatukan berkas objek C dengan hasil ikatan tersebut. Parameter mode out pada prosedur Ada dipetakan menjadi penunjuk int* di sisi C, sesuai konvensi parameter keluaran.

Kesalahan umum#

Lima kekeliruan berikut paling sering muncul saat antarmuka C dipraktikkan.

  • Lupa mengompilasi berkas C lebih dahulu. gnatmake tidak mengetahui berkas C mana yang dibutuhkan, sehingga tahap penghubungan berakhir dengan pesan undefined reference. Kompilasi berkas C dengan gcc terlebih dahulu, lalu berikan berkas objeknya melalui -largs.
  • Nama simbol yang tidak cocok. Linker mencocokkan teks nama secara persis, sehingga kuadrat dan Kuadrat dianggap dua simbol yang berbeda. Samakan nama simbol pada pragma dengan deklarasi di sisi C.
  • Menggunakan tipe Ada biasa pada batas antarmuka. Tipe Integer pada host tidak dijamin sama dengan int C pada target lain. Gunakan tipe dari Interfaces.C pada setiap parameter dan hasil antarmuka.
  • Mengirim tipe String langsung ke fungsi C. Tipe String Ada menyimpan informasi batasnya sendiri dan tidak diakhiri karakter nol. Ubah terlebih dahulu dengan To_C menjadi char_array.
  • Menyandingkan subprogram Ada dengan fungsi C++ tanpa penyesuaian. Kompilator C++ mengubah nama fungsi menjadi simbol yang lebih panjang, sehingga nama pada pragma tidak pernah ditemukan linker. Tandai deklarasi C++ dengan extern "C" atau batasi antarmuka pada berkas C murni.

Ringkasan#

  • pragma Import menyatakan subprogram yang implementasinya ditulis dalam C, sedangkan pragma Export mengekspos subprogram Ada dengan nama simbol C.
  • Package Interfaces.C menyediakan tipe yang cocok dengan C, bersama fungsi To_C dan To_Ada untuk string.
  • Program gabungan dihubungkan dengan opsi -largs pada gnatmake, atau dengan gnatbind -n dan gnatlink apabila program utamanya ditulis dalam C.
  • Kompilator tidak memeriksa kesesuaian dengan prototipe C, sehingga kesesuaian tipe menjadi tanggung jawab pemrogram.

Referensi#