C, Fortran コードの呼び出し
ほとんどのコードは Julia で記述できますが、C と Fortran で既に記述されている数値計算用の高品質で成熟したライブラリが多数あります。この既存のコードを簡単に使用できるように、Julia は C 関数と Fortran 関数を簡単かつ効率的に呼び出すことができます。Julia は「定型文なし」の哲学を持っています:関数は「接着剤」コードやコード生成もしくはコンパイルなしでJulia から直接呼び出すことができます。– 対話プロンプトからでも、です。通常の関数呼び出しのように見える ccall
構文を使用して適切な呼び出しを行うだけで実現することができます。
Julia から呼び出されるコードは、共有ライブラリとして使用できなければなりません。ほとんどの C ライブラリと Fortran ライブラリは既に共有ライブラリとしてコンパイルされていますが、GCC (または Clang) を使用してコードを自分でコンパイルする場合は、-shared
および fPIC
オプションを使用してください。Julia の JIT によって生成されるマシン命令はネイティブ C 呼び出しと同じなので、結果として生じるオーバーヘッドは C コードからライブラリ関数を呼び出すのと同じです。[1]
共有ライブラリと関数は、(:function, "library")
or ("function", "library")
の書式のタプルで参照できます。ここで 、function
は、C出エクスポートされた関数名、library` は共有ライブラリの名前です: ライブラリ読み込みパス(プラットフォーム固有の物も含む)中の共用ライブラリーはその名前のみで解決されます。ライブラリのフルパスを指定しても構いません。
関数名は、タプルの代わりに単独で(単に :function
または "function" として) 使用できます。この場合、名前は現在のプロセス内で解決されます。この形式を使用できるのは、C ライブラリ関数、Julia ランタイムの関数、または Julia にリンクされたアプリケーションの関数のいずれかを呼び出す時です。
デフォルトでは、Fortran コンパイラはマングリングされた名前を生成するので (たとえば、関数名を小文字または大文字に変換し、アンダースコアを追加する)、ccall
経由でFortranの関数を呼ぶためには、Fortran コンパイラが従うルールに対応してマングリングされた関数名を渡す必要があります。 また、Fortran 関数を呼び出す時は、すべての入力をヒープまたはスタックに割り当てられた値へのポインターとして渡す必要があります。これは、通常ヒープ割り当てされる配列やその他のミュータブルなオブジェクトだけでなく、通常スタック割り当てられ、C または Julia 呼び出し規約を使用する時にレジスタに一般的に渡される整数や浮動小数点数などのスカラー値についても同様です。
最後に、ccall
を使用して、ライブラリ関数の呼び出しを行う実際に生成することができます。ccall
の引数は:
(:function, "library")
のペア (これが最も一般的)もしくは
関数名シンボル
:function
か 関数名文字列"function"
( 現在のプロセスや libc 内のシンボルに対して使用可能) ,もしくは
関数ポインタ(例えば、
dlsym
関数から).The function's return type
A tuple of input types, corresponding to the function signature
The actual argument values to be passed to the function, if any; each is a separate parameter.
The (:function, "library")
pair, return type, and input types must be literal constants (i.e., they can't be variables, but see Non-constant Function Specifications below).
残りのパラメータはは、対象のメソッドが定義されたとき、コンパイル時に評価されます。
See below for how to map C types to Julia types.
上記全ての内容を含むシンプルな例ですが、次の関数は ほとんどのUnix 派生のシステムでは 標準 C ライブラリから clock
関数を呼び出します:
julia> t = ccall(:clock, Int32, ())
2292761
julia> t
2292761
julia> typeof(ans)
Int32
clock
は引数を受け取らず、Int32
を返します。よくある落とし穴1 つは、引数型の 1要素タプルにも末尾のコンマが必要になる点です。たとえば、getenv
関数を呼び出して環境変数の値へのポインターを取得するには、次のような呼び出しを行います:
julia> path = ccall(:getenv, Cstring, (Cstring,), "SHELL")
Cstring(@0x00007fff5fbffc45)
julia> unsafe_string(path)
"/bin/bash"
注意してほしいのは、引数型のタプルは、(Cstring)
ではなく (Cstring,)
と書かなければいけない点です。これは、(Cstring)
は、Cstring
が括弧でくくられているだけの式に過ぎないためです。 Cstring
1要素だけを含むタプルを表すにはカンマが必要です:
julia> (Cstring)
Cstring
julia> (Cstring,)
(Cstring,)
実際には、特に再利用可能な機能を提供する場合の話ですが、ccall
を使用するコードに対して、引数を設定して、CまたはFortranのターゲット関数の振る舞いのエラーチェックをして、例外をJulia側にまで伝播させるようなラッパー関数を準備することが多いです。C API と Fortran API は、エラー条件を示す方法に関して一貫性がないため、これは特に重要です。たとえば、getenv
C ライブラリ関数は、env.jl
から実際の定義の簡略化されたバージョンである次の Julia 関数にラップされます:
function getenv(var::AbstractString)
val = ccall(:getenv, Cstring, (Cstring,), var)
if val == C_NULL
error("getenv: undefined variable: ", var)
end
return unsafe_string(val)
end
C言語の getenv
関数は NULL
を返すことによってエラーを示しますが、他の標準 C 関数は-1、0、1 およびその他の特殊値を返すなど、さまざまな方法でエラーを示します。 このラッパーは、呼び出し元が存在しない環境変数を取得しようとした場合に問題を明確に示す例外をスローします:
julia> getenv("SHELL")
"/bin/bash"
julia> getenv("FOOBAR")
getenv: undefined variable: FOOBAR
Here is a slightly more complex example that discovers the local machine's hostname. In this example, the networking library code is assumed to be in a shared library named "libc". In practice, this function is usually part of the C standard library, and so the "libc" portion should be omitted, but we wish to show here the usage of this syntax.
function gethostname()
hostname = Vector{UInt8}(undef, 256) # MAXHOSTNAMELEN
err = ccall((:gethostname, "libc"), Int32,
(Ptr{UInt8}, Csize_t),
hostname, sizeof(hostname))
Base.systemerror("gethostname", err != 0)
hostname[end] = 0 # ensure null-termination
return unsafe_string(pointer(hostname))
end
次の使用例は、最初にバイトの配列を割り当て、次に C ライブラリ関数 gethostname
を呼び出して配列をホスト名で埋め、ホスト名バッファーへのポインターを取得し、NULL 終端 C 文字列であると仮定してJulia 文字列にポインタを変換します。C ライブラリでは、呼び出し元にメモリを割り当てて、呼び出し先に渡して入力するように要求するこのパターンを使用するのが一般的です。このような Julia からのメモリの割り当ては、一般に、初期化されていない配列を作成し、そのデータへのポインタを C 関数に渡すことによって行われます。配列が初期化されていないため、NULL バイトを含むことができるため、ここで Cstring
型を使用しない理由です。ccall
の一部としてCstring
に変換すると、含まれる NULL バイトがチェックされるため、変換エラーがスローされる可能性があります。
C言語コンパチな Julia関数のポインタをつくる
関数ポインター入力引数に持つ C 関数に Julia 関数を渡すことができます。 たとえば、下記の様なC プロトタイプ宣言を一致させるには下記のようにします:
typedef returntype (*functiontype)(argumenttype, ...)
@cfunction
マクロは、Julia関数の呼び出しをするための、C言語コンパチな関数ポインタを生成します。 @cfunction
への引数は:
- Julia 関数
- The function's return type
- A tuple of input types, corresponding to the function signature
As with ccall
, the return type and tuple of input types must be literal constants.
現在は、プラットフォームでデフォルトの C呼び出し規則のみがサポートされています。 つまり、@cfunction
で生成されたポインタは、WINAPIが 32bit Windows 上のstdcall
関数を期待する場合には用いることができません。 WIN64 (stdcall
が、C呼び出し規則と統合されている) 上であれば使えます。
古典的な例として、C の標準関数 qsort
は以下のように宣言されます:
void qsort(void *base, size_t nmemb, size_t size,
int (*compare)(const void*, const void*));
base
引数は、長さ nmemb
の配列へのポインタで、各要素は size
バイトです。 compare
は、2 つの要素 a
と b
へのポインタを受け取り、ソートのための比較結果を整数値で返すコールバック関数です。 この返り値は、a
が b
よりも先/後に現れるべき場合には、ゼロよりも小さい/大きい値になります(ソート結果としてa,b
の順番が任意という場合は0を返すことも可能です)。 さて、ここで並べ替える値の1次元配列 A
があるとします。 'qsort' 関数を使用します (Julia の組み込みの 'sort' 関数ではなく)。心配する前に 'qsort' を呼び出して引数を渡す場合は、一部の関数で動作する比較関数を記述する必要があります。 任意のオブジェクト ('<'を定義します)。
Now, suppose that we have a 1d array A
of values in Julia that we want to sort using the qsort
function (rather than Julia's built-in sort
function). Before we worry about calling qsort
and passing arguments, we need to write a comparison function:
julia> function mycompare(a, b)::Cint
return (a < b) ? -1 : ((a > b) ? +1 : 0)
end
mycompare (generic function with 1 method)
$qsort$ expects a comparison function that return a C $int$, so we annotate the return type to be $Cint$.
In order to pass this function to C, we obtain its address using the macro @cfunction
:
julia> mycompare_c = @cfunction(mycompare, Cint, (Ref{Cdouble}, Ref{Cdouble}));
@cfunction
requires three arguments: the Julia function (mycompare
), the return type (Cint
), and a literal tuple of the input argument types, in this case to sort an array of Cdouble
(Float64
) elements.
The final call to qsort
looks like this:
julia> A = [1.3, -2.7, 4.4, 3.1]
4-element Array{Float64,1}:
1.3
-2.7
4.4
3.1
julia> ccall(:qsort, Cvoid, (Ptr{Cdouble}, Csize_t, Csize_t, Ptr{Cvoid}),
A, length(A), sizeof(eltype(A)), mycompare_c)
julia> A
4-element Array{Float64,1}:
-2.7
1.3
3.1
4.4
As can be seen, A
is changed to the sorted array [-2.7, 1.3, 3.1, 4.4]
. Note that Julia takes care of converting the array to a Ptr{Cdouble}
), computing the size of the element type in bytes, and so on.
For fun, try inserting a println("mycompare($a, $b)")
line into mycompare
, which will allow you to see the comparisons that qsort
is performing (and to verify that it is really calling the Julia function that you passed to it).
Mapping C Types to Julia
It is critical to exactly match the declared C type with its declaration in Julia. Inconsistencies can cause code that works correctly on one system to fail or produce indeterminate results on a different system.
Note that no C header files are used anywhere in the process of calling C functions: you are responsible for making sure that your Julia types and call signatures accurately reflect those in the C header file.[2]
Automatic Type Conversion
Julia automatically inserts calls to the Base.cconvert
function to convert each argument to the specified type. For example, the following call:
ccall((:foo, "libfoo"), Cvoid, (Int32, Float64), x, y)
will behave as if the following were written:
ccall((:foo, "libfoo"), Cvoid, (Int32, Float64),
Base.unsafe_convert(Int32, Base.cconvert(Int32, x)),
Base.unsafe_convert(Float64, Base.cconvert(Float64, y)))
Base.cconvert
normally just calls convert
, but can be defined to return an arbitrary new object more appropriate for passing to C. This should be used to perform all allocations of memory that will be accessed by the C code. For example, this is used to convert an Array
of objects (e.g. strings) to an array of pointers.
Base.unsafe_convert
handles conversion to Ptr
types. It is considered unsafe because converting an object to a native pointer can hide the object from the garbage collector, causing it to be freed prematurely.
Type Correspondences
First, let's review some relevant Julia type terminology:
Syntax / Keyword | Example | Description |
---|---|---|
mutable struct | BitSet | "Leaf Type" :: A group of related data that includes a type-tag, is managed by the Julia GC, and is defined by object-identity. The type parameters of a leaf type must be fully defined (no TypeVars are allowed) in order for the instance to be constructed. |
abstract type | Any , AbstractArray{T, N} , Complex{T} | "Super Type" :: A super-type (not a leaf-type) that cannot be instantiated, but can be used to describe a group of types. |
T{A} | Vector{Int} | "Type Parameter" :: A specialization of a type (typically used for dispatch or storage optimization). |
"TypeVar" :: The T in the type parameter declaration is referred to as a TypeVar (short for type variable). | ||
primitive type | Int , Float64 | "Primitive Type" :: A type with no fields, but a size. It is stored and defined by-value. |
struct | Pair{Int, Int} | "Struct" :: A type with all fields defined to be constant. It is defined by-value, and may be stored with a type-tag. |
ComplexF64 (isbits ) | "Is-Bits" :: A primitive type , or a struct type where all fields are other isbits types. It is defined by-value, and is stored without a type-tag. | |
struct ...; end | nothing | "Singleton" :: a Leaf Type or Struct with no fields. |
(...) or tuple(...) | (1, 2, 3) | "Tuple" :: an immutable data-structure similar to an anonymous struct type, or a constant array. Represented as either an array or a struct. |
Bits Types
There are several special types to be aware of, as no other type can be defined to behave the same:
Float32
Exactly corresponds to the
float
type in C (orREAL*4
in Fortran).Float64
Exactly corresponds to the
double
type in C (orREAL*8
in Fortran).ComplexF32
Exactly corresponds to the
complex float
type in C (orCOMPLEX*8
in Fortran).ComplexF64
Exactly corresponds to the
complex double
type in C (orCOMPLEX*16
in Fortran).Signed
Exactly corresponds to the
signed
type annotation in C (or anyINTEGER
type in Fortran). Any Julia type that is not a subtype ofSigned
is assumed to be unsigned.
Ref{T}
Behaves like a
Ptr{T}
that can manage its memory via the Julia GC.
Array{T,N}
When an array is passed to C as a
Ptr{T}
argument, it is not reinterpret-cast: Julia requires that the element type of the array matchesT
, and the address of the first element is passed.Therefore, if an
Array
contains data in the wrong format, it will have to be explicitly converted using a call such astrunc(Int32, a)
.To pass an array
A
as a pointer of a different type without converting the data beforehand (for example, to pass aFloat64
array to a function that operates on uninterpreted bytes), you can declare the argument asPtr{Cvoid}
.If an array of eltype
Ptr{T}
is passed as aPtr{Ptr{T}}
argument,Base.cconvert
will attempt to first make a null-terminated copy of the array with each element replaced by itsBase.cconvert
version. This allows, for example, passing anargv
pointer array of typeVector{String}
to an argument of typePtr{Ptr{Cchar}}
.
On all systems we currently support, basic C/C++ value types may be translated to Julia types as follows. Every C type also has a corresponding Julia type with the same name, prefixed by C. This can help for writing portable code (and remembering that an int
in C is not the same as an Int
in Julia).
System Independent Types
C name | Fortran name | Standard Julia Alias | Julia Base Type |
---|---|---|---|
unsigned char | CHARACTER | Cuchar | UInt8 |
bool (only in C++) | Cuchar | UInt8 | |
short | INTEGER*2 , LOGICAL*2 | Cshort | Int16 |
unsigned short | Cushort | UInt16 | |
int , BOOL (C, typical) | INTEGER*4 , LOGICAL*4 | Cint | Int32 |
unsigned int | Cuint | UInt32 | |
long long | INTEGER*8 , LOGICAL*8 | Clonglong | Int64 |
unsigned long long | Culonglong | UInt64 | |
intmax_t | Cintmax_t | Int64 | |
uintmax_t | Cuintmax_t | UInt64 | |
float | REAL*4i | Cfloat | Float32 |
double | REAL*8 | Cdouble | Float64 |
complex float | COMPLEX*8 | ComplexF32 | Complex{Float32} |
complex double | COMPLEX*16 | ComplexF64 | Complex{Float64} |
ptrdiff_t | Cptrdiff_t | Int | |
ssize_t | Cssize_t | Int | |
size_t | Csize_t | UInt | |
void | Cvoid | ||
void and [[noreturn]] or _Noreturn | Union{} | ||
void* | Ptr{Cvoid} | ||
T* (where T represents an appropriately defined type) | Ref{T} | ||
char* (or char[] , e.g. a string) | CHARACTER*N | Cstring if NUL-terminated, or Ptr{UInt8} if not | |
char** (or *char[] ) | Ptr{Ptr{UInt8}} | ||
jl_value_t* (any Julia Type) | Any | ||
jl_value_t** (a reference to a Julia Type) | Ref{Any} | ||
va_arg | Not supported | ||
... (variadic function specification) | T... (where T is one of the above types, variadic functions of different argument types are not supported) |
The Cstring
type is essentially a synonym for Ptr{UInt8}
, except the conversion to Cstring
throws an error if the Julia string contains any embedded NUL characters (which would cause the string to be silently truncated if the C routine treats NUL as the terminator). If you are passing a char*
to a C routine that does not assume NUL termination (e.g. because you pass an explicit string length), or if you know for certain that your Julia string does not contain NUL and want to skip the check, you can use Ptr{UInt8}
as the argument type. Cstring
can also be used as the ccall
return type, but in that case it obviously does not introduce any extra checks and is only meant to improve readability of the call.
System Dependent Types
C name | Standard Julia Alias | Julia Base Type |
---|---|---|
char | Cchar | Int8 (x86, x86_64), UInt8 (powerpc, arm) |
long | Clong | Int (UNIX), Int32 (Windows) |
unsigned long | Culong | UInt (UNIX), UInt32 (Windows) |
wchar_t | Cwchar_t | Int32 (UNIX), UInt16 (Windows) |
When calling Fortran, all inputs must be passed by pointers to heap- or stack-allocated values, so all type correspondences above should contain an additional Ptr{..}
or Ref{..}
wrapper around their type specification.
For string arguments (char*
) the Julia type should be Cstring
(if NUL- terminated data is expected) or either Ptr{Cchar}
or Ptr{UInt8}
otherwise (these two pointer types have the same effect), as described above, not String
. Similarly, for array arguments (T[]
or T*
), the Julia type should again be Ptr{T}
, not Vector{T}
.
Julia's Char
type is 32 bits, which is not the same as the wide character type (wchar_t
or wint_t
) on all platforms.
A return type of Union{}
means the function will not return i.e. C++11 [[noreturn]]
or C11 _Noreturn
(e.g. jl_throw
or longjmp
). Do not use this for functions that return no value (void
) but do return, use Cvoid
instead.
For wchar_t*
arguments, the Julia type should be Cwstring
(if the C routine expects a NUL-terminated string) or Ptr{Cwchar_t}
otherwise. Note also that UTF-8 string data in Julia is internally NUL-terminated, so it can be passed to C functions expecting NUL-terminated data without making a copy (but using the Cwstring
type will cause an error to be thrown if the string itself contains NUL characters).
C functions that take an argument of the type char**
can be called by using a Ptr{Ptr{UInt8}}
type within Julia. For example, C functions of the form:
int main(int argc, char **argv);
can be called via the following Julia code:
argv = [ "a.out", "arg1", "arg2" ]
ccall(:main, Int32, (Int32, Ptr{Ptr{UInt8}}), length(argv), argv)
For Fortran functions taking variable length strings of type character(len=*)
the string lengths are provided as hidden arguments. Type and position of these arguments in the list are compiler specific, where compiler vendors usually default to using Csize_t
as type and append the hidden arguments at the end of the argument list. While this behaviour is fixed for some compilers (GNU), others optionally permit placing hidden arguments directly after the character argument (Intel,PGI). For example, Fortran subroutines of the form
subroutine test(str1, str2)
character(len=*) :: str1,str2
can be called via the following Julia code, where the lengths are appended
str1 = "foo"
str2 = "bar"
ccall(:test, Cvoid, (Ptr{UInt8}, Ptr{UInt8}, Csize_t, Csize_t),
str1, str2, sizeof(str1), sizeof(str2))
Fortran compilers may also add other hidden arguments for pointers, assumed-shape (:
) and assumed-size (*
) arrays. Such behaviour can be avoided by using ISO_C_BINDING
and including bind(c)
in the definition of the subroutine, which is strongly recommended for interoperable code. In this case there will be no hidden arguments, at the cost of some language features (e.g. only character(len=1)
will be permitted to pass strings).
A C function declared to return Cvoid
will return the value nothing
in Julia.
Struct Type Correspondences
Composite types, aka struct
in C or TYPE
in Fortran90 (or STRUCTURE
/ RECORD
in some variants of F77), can be mirrored in Julia by creating a struct
definition with the same field layout.
When used recursively, isbits
types are stored inline. All other types are stored as a pointer to the data. When mirroring a struct used by-value inside another struct in C, it is imperative that you do not attempt to manually copy the fields over, as this will not preserve the correct field alignment. Instead, declare an isbits
struct type and use that instead. Unnamed structs are not possible in the translation to Julia.
Packed structs and union declarations are not supported by Julia.
You can get an approximation of a union
if you know, a priori, the field that will have the greatest size (potentially including padding). When translating your fields to Julia, declare the Julia field to be only of that type.
Arrays of parameters can be expressed with NTuple
. For example, the struct in C notation written as
struct B {
int A[3];
};
b_a_2 = B.A[2];
can be written in Julia as
struct B
A::NTuple{3, Cint}
end
b_a_2 = B.A[3] # note the difference in indexing (1-based in Julia, 0-based in C)
Arrays of unknown size (C99-compliant variable length structs specified by []
or [0]
) are not directly supported. Often the best way to deal with these is to deal with the byte offsets directly. For example, if a C library declared a proper string type and returned a pointer to it:
struct String {
int strlen;
char data[];
};
In Julia, we can access the parts independently to make a copy of that string:
str = from_c::Ptr{Cvoid}
len = unsafe_load(Ptr{Cint}(str))
unsafe_string(str + Core.sizeof(Cint), len)
Type Parameters
The type arguments to ccall
and @cfunction
are evaluated statically, when the method containing the usage is defined. They therefore must take the form of a literal tuple, not a variable, and cannot reference local variables.
This may sound like a strange restriction, but remember that since C is not a dynamic language like Julia, its functions can only accept argument types with a statically-known, fixed signature.
However, while the type layout must be known statically to compute the intended C ABI, the static parameters of the function are considered to be part of this static environment. The static parameters of the function may be used as type parameters in the call signature, as long as they don't affect the layout of the type. For example, f(x::T) where {T} = ccall(:valid, Ptr{T}, (Ptr{T},), x)
is valid, since Ptr
is always a word-size primitive type. But, g(x::T) where {T} = ccall(:notvalid, T, (T,), x)
is not valid, since the type layout of T
is not known statically.
SIMD Values
Note: This feature is currently implemented on 64-bit x86 and AArch64 platforms only.
If a C/C++ routine has an argument or return value that is a native SIMD type, the corresponding Julia type is a homogeneous tuple of VecElement
that naturally maps to the SIMD type. Specifically:
- The tuple must be the same size as the SIMD type. For example, a tuple representing an
__m128
on x86 must have a size of 16 bytes.- The element type of the tuple must be an instance of
VecElement{T}
whereT
is a primitive type that is 1, 2, 4 or 8 bytes.
For instance, consider this C routine that uses AVX intrinsics:
#include <immintrin.h>
__m256 dist( __m256 a, __m256 b ) {
return _mm256_sqrt_ps(_mm256_add_ps(_mm256_mul_ps(a, a),
_mm256_mul_ps(b, b)));
}
The following Julia code calls dist
using ccall
:
const m256 = NTuple{8, VecElement{Float32}}
a = m256(ntuple(i -> VecElement(sin(Float32(i))), 8))
b = m256(ntuple(i -> VecElement(cos(Float32(i))), 8))
function call_dist(a::m256, b::m256)
ccall((:dist, "libdist"), m256, (m256, m256), a, b)
end
println(call_dist(a,b))
The host machine must have the requisite SIMD registers. For example, the code above will not work on hosts without AVX support.
Memory Ownership
malloc/free
Memory allocation and deallocation of such objects must be handled by calls to the appropriate cleanup routines in the libraries being used, just like in any C program. Do not try to free an object received from a C library with Libc.free
in Julia, as this may result in the free
function being called via the wrong library and cause the process to abort. The reverse (passing an object allocated in Julia to be freed by an external library) is equally invalid.
When to use T, Ptr{T} and Ref{T}
In Julia code wrapping calls to external C routines, ordinary (non-pointer) data should be declared to be of type T
inside the ccall
, as they are passed by value. For C code accepting pointers, Ref{T}
should generally be used for the types of input arguments, allowing the use of pointers to memory managed by either Julia or C through the implicit call to Base.cconvert
. In contrast, pointers returned by the C function called should be declared to be of output type Ptr{T}
, reflecting that the memory pointed to is managed by C only. Pointers contained in C structs should be represented as fields of type Ptr{T}
within the corresponding Julia struct types designed to mimic the internal structure of corresponding C structs.
In Julia code wrapping calls to external Fortran routines, all input arguments should be declared as of type Ref{T}
, as Fortran passes all variables by pointers to memory locations. The return type should either be Cvoid
for Fortran subroutines, or a T
for Fortran functions returning the type T
.
Mapping C Functions to Julia
ccall
/ @cfunction
argument translation guide
For translating a C argument list to Julia:
T
, whereT
is one of the primitive types:char
,int
,long
,short
,float
,double
,complex
,enum
or any of theirtypedef
equivalentsT
, whereT
is an equivalent Julia Bits Type (per the table above)- if
T
is anenum
, the argument type should be equivalent toCint
orCuint
- argument value will be copied (passed by value)
struct T
(including typedef to a struct)T
, whereT
is a Julia leaf type- argument value will be copied (passed by value)
void*
- depends on how this parameter is used, first translate this to the intended pointer type, then determine the Julia equivalent using the remaining rules in this list
- this argument may be declared as
Ptr{Cvoid}
, if it really is just an unknown pointer
jl_value_t*
Any
- argument value must be a valid Julia object
jl_value_t**
Ref{Any}
- argument value must be a valid Julia object (or
C_NULL
)
T*
Ref{T}
, whereT
is the Julia type corresponding toT
- argument value will be copied if it is an
isbits
type otherwise, the value must be a valid Julia object
T (*)(...)
(e.g. a pointer to a function)Ptr{Cvoid}
(you may need to use@cfunction
explicitly to create this pointer)
...
(e.g. a vararg)T...
, whereT
is the Julia type- currently unsupported by
@cfunction
va_arg
- not supported by
ccall
or@cfunction
- not supported by
ccall
/ @cfunction
return type translation guide
For translating a C return type to Julia:
void
Cvoid
(this will return the singleton instancenothing::Cvoid
)
T
, whereT
is one of the primitive types:char
,int
,long
,short
,float
,double
,complex
,enum
or any of theirtypedef
equivalentsT
, whereT
is an equivalent Julia Bits Type (per the table above)- if
T
is anenum
, the argument type should be equivalent toCint
orCuint
- argument value will be copied (returned by-value)
struct T
(including typedef to a struct)T
, whereT
is a Julia Leaf Type- argument value will be copied (returned by-value)
void*
- depends on how this parameter is used, first translate this to the intended pointer type, then determine the Julia equivalent using the remaining rules in this list
- this argument may be declared as
Ptr{Cvoid}
, if it really is just an unknown pointer
jl_value_t*
Any
- argument value must be a valid Julia object
jl_value_t**
Ptr{Any}
(Ref{Any}
is invalid as a return type)- argument value must be a valid Julia object (or
C_NULL
)
T*
If the memory is already owned by Julia, or is an
isbits
type, and is known to be non-null:Ref{T}
, whereT
is the Julia type corresponding toT
- a return type of
Ref{Any}
is invalid, it should either beAny
(corresponding tojl_value_t*
) orPtr{Any}
(corresponding tojl_value_t**
) - C MUST NOT modify the memory returned via
Ref{T}
ifT
is anisbits
type
If the memory is owned by C:
Ptr{T}
, whereT
is the Julia type corresponding toT
T (*)(...)
(e.g. a pointer to a function)Ptr{Cvoid}
(you may need to use@cfunction
explicitly to create this pointer)
Passing Pointers for Modifying Inputs
Because C doesn't support multiple return values, often C functions will take pointers to data that the function will modify. To accomplish this within a ccall
, you need to first encapsulate the value inside a Ref{T}
of the appropriate type. When you pass this Ref
object as an argument, Julia will automatically pass a C pointer to the encapsulated data:
width = Ref{Cint}(0)
range = Ref{Cfloat}(0)
ccall(:foo, Cvoid, (Ref{Cint}, Ref{Cfloat}), width, range)
Upon return, the contents of width
and range
can be retrieved (if they were changed by foo
) by width[]
and range[]
; that is, they act like zero-dimensional arrays.
C 向けラッパーの例
Let's start with a simple example of a C wrapper that returns a Ptr
type:
mutable struct gsl_permutation
end
# The corresponding C signature is
# gsl_permutation * gsl_permutation_alloc (size_t n);
function permutation_alloc(n::Integer)
output_ptr = ccall(
(:gsl_permutation_alloc, :libgsl), # name of C function and library
Ptr{gsl_permutation}, # output type
(Csize_t,), # tuple of input types
n # name of Julia variable to pass in
)
if output_ptr == C_NULL # Could not allocate memory
throw(OutOfMemoryError())
end
return output_ptr
end
The GNU Scientific Library (here assumed to be accessible through :libgsl
) defines an opaque pointer, gsl_permutation *
, as the return type of the C function gsl_permutation_alloc
. As user code never has to look inside the gsl_permutation
struct, the corresponding Julia wrapper simply needs a new type declaration, gsl_permutation
, that has no internal fields and whose sole purpose is to be placed in the type parameter of a Ptr
type. The return type of the ccall
is declared as Ptr{gsl_permutation}
, since the memory allocated and pointed to by output_ptr
is controlled by C.
The input n
is passed by value, and so the function's input signature is simply declared as (Csize_t,)
without any Ref
or Ptr
necessary. (If the wrapper was calling a Fortran function instead, the corresponding function input signature would instead be (Ref{Csize_t},)
, since Fortran variables are passed by pointers.) Furthermore, n
can be any type that is convertible to a Csize_t
integer; the ccall
implicitly calls Base.cconvert(Csize_t, n)
.
Here is a second example wrapping the corresponding destructor:
# The corresponding C signature is
# void gsl_permutation_free (gsl_permutation * p);
function permutation_free(p::Ref{gsl_permutation})
ccall(
(:gsl_permutation_free, :libgsl), # name of C function and library
Cvoid, # output type
(Ref{gsl_permutation},), # tuple of input types
p # name of Julia variable to pass in
)
end
Here, the input p
is declared to be of type Ref{gsl_permutation}
, meaning that the memory that p
points to may be managed by Julia or by C. A pointer to memory allocated by C should be of type Ptr{gsl_permutation}
, but it is convertible using Base.cconvert
and therefore
Now if you look closely enough at this example, you may notice that it is incorrect, given our explanation above of preferred declaration types. Do you see it? The function we are calling is going to free the memory. This type of operation cannot be given a Julia object (it will crash or cause memory corruption). Therefore, it may be preferable to declare the p
type as Ptr{gsl_permutation }
, to make it harder for the user to mistakenly pass another sort of object there than one obtained via gsl_permutation_alloc
.
If the C wrapper never expects the user to pass pointers to memory managed by Julia, then using p::Ptr{gsl_permutation}
for the method signature of the wrapper and similarly in the ccall
is also acceptable.
Here is a third example passing Julia arrays:
# The corresponding C signature is
# int gsl_sf_bessel_Jn_array (int nmin, int nmax, double x,
# double result_array[])
function sf_bessel_Jn_array(nmin::Integer, nmax::Integer, x::Real)
if nmax < nmin
throw(DomainError())
end
result_array = Vector{Cdouble}(undef, nmax - nmin + 1)
errorcode = ccall(
(:gsl_sf_bessel_Jn_array, :libgsl), # name of C function and library
Cint, # output type
(Cint, Cint, Cdouble, Ref{Cdouble}),# tuple of input types
nmin, nmax, x, result_array # names of Julia variables to pass in
)
if errorcode != 0
error("GSL error code $errorcode")
end
return result_array
end
The C function wrapped returns an integer error code; the results of the actual evaluation of the Bessel J function populate the Julia array result_array
. This variable is declared as a Ref{Cdouble}
, since its memory is allocated and managed by Julia. The implicit call to Base.cconvert(Ref{Cdouble}, result_array)
unpacks the Julia pointer to a Julia array data structure into a form understandable by C.
Fortran 向けラッパーの例
The following example utilizes ccall to call a function in a common Fortran library (libBLAS) to computes a dot product. Notice that the argument mapping is a bit different here than above, as we need to map from Julia to Fortran. On every argument type, we specify Ref
or Ptr
. This mangling convention may be specific to your fortran compiler and operating system, and is likely undocumented. However, wrapping each in a Ref
(or Ptr
, where equivalent) is a frequent requirement of Fortran compiler implementations:
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,
(Ref{Int32}, Ptr{Float64}, Ref{Int32}, Ptr{Float64}, Ref{Int32}),
n, DX, incx, DY, incy)
return product
end
Garbage Collection Safety
When passing data to a ccall
, it is best to avoid using the pointer
function. Instead define a convert method and pass the variables directly to the ccall
. ccall
automatically arranges that all of its arguments will be preserved from garbage collection until the call returns. If a C API will store a reference to memory allocated by Julia, after the ccall
returns, you must arrange that the object remains visible to the garbage collector. The suggested way to handle this is to make a global variable of type Array{Ref,1}
to hold these values, until the C library notifies you that it is finished with them.
Whenever you have created a pointer to Julia data, you must ensure the original data exists until you are done with using the pointer. Many methods in Julia such as unsafe_load
and String
make copies of data instead of taking ownership of the buffer, so that it is safe to free (or alter) the original data without affecting Julia. A notable exception is unsafe_wrap
which, for performance reasons, shares (or can be told to take ownership of) the underlying buffer.
The garbage collector does not guarantee any order of finalization. That is, if a
contained a reference to b
and both a
and b
are due for garbage collection, there is no guarantee that b
would be finalized after a
. If proper finalization of a
depends on b
being valid, it must be handled in other ways.
Non-constant Function Specifications
A (name, library)
function specification must be a constant expression. However, it is possible to use computed values as function names by staging through eval
as follows:
@eval ccall(($(string("a", "b")), "lib"), ...
This expression constructs a name using string
, then substitutes this name into a new ccall
expression, which is then evaluated. Keep in mind that eval
only operates at the top level, so within this expression local variables will not be available (unless their values are substituted with $
). For this reason, eval
is typically only used to form top-level definitions, for example when wrapping libraries that contain many similar functions. A similar example can be constructed for @cfunction
.
However, doing this will also be very slow and leak memory, so you should usually avoid this and instead keep reading. The next section discusses how to use indirect calls to efficiently accomplish a similar effect.
Indirect Calls
The first argument to ccall
can also be an expression evaluated at run time. In this case, the expression must evaluate to a Ptr
, which will be used as the address of the native function to call. This behavior occurs when the first ccall
argument contains references to non-constants, such as local variables, function arguments, or non-constant globals.
For example, you might look up the function via dlsym
, then cache it in a shared reference for that session. For example:
macro dlsym(func, lib)
z = Ref{Ptr{Cvoid}}(C_NULL)
quote
let zlocal = $z[]
if zlocal == C_NULL
zlocal = dlsym($(esc(lib))::Ptr{Cvoid}, $(esc(func)))::Ptr{Cvoid}
$z[] = $zlocal
end
zlocal
end
end
end
mylibvar = Libdl.dlopen("mylib")
ccall(@dlsym("myfunc", mylibvar), Cvoid, ())
Closure cfunctions
The first argument to @cfunction
can be marked with a $
, in which case the return value will instead be a struct CFunction
which closes over the argument. You must ensure that this return object is kept alive until all uses of it are done. The contents and code at the cfunction pointer will be erased via a finalizer
when this reference is dropped and atexit. This is not usually needed, since this functionality is not present in C, but can be useful for dealing with ill-designed APIs which don't provide a separate closure environment parameter.
function qsort(a::Vector{T}, cmp) where T
isbits(T) || throw(ArgumentError("this method can only qsort isbits arrays"))
callback = @cfunction $cmp Cint (Ref{T}, Ref{T})
# Here, `callback` isa Base.CFunction, which will be converted to Ptr{Cvoid}
# (and protected against finalization) by the ccall
ccall(:qsort, Cvoid, (Ptr{T}, Csize_t, Csize_t, Ptr{Cvoid}),
a, length(a), Base.elsize(a), callback)
# We could instead use:
# GC.@preserve callback begin
# use(Base.unsafe_convert(Ptr{Cvoid}, callback))
# end
# if we needed to use it outside of a `ccall`
return a
end
Closing a Library
It is sometimes useful to close (unload) a library so that it can be reloaded. For instance, when developing C code for use with Julia, one may need to compile, call the C code from Julia, then close the library, make an edit, recompile, and load in the new changes. One can either restart Julia or use the Libdl
functions to manage the library explicitly, such as:
lib = Libdl.dlopen("./my_lib.so") # Open the library explicitly.
sym = Libdl.dlsym(lib, :my_fcn) # Get a symbol for the function to call.
ccall(sym, ...) # Use the pointer `sym` instead of the (symbol, library) tuple (remaining arguments are the
same). Libdl.dlclose(lib) # Close the library explicitly.
Note that when using ccall
with the tuple input (e.g., ccall((:my_fcn, "./my_lib.so"), ...)
), the library is opened implicitly and it may not be explicitly closed.
Calling Convention
The second argument to ccall
can optionally be a calling convention specifier (immediately preceding return type). Without any specifier, the platform-default C calling convention is used. Other supported conventions are: stdcall
, cdecl
, fastcall
, and thiscall
(no-op on 64-bit Windows). For example (from base/libc.jl
) we see the same gethostname
ccall
as above, but with the correct signature for Windows:
hn = Vector{UInt8}(undef, 256)
err = ccall(:gethostname, stdcall, Int32, (Ptr{UInt8}, UInt32), hn, length(hn))
For more information, please see the LLVM Language Reference.
There is one additional special calling convention llvmcall
, which allows inserting calls to LLVM intrinsics directly. This can be especially useful when targeting unusual platforms such as GPGPUs. For example, for CUDA, we need to be able to read the thread index:
ccall("llvm.nvvm.read.ptx.sreg.tid.x", llvmcall, Int32, ())
As with any ccall
, it is essential to get the argument signature exactly correct. Also, note that there is no compatibility layer that ensures the intrinsic makes sense and works on the current target, unlike the equivalent Julia functions exposed by Core.Intrinsics
.
Accessing Global Variables
Global variables exported by native libraries can be accessed by name using the cglobal
function. The arguments to cglobal
are a symbol specification identical to that used by ccall
, and a type describing the value stored in the variable:
julia> cglobal((:errno, :libc), Int32)
Ptr{Int32} @0x00007f418d0816b8
The result is a pointer giving the address of the value. The value can be manipulated through this pointer using unsafe_load
and unsafe_store!
.
This errno
symbol may not be found in a library named "libc", as this is an implementation detail of your system compiler. Typically standard library symbols should be accessed just by name, allowing the compiler to fill in the correct one. Also, however, the errno
symbol shown in this example is special in most compilers, and so the value seen here is probably not what you expect or want. Compiling the equivalent code in C on any multi-threaded-capable system would typically actually call a different function (via macro preprocessor overloading), and may give a different result than the legacy value printed here.
Accessing Data through a Pointer
The following methods are described as "unsafe" because a bad pointer or type declaration can cause Julia to terminate abruptly.
Given a Ptr{T}
, the contents of type T
can generally be copied from the referenced memory into a Julia object using unsafe_load(ptr, [index])
. The index argument is optional (default is 1), and follows the Julia-convention of 1-based indexing. This function is intentionally similar to the behavior of getindex
and setindex!
(e.g. []
access syntax).
The return value will be a new object initialized to contain a copy of the contents of the referenced memory. The referenced memory can safely be freed or released.
If T
is Any
, then the memory is assumed to contain a reference to a Julia object (a jl_value_t*
), the result will be a reference to this object, and the object will not be copied. You must be careful in this case to ensure that the object was always visible to the garbage collector (pointers do not count, but the new reference does) to ensure the memory is not prematurely freed. Note that if the object was not originally allocated by Julia, the new object will never be finalized by Julia's garbage collector. If the Ptr
itself is actually a jl_value_t*
, it can be converted back to a Julia object reference by unsafe_pointer_to_objref(ptr)
. (Julia values v
can be converted to jl_value_t*
pointers, as Ptr{Cvoid}
, by calling pointer_from_objref(v)
.)
The reverse operation (writing data to a Ptr{T}
), can be performed using unsafe_store!(ptr, value, [index])
. Currently, this is only supported for primitive types or other pointer-free (isbits
) immutable struct types.
Any operation that throws an error is probably currently unimplemented and should be posted as a bug so that it can be resolved.
If the pointer of interest is a plain-data array (primitive type or immutable struct), the function unsafe_wrap(Array, ptr,dims, own = false)
may be more useful. The final parameter should be true if Julia should "take ownership" of the underlying buffer and call free(ptr)
when the returned Array
object is finalized. If the own
parameter is omitted or false, the caller must ensure the buffer remains in existence until all access is complete.
Arithmetic on the Ptr
type in Julia (e.g. using +
) does not behave the same as C's pointer arithmetic. Adding an integer to a Ptr
in Julia always moves the pointer by some number of bytes, not elements. This way, the address values obtained from pointer arithmetic do not depend on the element types of pointers.
Thread-safety
Some C libraries execute their callbacks from a different thread, and since Julia isn't thread-safe you'll need to take some extra precautions. In particular, you'll need to set up a two-layered system: the C callback should only schedule (via Julia's event loop) the execution of your "real" callback. To do this, create an AsyncCondition
object and wait
on it:
cond = Base.AsyncCondition()
wait(cond)
The callback you pass to C should only execute a ccall
to :uv_async_send
, passing cond.handle
as the argument, taking care to avoid any allocations or other interactions with the Julia runtime.
Note that events may be coalesced, so multiple calls to uv_async_send
may result in a single wakeup notification to the condition.
More About Callbacks
For more details on how to pass callbacks to C libraries, see this blog post.
C++
For direct C++ interfacing, see the Cxx package. For tools to create C++ bindings, see the CxxWrap package.
C と Julia の両方の非ライブラリ関数呼び出しはインライン化できるため、共有ライブラリ関数の呼び出しよりもオーバーヘッドが少ない場合があります。 ここで重要なのは、Julia 外の関数コールの実際のコストは、ネイティブ実行のコストとほぼ同じであるということです。
The Clang package can be used to auto-generate Julia code from a C header file.