呼叫 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))
iyd
、sec
、alt
、lat
和 lon
參數是輸入參數,而 dens
和 temp
是輸出參數。
以下所有範例都使用 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);
請注意,呼叫者必須知道 dens
和 temp
實際上是陣列。如果沒有傳遞陣列,將會造成區段錯誤,因為 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] ) );
}
以下是一些重點
- 檔案必須包含 OCaml 標頭檔
alloc.h
、memory.h
和mlvalue.h
。 - 函式會先呼叫 CAMLparam5 巨集。任何使用 CAML 類型的函式都必須在開頭執行此動作。
- 函式會使用 Double_val 和 Long_val 巨集,從 OCaml 值物件中擷取 C 類型。
- 所有值都會依照原型要求,以參考方式傳遞至 gtd6_ 例程。
- 函式會使用 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 函式。