Calling C and Fortran Code
Though most code can be written in Julia, there are many high-quality, mature libraries for numerical computing already written in C and Fortran. To allow easy use of this existing code, Julia makes it simple and efficient to call C and Fortran functions. Julia has a “no boilerplate” philosophy: functions can be called directly from Julia without any “glue” code, code generation, or compilation — even from the interactive prompt. This is accomplished in three steps:- Load a shared library and create a handle to it.
- Lookup a library function by name, getting a handle to it.
- Call the library function using the built-in ccall function.
Shared libraries are loaded with dlopen function, which provides access to the functionality of the POSIX dlopen(3) call: it locates a shared library binary and loads it into the process’ memory allowing the program to access functions and variables contained in the library. The following call loads the standard C library, and stores the resulting handle in a Julia variable called libc:
libc = dlopen("libc")
libc_clock = dlsym(libc, :clock)
- Function reference from dlsym — a value of type Ptr{Void}.
- Return type, which may be any bits type, including Int32, Int64, Float64, or Ptr{T} for any type parameter T, indicating a pointer to values of type T, or just Ptr for void* “untyped pointer” values.
- A tuple of input types, like those allowed for the return type.
- The following arguments, if any, are the actual argument values passed to the function.
julia> t = ccall(dlsym(libc, :clock), Int32, ())
5380445
julia> typeof(ans)
Int32
julia> path = ccall(dlsym(libc, :getenv), Ptr{Uint8}, (Ptr{Uint8},), "SHELL")
Ptr{Uint8} @0x00007fff5fbfd670
julia> cstring(path)
"/bin/zsh"
julia> (Ptr{Uint8})
Ptr{Uint8}
julia> (Ptr{Uint8},)
(Ptr{Uint8},)
function getenv(var::String)
val = ccall(dlsym(libc, :getenv),
Ptr{Uint8}, (Ptr{Uint8},), cstring(var))
if val == C_NULL
error("getenv: undefined variable: ", var)
end
cstring(val)
end
julia> getenv("SHELL")
"/bin/zsh"
julia> getenv("FOOBAR")
getenv: undefined variable: FOOBAR
function gethostname()
hostname = Array(Uint8, 128)
ccall(dlsym(libc, :gethostname), Int32,
(Ptr{Uint8}, Ulong),
hostname, length(hostname))
return cstring(convert(Ptr{Uint8}, hostname))
end
When calling a Fortran function, all inputs must be passed by reference.
A prefix & is used to indicate that a pointer to a scalar argument should be passed instead of the scalar value itself. The following example computes a dot product using a BLAS function.
libBLAS = dlopen("libLAPACK")
function compute_dot(DX::Vector, DY::Vector)
assert(length(DX) == length(DY))
n = length(DX)
incx = incy = 1
product = ccall(dlsym(libBLAS, :ddot_),
Float64,
(Ptr{Int32}, Ptr{Float64}, Ptr{Int32}, Ptr{Float64}, Ptr{Int32}),
&n, DX, &incx, DY, &incy)
return product
end
Note that no C header files are used anywhere in the process. Currently, it is not possible to pass structs and other non-primitive types from Julia to C libraries. However, C functions that generate and use opaque structs types by passing around pointers to them can return such values to Julia as Ptr{Void}, which can then be passed to other C functions as Ptr{Void}. 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.
Mapping C Types to Julia
Julia automatically inserts calls to the convert function to convert each argument to the specified type. For example, the following call:ccall(dlsym(libfoo, :foo), Void, (Int32, Float64),
x, y)
ccall(dlsym(libfoo, :foo), Void, (Int32, Float64),
convert(Int32, x), convert(Float64, y))
Array conversions
When an Array is passed to C as a Ptr argument, it is “converted” simply by taking the address of the first element. This is done in order to avoid copying arrays unnecessarily, and to tolerate the slight mismatches in pointer types that are often encountered in C APIs (for example, passing a Float64 array to a function that operates on uninterpreted bytes).Therefore, if an Array contains data in the wrong format, it will have to be explicitly converted using a call such as int32(a).
Type correspondences
On all systems we currently support, basic C/C++ value types may be translated to Julia types as follows.System-independent:
- bool ⟺ Bool
- char ⟺ Uint8
- signed char ⟺ Int8
- unsigned char ⟺ Uint8
- short ⟺ Int16
- unsigned short ⟺ Uint16
- int ⟺ Int32
- usigned int ⟺ Uint32
- long long ⟺ Int64
- usigned long long ⟺ Uint64
- float ⟺ Float32
- double ⟺ Float64
A C function declared to return Void will give nothing in Julia.
System-dependent:
- long ⟺ Int
- unsigned long ⟺ Uint
- size_t ⟺ Uint
- wchar_t ⟺ Char
C functions that take an arguments 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);
argv = [ "a.out", "arg1", "arg2" ]
ccall(:main, Int32, (Int32, Ptr{Ptr{Uint8}}), length(argv), argv)
No comments:
Post a Comment
Thank you