我們已經(jīng)知道 調(diào)用 C 和 Fortran 代碼 Julia 可以用簡單有效的方式調(diào)用 C 函數(shù)。但是有很多情況下正好相反:需要從 C 調(diào)用 Julia 函數(shù)。這可以把 Julia 代碼整合到更大型的 C/C++ 項目中去, 而不需要重新把所有都用 C/C++ 寫一遍。 Julia 提供了給 C 的 API 來實現(xiàn)這一點。正如大多數(shù)語言都有方法調(diào)用 C 函數(shù)一樣, Julia的 API 也可以用于搭建和其他語言之間的橋梁。
我們從一個簡單的 C 程序入手,它初始化 Julia 并且調(diào)用一些 Julia 的代碼::
#include <julia.h>
int main(int argc, char *argv[])
{
jl_init(NULL);
JL_SET_STACK_BASE;
jl_eval_string("print(sqrt(2.0))");
return 0;
}
編譯這個程序你需要把 Julia 的頭文件包含在路徑內(nèi)并且鏈接函數(shù)庫 libjulia
。 比方說 Julia 安裝在 $JULIA_DIR
, 就可以用 gcc 編譯::
gcc -o test -I$JULIA_DIR/include/julia -L$JULIA_DIR/usr/lib -ljulia test.c
或者可以看看 Julia 源碼里 example/
下的 embedding.c
。
調(diào)用Julia函數(shù)之前要先初始化 Julia, 可以用 jl_init
完成,這個函數(shù)的參數(shù)是Julia安裝路徑,類型是 const char*
。如果沒有任何參數(shù),Julia 會自動尋找 Julia 的安裝路徑。
第二個語句初始化了 Julia 的任務(wù)調(diào)度系統(tǒng)。這條語句必須在一個不返回的函數(shù)中出現(xiàn),只要 Julia 被調(diào)用(main
運行得很好)。嚴(yán)格地講,這條語句是可以選擇的,但是轉(zhuǎn)換任務(wù)的操作將引起問題,如果它被省略的話。
測試程序中的第三條語句使用 jl_eval_string
的調(diào)用評估了 Julia 語句。
真正的應(yīng)用程序不僅僅需要執(zhí)行表達(dá)式,而且返回主程序的值。jl_eval_string
返回 jl_value_t
,它是一個指向堆上分配的 Julia 對象的指針。用這種方式存儲簡單的數(shù)據(jù)類型比如 Float64
叫做 boxing
,提取存儲的原始數(shù)據(jù)叫做 unboxing
。我們提升過的用 Julia 計算 2 的平方根和用 C 語言讀取結(jié)果的樣本程序如下所示:
jl_value_t *ret = jl_eval_string("sqrt(2.0)");
if (jl_is_float64(ret)) {
double ret_unboxed = jl_unbox_float64(ret);
printf("sqrt(2.0) in C: %e \n", ret_unboxed);
}
為了檢查 ret
是否是一個指定的 Julia 類型,我們可以使用 jl_is_...
函數(shù)。通過將 typeof(sqrt(2.0))
輸入進 Julia shell,我們可以看到返回類型為 Float64
(C 中的 double)。為了將裝好的 Julia 的值轉(zhuǎn)換成 C 語言中的 double,jl_unbox_float64
功能在上面的代碼片段中被使用。
相應(yīng)的 jl_box_...
功能被用來用另一種方式轉(zhuǎn)換:
jl_value_t *a = jl_box_float64(3.0);
jl_value_t *b = jl_box_float32(3.0f);
jl_value_t *c = jl_box_int32(3);
正如我們下面將看到的,調(diào)用帶有指定參數(shù)的 Julia 函數(shù)裝箱(boxing)是需要的。
當(dāng) jl_eval_string
允許 C 語言來獲得 Julia 表達(dá)式的結(jié)果時,它不允許傳遞在 C 中計算的參數(shù)到 Julia 中。對于這個,你需要直接調(diào)用 Julia 函數(shù),使用 jl_call
:
jl_function_t *func = jl_get_function(jl_base_module, "sqrt");
jl_value_t *argument = jl_box_float64(2.0);
jl_value_t *ret = jl_call1(func, argument);
在第一步中,Julia 函數(shù) sqrt
的處理通過調(diào)用 jl_get_function
檢索。第一個傳遞給 jl_get_function
的參數(shù)是一個指向 Base
模塊的指針,在那里 sqrt
被定義。然后,double 值使用 jl_box_float64
封裝。最后,在最后一步中,函數(shù)使用 jl_call1
被調(diào)用。jl_call0
,jl_call2
和 jl_call3
函數(shù)也存在,來方便地處理不同參數(shù)的數(shù)量。為了傳遞更多的參數(shù),使用 jl_call
:
jl_value_t *jl_call(jl_function_t *f, jl_value_t **args, int32_t nargs)
第二個參數(shù) args
是一個 jl_value_t*
參數(shù)的數(shù)組而且 nargs
是參數(shù)的數(shù)字。
正如我們已經(jīng)看見的,Julia 對象作為指針在 C 中呈現(xiàn)。這引出了一個問題,誰應(yīng)該釋放這些對象。
通常情況下,Julia 對象通過一個 garbage collector(GC) 來釋放,但是 GC 不會自動地知道我們在 C 中有一個對 Julia 值的引用。這意味著 GC 可以釋放指針,使指針無效。
GC 僅能在 Julia 對象被分配時運行。像 jl_box_float64
的調(diào)用運行分配,而且分配也能在任何運行 Julia 代碼的指針中發(fā)生。在 jl_...
調(diào)用間使用指針通常是安全的。但是為了確認(rèn)值能使 jl_...
調(diào)用生存,我們不得不告訴 Julia 我們有一個對 Julia 值的引用。這可以使用 JL_GC_PUSH
宏指令完成。This can be done using the JL_GC_PUSH
macros:
jl_value_t *ret = jl_eval_string("sqrt(2.0)");
JL_GC_PUSH1(&ret);
// Do something with ret
JL_GC_POP();
JL_GC_POP
調(diào)用釋放了之前 JL_GC_PUSH
建立的引用。注意到 JL_GC_PUSH
在棧上工作,所以它在棧幀被銷毀之前必須準(zhǔn)確地和 JL_GC_POP
成組。
幾個 Julia 值能使用 JL_GC_PUSH2
,JL_GC_PUSH3
,和 JL_GC_PUSH4
宏指令被立刻 push。為了 push 一個 Julia 值的數(shù)組我們可以使用 JL_GC_PUSHARGS
宏指令,它能向以下那樣使用:cro, which can be used as follows:
jl_value_t **args;
JL_GC_PUSHARGS(args, 2); // args can now hold 2 `jl_value_t*` objects
args[0] = some_value;
args[1] = some_other_value;
// Do something with args (e.g. call jl_... functions)
JL_GC_POP();
有一些函數(shù)來控制 GC。在普通的使用案例中,這些不應(yīng)該是必需的。
void jl_gc_collect() |
Force a GC run |
---|---|
void jl_gc_disable() |
Disable the GC |
void jl_gc_enable() |
Enable the GC |
Julia 和 C 能不用復(fù)制而分享數(shù)組數(shù)據(jù)。下一個例子將展示這是如此工作的。
Julia 數(shù)組通過數(shù)據(jù)類型 jl_array_t*
用 C 語言顯示?;旧?,jl_array_t
是一個包含以下的結(jié)構(gòu):
為了使事情簡單,我們用一個 1D 數(shù)組開始。創(chuàng)建一個包含長度為 10 個元素的 Float64 數(shù)組通過以下完成:
jl_value_t* array_type = jl_apply_array_type(jl_float64_type, 1);
jl_array_t* x = jl_alloc_array_1d(array_type, 10);
或者,如果你已經(jīng)分配了數(shù)組你能生成一個對數(shù)據(jù)的簡單包裝:
double *existingArray = (double*)malloc(sizeof(double)*10);
jl_array_t *x = jl_ptr_to_array_1d(array_type, existingArray, 10, 0);
最后一個參數(shù)是一個表明 Julia 是否應(yīng)該獲取數(shù)據(jù)所有權(quán)的布爾值。如果這個參數(shù)非零,GC 將在數(shù)組不再引用時在數(shù)據(jù)指針上調(diào)用 free
。
為了獲取 x 的數(shù)據(jù),我們可以使用 jl_array_data
:
double *xData = (double*)jl_array_data(x);
現(xiàn)在我們可以填寫數(shù)組:
for(size_t i=0; i<jl_array_len(x); i++)
xData[i] = i;
現(xiàn)在讓我們調(diào)用一個在 x
上運行操作的 Julia 函數(shù):
jl_function_t *func = jl_get_function(jl_base_module, "reverse!");
jl_call1(func, (jl_value_t*)x);
通過打印數(shù)組,我們可以核實 x
的元素現(xiàn)在被顛倒了。
如果一個 Julia 函數(shù)返回一個數(shù)組,jl_eval_string
和 jl_call
的返回值能被轉(zhuǎn)換成一個 jl_array_t*
:
jl_function_t *func = jl_get_function(jl_base_module, "reverse");
jl_array_t *y = (jl_array_t*)jl_call1(func, (jl_value_t*)x);
現(xiàn)在 y
的內(nèi)容能在使用 jl_array_data
前被獲取。一如往常,當(dāng)它在使用中時確保保持對數(shù)組的引用。
Julia 的多維數(shù)組在內(nèi)存中以列的順序被存儲。這兒是一些創(chuàng)建二維數(shù)組和獲取屬性的代碼:
// Create 2D array of float64 type
jl_value_t *array_type = jl_apply_array_type(jl_float64_type, 2);
jl_array_t *x = jl_alloc_array_2d(array_type, 10, 5);
// Get array pointer
double *p = (double*)jl_array_data(x);
// Get number of dimensions
int ndims = jl_array_ndims(x);
// Get the size of the i-th dim
size_t size0 = jl_array_dim(x,0);
size_t size1 = jl_array_dim(x,1);
// Fill array with data
for(size_t i=0; i<size1; i++)
for(size_t j=0; j<size0; j++)
p[j + size0*i] = i + j;
注意到當(dāng) Julia 數(shù)組使用 1-based 的索引,C 的 API 使用 0-based 的索引(例如在調(diào)用 jl_array_dim
時)以作為慣用的 C 代碼讀取。
Julia 代碼能拋出異常。比如,考慮以下:
jl_eval_string("this_function_does_not_exist()");
這個調(diào)用將什么都不做。但是,檢查一個異常是否拋出是可能的。
if (jl_exception_occurred())
printf("%s \n", jl_typeof_str(jl_exception_occurred()));
如果你用一個支持異常的語言(比如,Python,C#,C++)使用 Julia C API,用一個檢查異常是否被拋出的函數(shù)包裝每一個調(diào)用 libjulia 的調(diào)用是有道理的,而且它用主語言重新拋出異常。
當(dāng)寫一個可調(diào)用的 Julia 函數(shù)時,驗證參數(shù)和拋出異常來指出錯誤是必要的。一個典型的類型檢查像這樣:
if (!jl_is_float64(val)) {
jl_type_error(function_name, (jl_value_t*)jl_float64_type, val);
}
通常的異常能使用函數(shù)來引起:
void jl_error(const char *str);
void jl_errorf(const char *fmt, ...);
jl_error
使用一個 C 的字符串,jl_errorf
像 printf
一樣被調(diào)用:
jl_errorf("argument x = %d is too large", x);
在這個例子中 x
被假設(shè)為一個整型。
更多建議: