Prosedur bind(c)

Memanggil fungsi C dari Fortran melalui bind(c) dan meneruskan callback C ke prosedur Fortran.

Daftar isi

Atribut bind(c) menandai sebuah antarmuka atau prosedur agar mengikuti konvensi pemanggilan C, yaitu cara kompiler menata argumen dan nama simbol pada berkas hasil kompilasi. Topik ini membahas pemanggilan fungsi C dari Fortran melalui blok interface beratribut bind(c), kompilasi campuran dua bahasa menjadi satu program, serta pemanggilan balik (callback) ketika kode C menerima fungsi Fortran sebagai argumennya. Satu program campuran dan satu program pemanggilan balik disertakan, keduanya dikompilasi dan dijalankan, sehingga kontrak antar bahasa terlihat dari keluaran aslinya.

Deklarasi fungsi C dalam antarmuka Fortran#

Tanpa perlakuan khusus, gfortran menerjemahkan nama prosedur Fortran menjadi simbol C yang diberi tambahan garis bawah dan menata argumennya menurut konvensi Fortran, sehingga tanda tangan kedua bahasa tidak pernah bertemu. Blok interface dengan atribut bind(c, name="...") memutus kebuntuan itu. Kompiler memanggil simbol C persis dengan nama yang diberikan pada atribut name, dan setiap argumen dinyatakan dengan tipe dari iso_c_binding beserta arah datanya. Atribut value membuat argumen scalar diteruskan C secara nilai, sedangkan array dinyatakan sebagai bentuk diasumsikan (assumed-size) seperti v(*) karena C hanya menerima alamatnya.

Program berikut memanggil dua fungsi C, yaitu rata_rata yang mengembalikan rata-rata elemen array dan jumlah_vektor yang menjumlahkan dua array ke array ketiga.

fortran panggil.f90
program panggil
   use, intrinsic :: iso_c_binding
   implicit none
   interface
      function rata_rata(n, v) bind(c, name="rata_rata") result(r)
         import :: c_int, c_double
         integer(c_int), value :: n
         real(c_double), intent(in) :: v(*)
         real(c_double) :: r
      end function rata_rata
      subroutine jumlah_vektor(n, a, b, c) bind(c, name="jumlah_vektor")
         import :: c_int, c_double
         integer(c_int), value :: n
         real(c_double), intent(in) :: a(*)
         real(c_double), intent(in) :: b(*)
         real(c_double), intent(out) :: c(*)
      end subroutine jumlah_vektor
   end interface
   real(c_double) :: u(3) = [2.0_c_double, 4.0_c_double, 6.0_c_double]
   real(c_double) :: v(3) = [1.0_c_double, 1.0_c_double, 1.0_c_double]
   real(c_double) :: w(3)
   call jumlah_vektor(3_c_int, u, v, w)
   print *, "rata-rata u :", rata_rata(3_c_int, u)
   print *, "jumlah u + v:", w
end program panggil

Pihak C-nya berupa berkas cek.c yang mendefinisikan kedua fungsi tersebut dengan tanda tangan yang cocok.

c cek.c
// tidak-dijalankan
#include <stddef.h>

double rata_rata(int n, const double *v) {
    double total = 0.0;
    for (int i = 0; i < n; i++) {
        total += v[i];
    }
    return total / n;
}

void jumlah_vektor(int n, const double *a, const double *b, double *c) {
    for (int i = 0; i < n; i++) {
        c[i] = a[i] + b[i];
    }
}

Karena C tidak mengenal intent, arah aliran data disepakati melalui kualifikasi C dan atribut Fortran secara bersamaan. Tabel berikut merangkum padanan deklarasi yang dipakai pada program di atas.

Deklarasi FortranPrototipe C yang cocok
integer(c_int), value :: nint n
real(c_double), intent(in) :: v(*)const double *v
real(c_double), intent(out) :: c(*)double *c
real(c_double) :: r sebagai nilai balikdouble sebagai nilai balik

Penanda const pada pihak C dan intent(in) pada pihak Fortran mendokumentasikan kontrak yang sama dari dua sisi. Kompiler tidak mencocokkan keduanya, tetapi kehadiran keduanya membuat perubahan pada satu sisi mudah diperhatikan dari sisi yang lain, dan tabel semacam ini menjadi rujukan tercepat saat antarmuka baru ditulis.

Kompilasi campuran dan pemanggilan#

Kedua bahasa dikompilasi terpisah menjadi berkas objek, lalu disambungkan (link) menjadi satu program. Kompilasi C dilakukan gcc dengan opsi -c yang menghasilkan cek.o tanpa penyambungan, sedangkan gfortran dipilih sebagai penggerak tahap penyambungan karena pustaka jalannya (runtime) Fortran harus dilibatkan. Perintah berikut dijalankan dari direktori topik.

bash
# kompilasi berkas C menjadi berkas objek
gcc -c cek.c -o cek.o
# kompilasi Fortran dan sambungkan dengan berkas objek C
gfortran panggil.f90 cek.o -o app
# jalankan program campuran hasil kompilasi
./app
text Keluaran
 rata-rata u :   4.0000000000000000     
 jumlah u + v:   3.0000000000000000        5.0000000000000000        7.0000000000000000     

