我們已經(jīng)知道 調(diào)用 C 和 Fortran 代碼 Julia 可以用簡(jiǎn)單有效的方式調(diào)用 C 函數(shù)。但是有很多情況下正好相反:需要從 C 調(diào)用 Julia 函數(shù)。這可以把 Julia 代碼整合到更大型的 C/C++ 項(xiàng)目中去, 而不需要重新把所有都用 C/C++ 寫(xiě)一遍。 Julia 提供了給 C 的 API 來(lái)實(shí)現(xiàn)這一點(diǎn)。正如大多數(shù)語(yǔ)言都有方法調(diào)用 C 函數(shù)一樣, Julia的 API 也可以用于搭建和其他語(yǔ)言之間的橋梁。
我們從一個(gè)簡(jiǎn)單的 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;
}
編譯這個(gè)程序你需要把 Julia 的頭文件包含在路徑內(nèi)并且鏈接函數(shù)庫(kù) libjulia
。 比方說(shuō) 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
完成,這個(gè)函數(shù)的參數(shù)是Julia安裝路徑,類(lèi)型是 const char*
。如果沒(méi)有任何參數(shù),Julia 會(huì)自動(dòng)尋找 Julia 的安裝路徑。
第二個(gè)語(yǔ)句初始化了 Julia 的任務(wù)調(diào)度系統(tǒng)。這條語(yǔ)句必須在一個(gè)不返回的函數(shù)中出現(xiàn),只要 Julia 被調(diào)用(main
運(yùn)行得很好)。嚴(yán)格地講,這條語(yǔ)句是可以選擇的,但是轉(zhuǎn)換任務(wù)的操作將引起問(wèn)題,如果它被省略的話(huà)。
測(cè)試程序中的第三條語(yǔ)句使用 jl_eval_string
的調(diào)用評(píng)估了 Julia 語(yǔ)句。
真正的應(yīng)用程序不僅僅需要執(zhí)行表達(dá)式,而且返回主程序的值。jl_eval_string
返回 jl_value_t
,它是一個(gè)指向堆上分配的 Julia 對(duì)象的指針。用這種方式存儲(chǔ)簡(jiǎn)單的數(shù)據(jù)類(lèi)型比如 Float64
叫做 boxing
,提取存儲(chǔ)的原始數(shù)據(jù)叫做 unboxing
。我們提升過(guò)的用 Julia 計(jì)算 2 的平方根和用 C 語(yǔ)言讀取結(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
是否是一個(gè)指定的 Julia 類(lèi)型,我們可以使用 jl_is_...
函數(shù)。通過(guò)將 typeof(sqrt(2.0))
輸入進(jìn) Julia shell,我們可以看到返回類(lèi)型為 Float64
(C 中的 double)。為了將裝好的 Julia 的值轉(zhuǎn)換成 C 語(yǔ)言中的 double,jl_unbox_float64
功能在上面的代碼片段中被使用。
相應(yīng)的 jl_box_...
功能被用來(lái)用另一種方式轉(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 語(yǔ)言來(lái)獲得 Julia 表達(dá)式的結(jié)果時(shí),它不允許傳遞在 C 中計(jì)算的參數(shù)到 Julia 中。對(duì)于這個(gè),你需要直接調(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
的處理通過(guò)調(diào)用 jl_get_function
檢索。第一個(gè)傳遞給 jl_get_function
的參數(shù)是一個(gè)指向 Base
模塊的指針,在那里 sqrt
被定義。然后,double 值使用 jl_box_float64
封裝。最后,在最后一步中,函數(shù)使用 jl_call1
被調(diào)用。jl_call0
,jl_call2
和 jl_call3
函數(shù)也存在,來(lái)方便地處理不同參數(shù)的數(shù)量。為了傳遞更多的參數(shù),使用 jl_call
:
jl_value_t *jl_call(jl_function_t *f, jl_value_t **args, int32_t nargs)
第二個(gè)參數(shù) args
是一個(gè) jl_value_t*
參數(shù)的數(shù)組而且 nargs
是參數(shù)的數(shù)字。
正如我們已經(jīng)看見(jiàn)的,Julia 對(duì)象作為指針在 C 中呈現(xiàn)。這引出了一個(gè)問(wèn)題,誰(shuí)應(yīng)該釋放這些對(duì)象。
通常情況下,Julia 對(duì)象通過(guò)一個(gè) garbage collector(GC) 來(lái)釋放,但是 GC 不會(huì)自動(dòng)地知道我們?cè)?C 中有一個(gè)對(duì) Julia 值的引用。這意味著 GC 可以釋放指針,使指針無(wú)效。
GC 僅能在 Julia 對(duì)象被分配時(shí)運(yùn)行。像 jl_box_float64
的調(diào)用運(yùn)行分配,而且分配也能在任何運(yùn)行 Julia 代碼的指針中發(fā)生。在 jl_...
調(diào)用間使用指針通常是安全的。但是為了確認(rèn)值能使 jl_...
調(diào)用生存,我們不得不告訴 Julia 我們有一個(gè)對(duì) 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
在棧上工作,所以它在棧幀被銷(xiāo)毀之前必須準(zhǔn)確地和 JL_GC_POP
成組。
幾個(gè) Julia 值能使用 JL_GC_PUSH2
,JL_GC_PUSH3
,和 JL_GC_PUSH4
宏指令被立刻 push。為了 push 一個(gè) 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ù)來(lái)控制 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ù)。下一個(gè)例子將展示這是如此工作的。
Julia 數(shù)組通過(guò)數(shù)據(jù)類(lèi)型 jl_array_t*
用 C 語(yǔ)言顯示?;旧?,jl_array_t
是一個(gè)包含以下的結(jié)構(gòu):
為了使事情簡(jiǎn)單,我們用一個(gè) 1D 數(shù)組開(kāi)始。創(chuàng)建一個(gè)包含長(zhǎng)度為 10 個(gè)元素的 Float64 數(shù)組通過(guò)以下完成:
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ù)組你能生成一個(gè)對(duì)數(shù)據(jù)的簡(jiǎn)單包裝:
double *existingArray = (double*)malloc(sizeof(double)*10);
jl_array_t *x = jl_ptr_to_array_1d(array_type, existingArray, 10, 0);
最后一個(gè)參數(shù)是一個(gè)表明 Julia 是否應(yīng)該獲取數(shù)據(jù)所有權(quán)的布爾值。如果這個(gè)參數(shù)非零,GC 將在數(shù)組不再引用時(shí)在數(shù)據(jù)指針上調(diào)用 free
。
為了獲取 x 的數(shù)據(jù),我們可以使用 jl_array_data
:
double *xData = (double*)jl_array_data(x);
現(xiàn)在我們可以填寫(xiě)數(shù)組:
for(size_t i=0; i<jl_array_len(x); i++)
xData[i] = i;
現(xiàn)在讓我們調(diào)用一個(gè)在 x
上運(yùn)行操作的 Julia 函數(shù):
jl_function_t *func = jl_get_function(jl_base_module, "reverse!");
jl_call1(func, (jl_value_t*)x);
通過(guò)打印數(shù)組,我們可以核實(shí) x
的元素現(xiàn)在被顛倒了。
如果一個(gè) Julia 函數(shù)返回一個(gè)數(shù)組,jl_eval_string
和 jl_call
的返回值能被轉(zhuǎn)換成一個(gè) 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í)確保保持對(duì)數(shù)組的引用。
Julia 的多維數(shù)組在內(nèi)存中以列的順序被存儲(chǔ)。這兒是一些創(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
時(shí))以作為慣用的 C 代碼讀取。
Julia 代碼能拋出異常。比如,考慮以下:
jl_eval_string("this_function_does_not_exist()");
這個(gè)調(diào)用將什么都不做。但是,檢查一個(gè)異常是否拋出是可能的。
if (jl_exception_occurred())
printf("%s \n", jl_typeof_str(jl_exception_occurred()));
如果你用一個(gè)支持異常的語(yǔ)言(比如,Python,C#,C++)使用 Julia C API,用一個(gè)檢查異常是否被拋出的函數(shù)包裝每一個(gè)調(diào)用 libjulia 的調(diào)用是有道理的,而且它用主語(yǔ)言重新拋出異常。
當(dāng)寫(xiě)一個(gè)可調(diào)用的 Julia 函數(shù)時(shí),驗(yàn)證參數(shù)和拋出異常來(lái)指出錯(cuò)誤是必要的。一個(gè)典型的類(lèi)型檢查像這樣:
if (!jl_is_float64(val)) {
jl_type_error(function_name, (jl_value_t*)jl_float64_type, val);
}
通常的異常能使用函數(shù)來(lái)引起:
void jl_error(const char *str);
void jl_errorf(const char *fmt, ...);
jl_error
使用一個(gè) C 的字符串,jl_errorf
像 printf
一樣被調(diào)用:
jl_errorf("argument x = %d is too large", x);
在這個(gè)例子中 x
被假設(shè)為一個(gè)整型。
更多建議: