呼叫 Fortran 函式庫

Fortran 不是許多人編寫新程式碼的語言,但它在科學界仍然被廣泛使用。有許多用於數值計算的函式庫,永遠不會用 C 或 C++ 編寫。不過,從 OCaml 呼叫 Fortran 例程是完全可能的,因為它們通常會編譯成與 C 程式相同的物件格式,並具有最小的名稱修飾。

本教學將逐步介紹為 Fortran 函式編譯介面模組的過程。這裡涉及的步驟與包裝 C 函式的步驟相同,但需要考慮一些針對 Fortran 的因素。

Fortran 函式包含在名為 func.f 的檔案中,並具有以下簽名

subroutine gtd6(integer iyd, real sec, real alt, real lat, real lon, real dens(8), real temp(2))

iydsecaltlatlon 參數是輸入參數,而 denstemp 是輸出參數。

以下所有範例都使用 GNU Fortran 77 編譯器 (g77)。這些範例均未使用 GNU Fortran 90 編譯器 (gfort) 進行測試,並且在 gfort 經過一段時間的驗證之前不會進行測試。

步驟 1:編譯 Fortran 例程

C/C++ 只有一種子程式類別 (函式),而 Fortran 有兩種:函式和子程式。函式相當於非 void 的 C 函式,它接受參數並始終傳回一個值。子程式相當於 void 的 C 函式。

當 g77 編譯 Fortran 函式時,它會建立一個名稱函式,並附加一個底線。如果函式的 Fortran 名稱包含任何底線,則編譯的函式名稱將附加兩個底線。可以透過這個名稱呼叫產生的函式。子程式將轉換為傳回 int 的 C 函式。

若要將 funcs.f 檔案編譯為物件檔,可以使用以下命令

prompt> g77 -c funcs.f

這將產生檔案 'funcs.o'。然後,您可以使用以下命令查看已編譯函式的名稱

prompt> nm funcs.o

在此輸出中,您會看到一行具有以下內容

T gtd6_

這表示已建立函式 gtd6_,並且該函式位於物件檔中。

Fortran 支援整數和實數類型,它們的名稱也如此。在我們的範例中,我們只有實數和整數類型。實數相當於 C 的 double,整數相當於 C 的 long。此外,Fortran 會依參考傳遞所有內容,因此我們的 gtd6 函式的對應 C 原型是

int gtd6_(integer *iyd, real* sec, real* alt, real* glat, real* glong, real* dens, real* temp);

請注意,呼叫者必須知道 denstemp 實際上是陣列。如果沒有傳遞陣列,將會造成區段錯誤,因為 gtd6_ 函式會將它們當成陣列使用 (這也是 OCaml 的另一個優勢)。

步驟 2:建立 C 包裝函式

由於 OCaml 的外部函式介面是以 C 為基礎,因此必須建立 C 包裝函式。為了避免傳回值陣列的困難,我們將只建立一個函式,該函式將傳回該函式計算的溫度陣列的第二個元素,並忽略其他傳回值 (這是該函式非常頻繁的使用方式)。此函式將位於原始碼檔案 wrapper.c 中。

CAMLprim value gtd6_t (value iydV, value secVal, value altVal, value latVal, value lonVal) {
   CAMLparam5( iydV, secVal, altVal, latVal, lonVal );
   long iyd = Long_val( iydV );
   float    sec = Double_val( secVal );
   float    alt = Double_val( altVal );
   float    lat = Double_val( latVal );
   float    lon = Double_val( lonVal );

   gtd6_(&iyd, &sec, &alt, &glat, &glon, d, t);
   CAMLreturn( caml_copy_double( t[1] ) );
}

以下是一些重點

  1. 檔案必須包含 OCaml 標頭檔 alloc.hmemory.hmlvalue.h
  2. 函式會先呼叫 CAMLparam5 巨集。任何使用 CAML 類型的函式都必須在開頭執行此動作。
  3. 函式會使用 Double_val 和 Long_val 巨集,從 OCaml 值物件中擷取 C 類型。
  4. 所有值都會依照原型要求,以參考方式傳遞至 gtd6_ 例程。
  5. 函式會使用 copy_caml_double 函式和 CAMLreturn 巨集來建立包含傳回值的新值,並分別傳回該值。

步驟 3:編譯共用函式庫。

現在有了兩個原始碼檔案 funcs.f 和 wrapper.c,我們需要建立一個可由 OCaml 載入的共用函式庫。將它作為多步驟程序執行會比較容易,以下是指令

prompt> g77 -c funcs.f

prompt> cc -I<ocaml include path> -c wrapper.c

prompt> cc -shared -o wrapper.so wrapper.o funcs.o -lg2c

這將建立一個名為 wrapper.so 的共用物件函式庫,其中包含 Fortran 函式和包裝函式。-lg2c 選項是必要的,可提供所使用內建 Fortran 函式的實作。

步驟 4:現在輪到 OCaml

現在,在 OCaml 檔案 (gtd6.ml) 中,我們必須定義對函式的外部參考和呼叫該函式的函式。

external temp : int -> float -> float -> float -> float -> float = "gtd6_t"

let () =
  print_double (temp 1 2.0 3.0 4.0 5.0);
  print_newline ()

這會告訴 OCaml,temp 函式接受 5 個參數並傳回單一浮點數,並呼叫 C 函式 gtd6_t。

此時,給定的步驟是將其編譯為位元組碼。我還沒有太多編譯為原生的經驗,所以讓其他人協助 (或等到我學會如何做)。

prompt> ocamlc -c gtd6.ml prompt> ocamlc -o test gtd6.cmo wrapper.so

瞧,我們已從 OCaml 呼叫了 Fortran 函式。

仍然需要協助嗎?

協助改進我們的文件

所有 OCaml 文件都是開放原始碼。看到有錯誤或不清楚的地方嗎?提交提取要求。

OCaml

創新。社群。安全性。