Baris pertama keluaran berasal dari pemanggilan rata_rata(3_c_int, u), yang menghitung (2 + 4 + 6) / 3 dan mengembalikan 4.0 melalui nilai balik fungsi C. Baris kedua berasal dari jumlah_vektor, yang mengisi w melalui pointer karena argumennya dinyatakan intent(out). Kedua fungsi berjalan tanpa konversi data apa pun, karena real(c_double) dan double menempati memori yang sama persis.

Meneruskan fungsi Fortran ke C#

Arah sebaliknya juga diperlukan ketika kode C meminta pemberi kerja (caller) menyediakan fungsinya sendiri, misalnya fungsi matematika yang diterapkan pada setiap elemen. C menerima fungsi melalui pointer fungsi (function pointer), dan padanannya di Fortran adalah tipe type(c_funptr). Prosedur Fortran yang akan diteruskan wajib beratribut bind(c) agar konvensi pemanggilannya cocok, dan alamatnya diambil dengan c_funloc. Bentuk fungsi yang diharapkan C didokumentasikan pada abstract interface, sehingga kesalahan tanda tangan terdeteksi saat kompilasi.

fortran callback.f90
module mod_callback
   use, intrinsic :: iso_c_binding
   implicit none
   abstract interface
      function formula(x) bind(c)
         import :: c_double
         real(c_double), value :: x
         real(c_double) :: formula
      end function formula
   end interface
contains
   function kuadrat(x) bind(c) result(y)
      real(c_double), value :: x
      real(c_double) :: y
      y = x * x
   end function kuadrat
end module mod_callback

program callback
   use, intrinsic :: iso_c_binding
   use mod_callback, only: formula, kuadrat
   implicit none
   interface
      subroutine terapkan(n, v, f) bind(c, name="terapkan")
         import :: c_int, c_double, c_funptr
         integer(c_int), value :: n
         real(c_double), intent(inout) :: v(*)
         type(c_funptr), value :: f
      end subroutine terapkan
   end interface
   real(c_double) :: data(4) = [1.0_c_double, 2.0_c_double, 3.0_c_double, 4.0_c_double]
   call terapkan(4_c_int, data, c_funloc(kuadrat))
   print *, "kuadrat tiap elemen:", data
end program callback

Pihak C-nya berupa prosedur terapkan yang memanggil f pada setiap elemen array.

c callback.c
// tidak-dijalankan
void terapkan(int n, double *v, double (*f)(double)) {
    for (int i = 0; i < n; i++) {
        v[i] = f(v[i]);
    }
}
bash
# kompilasi berkas C yang memuat terapkan
gcc -c callback.c -o callback.o
# sambungkan modul dan program Fortran dengan berkas objek C
gfortran callback.f90 callback.o -o terapkan
# jalankan program pemanggilan balik hasil kompilasi
./terapkan
text Keluaran
 kuadrat tiap elemen:   1.0000000000000000        4.0000000000000000        9.0000000000000000        16.000000000000000     

Prosedur C terapkan tidak mengetahui apa pun tentang Fortran, tetapi berhasil memanggil kuadrat empat kali karena alamat yang diterimanya dari c_funloc mengikuti konvensi pemanggilan C. Array data berubah di tempat (in place) karena argumennya dinyatakan intent(inout), sehingga nilai 1, 2, 3, 4 menjadi 1, 4, 9, 16 pada keluaran.

Arah kebalikannya juga tersedia apabila C menyimpan pointer fungsi dan mengembalikannya kepada Fortran. Prosedur c_f_procpointer menghubungkan type(c_funptr) dengan prosedur pointer Fortran, sehingga fungsi yang dipilih oleh C pada waktu berjalan tetap dapat dipanggil dengan sintaks Fortran biasa. Pola ini lazim pada pustaka yang menawarkan beberapa implementasi algoritma untuk satu kegunaan, misalnya pilihan antara versi cepat dan versi hemat memori, dan pemilihannya baru ditetapkan setelah pustaka dimuat.

Kesalahan umum#

  • Ketidakcocokan cara penerusan argumen. Argumen scalar C diteruskan secara nilai, sedangkan tanpa atribut value Fortran meneruskannya sebagai alamat. Deklarasi integer(c_int) :: n tanpa value untuk fungsi C int n membuat C membaca angka dari alamat yang salah, dan akibatnya nilai acak atau kerusakan memori.
  • Nama simbol yang tidak cocok. Atribut name pada bind(c) dipakai apa adanya tanpa tambahan apa pun, sehingga untuk pustaka yang mengikuti konvensi garis bawah seperti OpenBLAS, atributnya harus name="dgemm_" beserta garis bawahnya. Kekeliruan ini muncul sebagai error undefined reference pada tahap penyambungan.
  • Melupakan import pada badan antarmuka. Badan interface tidak mewarisi deklarasi modul atau program secara otomatis, sehingga c_int yang tidak diimpor menyebabkan error kompilasi, bukan error saat program berjalan.

Ringkasan#

  • Blok interface dengan bind(c, name="...") mengikat deklarasi Fortran pada simbol C yang tepat, termasuk arah dan cara penerusan setiap argumen.
  • Kompilasi campuran dilakukan terpisah dengan gcc -c untuk berkas C, lalu gfortran menyambungkan berkas objeknya bersama pustaka jalannya Fortran.
  • Fungsi Fortran dapat diteruskan ke C sebagai type(c_funptr) melalui c_funloc, dengan syarat prosedurnya beratribut bind(c).

Referensi#