被調(diào)用的代碼應(yīng)該是共享庫(kù)的格式。大多數(shù) C 和 Fortran 庫(kù)都已經(jīng)被編譯為共享庫(kù)。如果自己使用 GCC (或 Clang )編譯代碼,需要添加 -shared
和 -fPIC
選項(xiàng)。Julia 調(diào)用這些庫(kù)的開(kāi)銷(xiāo)與本地 C 語(yǔ)言相同。
調(diào)用共享庫(kù)和函數(shù)時(shí)使用多元組形式: (:function, "library")
或 ("function", "library")
,其中 function
是 C 的導(dǎo)出函數(shù)名, library
是共享庫(kù)名。共享庫(kù)依據(jù)名字來(lái)解析,路徑由環(huán)境變量來(lái)確定,有時(shí)需要直接指明。
多元組內(nèi)有時(shí)僅有函數(shù)名(僅 :function
或 "function"
)。此時(shí),函數(shù)名由當(dāng)前進(jìn)程解析。這種形式可以用來(lái)調(diào)用 C 庫(kù)函數(shù), Julia 運(yùn)行時(shí)函數(shù),及鏈接到 Julia 的應(yīng)用中的函數(shù)。
使用 ccall
來(lái)生成庫(kù)函數(shù)調(diào)用。 ccall
的參數(shù)如下:
Int32
, Int64
, Float64
,或者指向任意類(lèi)型參數(shù) T
的指針 Ptr{T}
,或者僅僅是指向無(wú)類(lèi)型指針 void*
的 Ptr
下例調(diào)用標(biāo)準(zhǔn) C 庫(kù)中的 clock
:
julia> t = ccall( (:clock, "libc"), Int32, ())
2292761
julia> t
2292761
julia> typeof(ans)
Int32
clock
函數(shù)沒(méi)有參數(shù),返回 Int32
類(lèi)型。輸入的類(lèi)型如果只有一個(gè),常寫(xiě)成一元多元組,在后面跟一逗號(hào)。例如要調(diào)用 getenv
函數(shù)取得指向一個(gè)環(huán)境變量的指針,應(yīng)這樣調(diào)用:
julia> path = ccall( (:getenv, "libc"), Ptr{Uint8}, (Ptr{Uint8},), "SHELL")
Ptr{Uint8} @0x00007fff5fbffc45
julia> bytestring(path)
"/bin/bash"
注意,類(lèi)型多元組的參數(shù)必須寫(xiě)成 (Ptr{Uint8},)
,而不是 (Ptr{Uint8})
。這是因?yàn)?(Ptr{Uint8})
等價(jià)于 Ptr{Uint8}
,它并不是一個(gè)包含 Ptr{Uint8}
的一元多元組:
julia> (Ptr{Uint8})
Ptr{Uint8}
julia> (Ptr{Uint8},)
(Ptr{Uint8},)
實(shí)際中要提供可復(fù)用代碼時(shí),通常要使用 Julia 的函數(shù)來(lái)封裝 ccall
,設(shè)置參數(shù),然后檢查 C 或 Fortran 函數(shù)中可能出現(xiàn)的任何錯(cuò)誤,將其作為異常傳遞給 Julia 的函數(shù)調(diào)用者。下例中, getenv
C 庫(kù)函數(shù)被封裝在 env.jl 里的 Julia 函數(shù)中:
function getenv(var::String)
val = ccall( (:getenv, "libc"),
Ptr{Uint8}, (Ptr{Uint8},), var)
if val == C_NULL
error("getenv: undefined variable: ", var)
end
bytestring(val)
end
上例中,如果函數(shù)調(diào)用者試圖讀取一個(gè)不存在的環(huán)境變量,封裝將拋出異常:
julia> getenv("SHELL")
"/bin/bash"
julia> getenv("FOOBAR")
getenv: undefined variable: FOOBAR
下例稍復(fù)雜些,顯示本地機(jī)器的主機(jī)名:
function gethostname()
hostname = Array(Uint8, 128)
ccall( (:gethostname, "libc"), Int32,
(Ptr{Uint8}, Uint),
hostname, length(hostname))
return bytestring(convert(Ptr{Uint8}, hostname))
end
此例先分配出一個(gè)字節(jié)數(shù)組,然后調(diào)用 C 庫(kù)函數(shù) gethostname
向數(shù)組中填充主機(jī)名,取得指向主機(jī)名緩沖區(qū)的指針,在默認(rèn)其為空結(jié)尾 C 字符串的前提下,將其轉(zhuǎn)換為 Julia 字符串。 C 庫(kù)函數(shù)一般都用這種方式從函數(shù)調(diào)用者那兒,將申請(qǐng)的內(nèi)存?zhèn)鬟f給被調(diào)用者,然后填充。在 Julia 中分配內(nèi)存,通常都需要通過(guò)構(gòu)建非初始化數(shù)組,然后將指向數(shù)據(jù)的指針傳遞給 C 函數(shù)。
調(diào)用 Fortran 函數(shù)時(shí),所有的輸入都必須通過(guò)引用來(lái)傳遞。
&
前綴說(shuō)明傳遞的是指向標(biāo)量參數(shù)的指針,而不是標(biāo)量值本身。下例使用 BLAS 函數(shù)計(jì)算點(diǎn)積:
function compute_dot(DX::Vector{Float64}, DY::Vector{Float64})
assert(length(DX) == length(DY))
n = length(DX)
incx = incy = 1
product = ccall( (:ddot_, "libLAPACK"),
Float64,
(Ptr{Int32}, Ptr{Float64}, Ptr{Int32}, Ptr{Float64}, Ptr{Int32}),
&n, DX, &incx, DY, &incy)
return product
end
前綴 &
的意思與 C 中的不同。對(duì)引用的變量的任何更改,都是對(duì) Julia 不可見(jiàn)的。 &
并不是真正的地址運(yùn)算符,可以在任何語(yǔ)法中使用它,例如 &0
和 &f(x)
。
注意在處理過(guò)程中,C 的頭文件可以放在任何地方。目前還不能將 Julia 的結(jié)構(gòu)和其他非基礎(chǔ)類(lèi)型傳遞給 C 庫(kù)。通過(guò)傳遞指針來(lái)生成、使用非透明結(jié)構(gòu)類(lèi)型的 C 函數(shù),可以向 Julia 返回 Ptr{Void}
類(lèi)型的值,這個(gè)值以 Ptr{Void}
的形式被其它 C 函數(shù)調(diào)用。可以像任何 C 程序一樣,通過(guò)調(diào)用庫(kù)中對(duì)應(yīng)的程序,對(duì)對(duì)象進(jìn)行內(nèi)存分配和釋放。
Julia 自動(dòng)調(diào)用 convert
函數(shù),將參數(shù)轉(zhuǎn)換為指定類(lèi)型。例如:
ccall( (:foo, "libfoo"), Void, (Int32, Float64),
x, y)
會(huì)按如下操作:
ccall( (:foo, "libfoo"), Void, (Int32, Float64),
convert(Int32, x), convert(Float64, y))
如果標(biāo)量值與 &
一起被傳遞作為 Ptr{T}
類(lèi)型的參數(shù)時(shí),值首先會(huì)被轉(zhuǎn)換為 T
類(lèi)型。
把數(shù)組作為一個(gè) Ptr{T}
參數(shù)傳遞給 C 時(shí),它不進(jìn)行轉(zhuǎn)換。Julia 僅檢查元素類(lèi)型是否為 T
,然后傳遞首元素的地址。這樣做可以避免不必要的復(fù)制整個(gè)數(shù)組。
因此,如果 Array
中的數(shù)據(jù)格式不對(duì)時(shí),要使用顯式轉(zhuǎn)換,如 int32(a)
。
如果想把數(shù)組 不經(jīng)轉(zhuǎn)換 而作為一個(gè)不同類(lèi)型的指針傳遞時(shí),要么聲明參數(shù)為 Ptr{Void}
類(lèi)型,要么顯式調(diào)用 convert(Ptr{T}, pointer(A))
。
基礎(chǔ)的 C/C++ 類(lèi)型和 Julia 類(lèi)型對(duì)照如下。每個(gè) C 類(lèi)型也有一個(gè)對(duì)應(yīng)名稱(chēng)的 Julia 類(lèi)型,不過(guò)冠以了前綴 C 。這有助于編寫(xiě)簡(jiǎn)便的代碼(但 C 中的 int 與 Julia 中的 Int 不同)。
與系統(tǒng)無(wú)關(guān):
unsigned char | Cuchar | Uint8 |
---|---|---|
short | Cshort | Int16 |
unsigned short | Cushort | Uint16 |
int | Cint | Int32 |
unsigned int | Cuint | Uint32 |
long long | Clonglong | Int64 |
unsigned long long | Culonglong | Uint64 |
intmax_t | Cintmax_t | Int64 |
uintmax_t | Cuintmax_t | Uint64 |
float | Cfloat | Float32 |
double | Cdouble | Float64 |
ptrdiff_t | Cptrdiff_t | Int |
ssize_t | Cssize_t | Int |
size_t | Csize_t | Uint |
void | Void | |
void* | Ptr{Void} | |
char* (or char[], e.g. a string) | Ptr{Uint8} | |
char* (or char[]) | Ptr{Ptr{Uint8}} | |
struct T* (T 正確表示一個(gè)定義好的 bit 類(lèi)型) | Ptr{T} (在參數(shù)列表中使用 &variable_name 調(diào)用) | |
struct T (T 正確表示一個(gè)定義好的 bit 類(lèi)型) | T (在參數(shù)列表中使用 &variable_name 調(diào)用) | |
jl_value_t* (任何 Julia 類(lèi)型) | Ptr{Any} |
對(duì)應(yīng)于字符串參數(shù)( char*
)的 Julia 類(lèi)型為 Ptr{Uint8}
,而不是 ASCIIString
。參數(shù)中有 char**
類(lèi)型的 C 函數(shù),在 Julia 中調(diào)用時(shí)應(yīng)使用 Ptr{Ptr{Uint8}}
類(lèi)型。例如,C 函數(shù):
int main(int argc, char **argv);
在 Julia 中應(yīng)該這樣調(diào)用:
argv = [ "a.out", "arg1", "arg2" ]
ccall(:main, Int32, (Int32, Ptr{Ptr{Uint8}}), length(argv), argv)
對(duì)于 wchar_t*
參數(shù),Julia 類(lèi)型為 Ptr{Wchar_t}
,并且數(shù)據(jù)可以通過(guò) wstring(s)
方法轉(zhuǎn)換為原始的 Julia 字符串(等同于 utf16(s)
或utf32(s)
,這取決于 Cwchar_t
的寬度)。還要注意 ASCII, UTF-8, UTF-16, 和UTF-32 字符串?dāng)?shù)據(jù)在 Julia 內(nèi)部是以 NUL 結(jié)尾的,所以它能夠傳遞到 C 函數(shù)中以 NUL 為結(jié)尾的數(shù)據(jù),而不用再做一個(gè)拷貝。
下列方法是“不安全”的,因?yàn)閴闹羔樆蝾?lèi)型聲明可能會(huì)導(dǎo)致意外終止或損壞任意進(jìn)程內(nèi)存。
指定 Ptr{T}
,常使用 unsafe_ref(ptr, [index])
方法,將類(lèi)型為 T
的內(nèi)容從所引用的內(nèi)存復(fù)制到 Julia 對(duì)象中。 index
參數(shù)是可選的(默認(rèn)為 1 ),它是從 1 開(kāi)始的索引值。此函數(shù)類(lèi)似于 getindex()
和 setindex!()
的行為(如 []
語(yǔ)法)。
返回值是一個(gè)被初始化的新對(duì)象,它包含被引用內(nèi)存內(nèi)容的淺拷貝。被引用的內(nèi)存可安全釋放。
如果 T
是 Any
類(lèi)型,被引用的內(nèi)存會(huì)被認(rèn)為包含對(duì) Julia 對(duì)象 jl_value_t*
的引用,結(jié)果為這個(gè)對(duì)象的引用,且此對(duì)象不會(huì)被拷貝。需要謹(jǐn)慎確保對(duì)象始終對(duì)垃圾回收機(jī)制可見(jiàn)(指針不重要,重要的是新的引用),來(lái)確保內(nèi)存不會(huì)過(guò)早釋放。注意,如果內(nèi)存原本不是由 Julia 申請(qǐng)的,新對(duì)象將永遠(yuǎn)不會(huì)被 Julia 的垃圾回收機(jī)制釋放。如果 Ptr
本身就是 jl_value_t*
,可使用 unsafe_pointer_to_objref(ptr)
將其轉(zhuǎn)換回 Julia 對(duì)象引用。(可通過(guò)調(diào)用 pointer_from_objref(v)
將Julia 值 v
轉(zhuǎn)換為 jl_value_t*
指針 Ptr{Void}
。)
逆操作(向 Ptr{T} 寫(xiě)數(shù)據(jù))可通過(guò) unsafe_store!(ptr, value, [index])
來(lái)實(shí)現(xiàn)。目前,僅支持位類(lèi)型和其它無(wú)指針( isbits
)不可變類(lèi)型。
現(xiàn)在任何拋出異常的操作,估摸著都是還沒(méi)實(shí)現(xiàn)完呢。來(lái)寫(xiě)個(gè)帖子上報(bào) bug 吧,就會(huì)有人來(lái)解決啦。
如果所關(guān)注的指針是(位類(lèi)型或不可變)的目標(biāo)數(shù)據(jù)數(shù)組, pointer_to_array(ptr,dims,[own])
函數(shù)就非常有用啦。如果想要 Julia “控制”底層緩沖區(qū)并在返回的 Array
被釋放時(shí)調(diào)用 free(ptr)
,最后一個(gè)參數(shù)應(yīng)該為真。如果省略 own
參數(shù)或它為假,則調(diào)用者需確保緩沖區(qū)一直存在,直至所有的讀取都結(jié)束。
Ptr
的算術(shù)(比如 +
) 和 C 的指針?biāo)阈g(shù)不同, 對(duì) Ptr
加一個(gè)整數(shù)會(huì)將指針移動(dòng)一段距離的 字節(jié) , 而不是元素。這樣從指針運(yùn)算上得到的地址不會(huì)依賴(lài)指針類(lèi)型。
因?yàn)?C 不支持多返回值, 所以通常 C 函數(shù)會(huì)用指針來(lái)修改值。 在 ccall
里完成這些需要把值放在適當(dāng)類(lèi)型的數(shù)組里。當(dāng)你用 Ptr
傳遞整個(gè)數(shù)組時(shí),
Julia 會(huì)自動(dòng)傳遞一個(gè) C 指針到被這個(gè)值:
width = Cint[0]
range = Cfloat[0]
ccall(:foo, Void, (Ptr{Cint}, Ptr{Cfloat}), width, range)
這被廣泛用在了 Julia 的 LAPACK 接口上, 其中整數(shù)類(lèi)型的 info
被以引用的方式傳到 LAPACK, 再返回是否成功。
給 ccall 傳遞數(shù)據(jù)時(shí),最好避免使用 pointer()
函數(shù)。應(yīng)當(dāng)定義一個(gè)轉(zhuǎn)換方法,將變量直接傳遞給 ccall 。ccall 會(huì)自動(dòng)安排,使得在調(diào)用返回前,它的所有參數(shù)都不會(huì)被垃圾回收機(jī)制處理。如果 C API 要存儲(chǔ)一個(gè)由 Julia 分配好的內(nèi)存的引用,當(dāng) ccall 返回后,需要自己設(shè)置,使對(duì)象對(duì)垃圾回收機(jī)制保持可見(jiàn)。推薦的方法為,在一個(gè)類(lèi)型為 Array{Any,1}
的全局變量中保存這些值,直到 C 接口通知它已經(jīng)處理完了。
只要構(gòu)造了指向 Julia 數(shù)據(jù)的指針,就必須保證原始數(shù)據(jù)直至指針使用完之前一直存在。Julia 中的許多方法,如 unsafe_ref()
和 bytestring()
,都復(fù)制數(shù)據(jù)而不是控制緩沖區(qū),因此可以安全釋放(或修改)原始數(shù)據(jù),不會(huì)影響到 Julia 。有一個(gè)例外需要注意,由于性能的原因, pointer_to_array()
會(huì)共享(或控制)底層緩沖區(qū)。
垃圾回收并不能保證回收的順序。例如,當(dāng) a
包含對(duì) b
的引用,且兩者都要被垃圾回收時(shí),不能保證 b
在 a
之后被回收。這需要用其它方式來(lái)處理。
(name, library)
函數(shù)說(shuō)明應(yīng)為常量表達(dá)式??梢酝ㄟ^(guò) eval
,將計(jì)算結(jié)果作為函數(shù)名:
@eval ccall(($(string("a","b")),"lib"), ...
表達(dá)式用 string
構(gòu)造名字,然后將名字代入 ccall
表達(dá)式進(jìn)行計(jì)算。注意 eval
僅在頂層運(yùn)行,因此在表達(dá)式之內(nèi),不能使用本地變量(除非本地變量的值使用 $
進(jìn)行過(guò)內(nèi)插)。 eval
通常用來(lái)作為頂層定義,例如,將包含多個(gè)相似函數(shù)的庫(kù)封裝在一起。
ccall
的第一個(gè)參數(shù)可以是運(yùn)行時(shí)求值的表達(dá)式。此時(shí),表達(dá)式的值應(yīng)為 Ptr
類(lèi)型,指向要調(diào)用的原生函數(shù)的地址。這個(gè)特性用于 ccall
的第一參數(shù)包含對(duì)非常量(本地變量或函數(shù)參數(shù))的引用時(shí)。
ccall
的第二個(gè)(可選)參數(shù)指定調(diào)用方式(在返回值之前)。如果沒(méi)指定,將會(huì)使用操作系統(tǒng)的默認(rèn) C 調(diào)用方式。其它支持的調(diào)用方式為: stdcall
, cdecl
, fastcall
和 thiscall
。例如 (來(lái)自 base/libc.jl):
hn = Array(Uint8, 256)
err=ccall(:gethostname, stdcall, Int32, (Ptr{Uint8}, Uint32), hn, length(hn))
更多信息請(qǐng)參考 LLVM Language Reference
當(dāng)全局變量導(dǎo)出到本地庫(kù)時(shí)可以使用 cglobal
方法,通過(guò)名稱(chēng)進(jìn)行訪問(wèn)。cglobal
的參數(shù)和 ccall
的指定參數(shù)是相同的符號(hào),并且其表述了存儲(chǔ)在變量中的值類(lèi)型:
julia> cglobal((:errno,:libc), Int32)
Ptr{Int32} @0x00007f418d0816b8
該結(jié)果是一個(gè)該值的地址的指針??梢酝ㄟ^(guò)這個(gè)指針對(duì)這個(gè)值進(jìn)行操作,但需要使用 unsafe_load
和 unsafe_store
。
可以將 Julia 函數(shù)傳遞給本地的函數(shù),只要該函數(shù)有指針參數(shù)。一個(gè)典型的例子為標(biāo)準(zhǔn) C 庫(kù) qsort
函數(shù),描述如下:
void qsort(void *base, size_t nmemb, size_t size,
int(*compare)(const void *a, const void *b));
base
參數(shù)是一個(gè)數(shù)組長(zhǎng)度 nmemb
的指針,每個(gè)元素大小為 size
字節(jié)。compare
是一個(gè)回調(diào)函數(shù),帶有兩個(gè)元素 a
和 b
的指針,并且如果 a
在 b
之前或之后出現(xiàn),則返回一個(gè)大于或者小于 0 的整數(shù)(如果允許任意順序的話(huà),結(jié)果為 0)?,F(xiàn)在假設(shè)我們?cè)?Julia 值中有一個(gè)一維數(shù)組 A
,我們想給這個(gè)數(shù)組進(jìn)行排序,使用 qsort
函數(shù)(不用 Julia 的內(nèi)置函數(shù))。在我們調(diào)用 qsort
和傳遞參數(shù)之前,我們需要寫(xiě)一個(gè)比較函數(shù),來(lái)適應(yīng)任意類(lèi)型 T:
function mycompare{T}(a_::Ptr{T}, b_::Ptr{T})
a = unsafe_load(a_)
b = unsafe_load(b_)
return convert(Cint, a < b ? -1 : a > b ? +1 : 0)
end
請(qǐng)注意,我們必須注意返回值類(lèi)型:qsort
需要的是 C 語(yǔ)言的 int
類(lèi)型變量作為返回值,所以我們必須通過(guò)調(diào)用 convert
來(lái)確保返回 Cint
。
為了能夠傳遞這個(gè)函數(shù)給 C,我們要通過(guò) cfunction
來(lái)得到它的地址:
const mycompare_c = cfunction(mycompare, Cint, (Ptr{Cdouble}, Ptr{Cdouble}))
cfunction
接受三個(gè)參數(shù):Julia 函數(shù)(mycompare
),返回值類(lèi)型 (Cint
),和一個(gè)參數(shù)類(lèi)型的元組,在這種情況下對(duì) cdouble
(Float64)元素 的數(shù)組進(jìn)行排序。
最終對(duì) qsort
的調(diào)用如下:
A = [1.3, -2.7, 4.4, 3.1]
ccall(:qsort, Void, (Ptr{Cdouble}, Csize_t, Csize_t, Ptr{Void}),
A, length(A), sizeof(eltype(A)), mycompare_c)
執(zhí)行該操作之后, A
會(huì)更改為排序數(shù)組 [ -2.7, 1.3, 3.1, 4.4]
。注意 Julia 知道如何去將數(shù)組轉(zhuǎn)換為 Ptr{Cdouble}
,如何計(jì)算字節(jié)大?。ㄅc C 的 sizeof
是相同的)等等。如果你有興趣,你可以嘗試在 mycompare
插入一個(gè) println("mycompare($a,$b)")
,這將允許你以比較的方式去查看 qsort
(并且確認(rèn)它的確調(diào)用了 你傳遞的 Julia 函數(shù))。
一些 C 從不同的線程中執(zhí)行他們的回調(diào)函數(shù),并且 Julia 不含有線程安全,你需要做一些額外的預(yù)防措施。特別是,你需要設(shè)置兩層系統(tǒng):C 的回調(diào)應(yīng)該只調(diào)度(通過(guò) Julia 的時(shí)間循環(huán))你“真正”的回調(diào)函數(shù)的執(zhí)行。你的回調(diào)需要兩個(gè)輸入(你很可能會(huì)忘記)并且通過(guò) SingleAsyncWork
進(jìn)行包裝::
cb = Base.SingleAsyncWork(data -> my_real_callback(args))
你傳遞給 C 的回調(diào)應(yīng)該僅僅執(zhí)行 ccall
到 :uv_async_send
,傳遞 cb.handle
作為參數(shù)。
對(duì)于更多的如何傳遞回調(diào)到 C 庫(kù)的細(xì)節(jié),請(qǐng)參考 blog post。
Cpp 和 Clang 擴(kuò)展包提供了有限的 C++ 支持。
當(dāng)處理不同的平臺(tái)庫(kù)的時(shí)候,經(jīng)常要針對(duì)特殊平臺(tái)提供特殊函數(shù)。這時(shí)常用到變量 OS_NAME
。此外,還有一些常用的宏: @windows
, @unix
, @linux
, 及 @osx
。注意, linux 和 osx 是 unix 的不相交的子集。宏的用法類(lèi)似于三元條件運(yùn)算符。
簡(jiǎn)單的調(diào)用:
ccall( (@windows? :_fopen : :fopen), ...)
復(fù)雜的調(diào)用:
@linux? (
begin
some_complicated_thing(a)
end
: begin
some_different_thing(a)
end
)
鏈?zhǔn)秸{(diào)用(圓括號(hào)可以省略,但為了可讀性,最好加上):
@windows? :a : (@osx? :b : :c)
更多建議: