Calling code in C and Fortran
Although you can write almost any code on Julia, there are many ready-made, developed libraries for numerical calculations in C and Fortran. To make this existing code easy to use, Julia supports efficient function calls in C and Fortran. Julia uses the "no stereotypes" principle: functions can be called directly from Julia without linking code, code generation, or compilation — even from the interactive command line. To do this, it is enough to make the appropriate call using a macro. @ccall
(or less convenient syntax ccall
; see the syntax section ccall
).
The code being called must be available as a shared library. Most C and Fortran libraries are supplied as already compiled shared libraries, but if you compile the code yourself using GCC (or Clang), you must use the -shared
and -fPIC
options. The machine instructions generated by the Julia JIT compiler are similar to their own C call, so the total costs will eventually be the same as when calling a library function from code in C. [1]
Fortran compilers are by default https://en.wikipedia.org/wiki/Name_mangling#Fortran [generate adjusted names] (for example, convert function names to lowercase or uppercase, often adding an underscore at the end). Therefore, to call a function in Fortran, you must pass a name that has been adjusted according to the rules used in your Fortran compiler. In addition, when calling a Fortran function, all input data must be passed as pointers to values placed on the "heap" or stack. This applies not only to arrays and other mutable objects, which are usually placed on the "heap", but also to scalar values, such as integers or floating points, which are usually placed on the stack and passed in registers according to the C or Julia calling conventions.
Syntax @ccall
to create a library function call looks like this:
@ccall library.function_name(argvalue1::argtype1, ...)::returntype
@ccall function_name(argvalue1::argtype1, ...)::returntype
@ccall $function_pointer(argvalue1::argtype1, ...)::returntype
where library
is a string constant or literal (however, see the section Non-consistent function specifications below). The library can be omitted, in which case the function name is resolved in the current process. This form can be used to call library functions in C, functions in the Julia runtime, or functions in a Julia-related application. You can also specify the full path to the library. In addition, you can also use @ccall
to call the function pointer $function_pointer
, for example, returned by `Libdl.dlsym'. The `argtype`s correspond to the signature of the C function, and the `argvalue`s are the actual values of the arguments to be passed to the function.
For information about the C and Julia type mapping, see below. |
The following is a simple but fully functional example of calling the clock
function from the C standard library on most Unix-like systems.
julia> t = @ccall clock()::Int32
2292761
julia> typeof(t)
Int32
The clock function takes no arguments and returns an Int32 value. Calling the 'getenv` function, which returns a pointer to the value of an environment variable, should look like this:
julia> path = @ccall getenv("SHELL"::Cstring)::Cstring
Cstring(@0x00007fff5fbffc45)
julia> unsafe_string(path)
"/bin/bash"
In practice, especially when reuse is required, calls to @ccall
usually consist of Julia functions that set arguments and then check for errors, as prescribed by a function in C or Fortran. If an error occurs, the usual Julia exception is thrown. This is especially important for the well-known reason that the C and Fortran APIs report error status differently. For example, the getenv
function from the C library includes the following Julia function, which is a simplified version of the definition from https://github.com/JuliaLang/julia/blob/master/base/env.jl [env.jl
]:
function getenv(var::AbstractString)
val = @ccall getenv(var::Cstring)::Cstring
if val == C_NULL
error("getenv: undefined variable: ", var)
end
return unsafe_string(val)
end
The C function `getenv' reports an error by returning `C_NULL', however, other standard C functions may do this differently, such as returning --1, 0, 1 and other special values. If the caller tries to get a non-existent environment variable, this shell throws an exception, which reports the problem.:
julia> getenv("SHELL")
"/bin/bash"
julia> getenv("FOOBAR")
ERROR: getenv: undefined variable: FOOBAR
Here is a slightly more complex example that defines the hostname of a local computer.
function gethostname()
hostname = Vector{UInt8}(undef, 256) # MAXHOSTNAMELEN
err = @ccall gethostname(hostname::Ptr{UInt8}, sizeof(hostname)::Csize_t)::Int32
Base.systemerror("gethostname", err != 0)
hostname[end] = 0 # обеспечиваем завершающий нулевой символ
return GC.@preserve hostname unsafe_string(pointer(hostname))
end
In this example, an array of bytes is first placed in memory. Then the gethostname
function from the C library is called to fill the array with the hostname. Finally, a pointer to the buffer with the hostname is taken (it is assumed that this is a C string with a trailing null character) and converted to the Julia string.
C libraries often require the caller to allocate a memory area that will be passed to the called object to fill. In Julia, an uninitialized array is usually created for this purpose, and a pointer to the data is passed to the C function. That’s why the Cstring
type is not used here.: since the array is not initialized, it can contain zero bytes. When converting to Cstring
, the presence of zero bytes is checked as part of the @ccall
call, which may cause a conversion error.
Dereferencing the pointer pointer(hostname)
using unsafe_string
is an unsafe operation, as it requires access to the memory area allocated for `hostname', which may already have been cleared by the garbage collector. The macro `GC.@preserve ` blocks such cleanup and thus prevents access to an invalid memory area.
Finally, here is an example of specifying a library as a path. We are creating a shared library with the following contents:
#include <stdio.h>
void say_y(int y)
{
printf("Hello from C: got y = %d.\n", y);
}
and we compile it using the gcc -fPIC -shared -o' command. mylib.so mylib.c
. After that, you can call it by specifying the path (absolute) as the library name.:
julia> @ccall "./mylib.so".say_y(5::Cint)::Cvoid
Hello from C: got y = 5.
Creating Julia function pointers compatible with C
Julia functions can be passed to unmanaged C functions that take function pointers as arguments. For example, this way you can ensure compliance with the prototype C of the following type.
typedef returntype (*functiontype)(argumenttype, ...)
The macro @cfunction
creates a C-compatible function pointer to call the Julia function. Method @cfunction
has the following arguments:
-
The Julia function
-
The return type of the function
-
A tuple of input types corresponding to the function signature
Just like in the case of `@ccall', the return type and input types must be literal constants. |
Currently, only the default C calling convention for the platform is supported. This means that the pointers created by the macro |
The callback functions provided by |
A classic example is the `qsort' function from the C standard library, declared as follows.
void qsort(void *base, size_t nitems, size_t size,
int (*compare)(const void*, const void*));
The base
argument is a pointer to an array with a length of nitems', each element of which has a size of `size
bytes. 'compare` is a callback function that takes pointers to two elements (a
and b
) and returns an integer greater than or less than zero if element a
should be before or after element b
(or zero if any order is allowed).
Now let’s assume that Julia has a one-dimensional array of values A
that needs to be sorted using the qsort
function (rather than the built-in Julia sort
function). Before calling qsort
and passing arguments, you need to write a comparison function.:
julia> function mycompare(a, b)::Cint
return (a < b) ? -1 : ((a > b) ? +1 : 0)
end;
'qsort` expects a comparison function that returns a value of type C `int', so we annotate the returned type as `Cint'.
To pass this function to C, we get its address using the macro @cfunction
:
julia> mycompare_c = @cfunction(mycompare, Cint, (Ref{Cdouble}, Ref{Cdouble}));
The macro '@cfunction` accepts three arguments: the Julia function (mycompare'), the return type (`Cint
), and a literal tuple of input argument types, in this case for sorting an array of elements of type Cdouble' (`Float64
).
In the final form, the `qsort' call looks like this.
julia> A = [1.3, -2.7, 4.4, 3.1];
julia> @ccall qsort(A::Ptr{Cdouble}, length(A)::Csize_t, sizeof(eltype(A))::Csize_t, mycompare_c::Ptr{Cvoid})::Cvoid
julia> A
4-element Vector{Float64}:
-2.7
1.3
3.1
4.4
As shown in the example, the original Julia A
array is now sorted.: [-2.7, 1.3, 3.1, 4.4]
. Please note that Julia performs array conversion to Ptr{Cdouble}
, calculating the size of the element type in bytes, etc.
For interest, try inserting the string println("mycompare($a, $b)")
into mycompare'. This will allow you to see what comparisons the `qsort
function makes (and check if it actually calls the passed Julia function).
Comparison of types C and Julia
It is extremely important that the type declarations in C and Julia match exactly. Due to inconsistencies, code that works correctly on one system may fail or produce uncertain results on another.
Please note that the C header files are not used in the process of calling functions in C: you are responsible for matching the types and signatures of Julia calls to the C header file[using https://github.com/ihnorton/Clang.jl [of the Clang package], you can automatically create Julia code based on the C header file].
Automatic type conversion
Julia automatically adds function calls Base.cconvert
to convert each argument to the specified type. For example, the challenge:
@ccall "libfoo".foo(x::Int32, y::Float64)::Cvoid
it will be executed as if it had the following format:
c_x = Base.cconvert(Int32, x)
c_y = Base.cconvert(Float64, y)
GC.@preserve c_x c_y begin
@ccall "libfoo".foo(
Base.unsafe_convert(Int32, c_x)::Int32,
Base.unsafe_convert(Float64, c_y)::Float64
)::Cvoid
end
Function Base.cconvert
usually just calls convert
, but it can be redefined so that it returns another arbitrary object that is better suited for transmission to C. This way, all memory areas that the C code will access should be allocated. For example, in this way, an array of objects (say, strings) is converted into an array of pointers.
Base.unsafe_convert
performs conversion to types Ptr
. This operation is considered unsafe because converting an object to an unmanaged pointer may make it inaccessible to the garbage collector, which will cause it to be destroyed ahead of time.
Matching types
First, let’s look at a number of important terms related to types in Julia.
Syntax or keyword | Example | Description |
---|---|---|
|
|
Leaf Type: A set of related data, which includes a type label, is managed by the Julia garbage collector and is determined by the object identifier. To create an instance of the final type, the type parameters must be fully defined (the |
|
|
Supertype: A non-finite type that cannot be instantiated, but which can be used to describe a group of types. |
|
|
Type Parameter: Type specialization (usually used for dispatching or optimizing storage). |
Type variable (TypeVar): The 'T` element in the type parameter declaration is called TypeVar (short for type variable — type variable). |
||
|
|
Primitive Type: A type that has no fields, but has a certain size. It is stored and determined by value. |
|
|
Struct: A type with all fields defined as constants. It is determined by value and can be stored with a type label. |
|
Bit type (Is-Bits): a primitive type ( |
|
|
|
Single type (Singleton): a finite type or struct without fields. |
|
|
Tuple: An immutable data structure similar to an anonymous structure type or an array of constants. It is represented by an array or a struct. |
Bit types
Special attention should be paid to a number of special types, the features of which cannot be implemented in other types.:
-
Float32
Exactly corresponds to the type
float
in C (orREAL*4
in Fortran). -
Float64
Exactly corresponds to the type
double
in C (orREAL*8
in Fortran). -
ComplexF32
Exactly corresponds to the type
complex float
in C (orCOMPLEX*8
in Fortran). -
ComplexF64
Exactly corresponds to the type
complex double
in C (orCOMPLEX*16
in Fortran). -
Signed
Exactly corresponds to the
signed
annotation in C (or any type ofINTEGER
in Fortran). Any Julia type that is not a subtypeSigned
, is considered an unsigned type. -
Ref{T}
It works as a pointer
Ptr{T}
, which can manage its memory area through the Julia garbage collector. -
Array{T,N}
When an array is passed to C as an argument
Ptr{T}
, it is not cast using reinterpret cast: Julia requires matching the type of array elements to typeT
and passing the address of the first element.Therefore, if the
Array
contains data in an incorrect format, it must be explicitly converted using a call such as `trunc.(Int32, A)'.To pass the array
A
as a pointer of another type, without first converting the data (for example, to pass an array of typeFloat64
to a function that operates with uninterpreted bytes), you can declare the argument asPtr{Cvoid}
.If the array is of type eltype
Ptr{T}
is passed as an argumentPtr{Ptr{T}}
, functionBase.cconvert
will first try to create a null-terminated copy of it, replacing each element with its versionBase.cconvert
. This allows, for example, to pass an array ofargv
pointers of typeVector'.{String}
in the type argumentPtr{Ptr{Cchar}}
.
In all currently supported systems, the basic value types are C/C++ they can be converted to Julia types in the following way. For each type of C, there is also a corresponding Julia type with the same name, but with the prefix C. This can help when writing portable code (and don’t forget that the type int
in C is different from the type Int
in Julia).
System-independent types
Name in C | The name in Fortran | The standard alias in Julia | The basic type is Julia |
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|||
|
|
||
|
|
||
|
|
'Cstring', if terminated with a zero, or |
|
|
|
||
'jl_value_t*` (any type of Julia) |
|
||
|
|
||
|
Not supported |
||
|
|
||
|
|
Type 'Cstring` is, in fact, a synonym for Ptr{UInt8}
, with the exception that the conversion to Cstring
causes an error if the Julia string contains embedded null characters (such a string would be automatically truncated if the C routine regarded the null character as terminating). If you pass char*
to a C routine that does not expect trailing null characters (for example, because you explicitly pass the string length), or if you are sure that the Julia string does not contain null characters and want to skip checking, you can use Ptr' as the argument type.{UInt8}
. Cstring
can also be used as a return type. ccall
, but no additional checks are performed in this case.: this is done only to increase the readability of the call.
System-dependent types
Name in C | The standard alias in Julia | The basic type is Julia |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
When calling Fortran code, all input data must be passed as pointers to values placed on the "heap" or stack, so the specifications of all the relevant types listed above must be enclosed in an additional |
For string arguments ( |
The type |
For the arguments |
C functions that take an argument of type
you can call Julia using the following code:
|
For Fortran functions that accept variable-length strings of the type
you can call it using the following Julia code, where the string lengths are added at the end of the list:
|
Fortran compilers can also add other hidden arguments for pointers and arrays of the intended shape ( |
A C function declared as returning |
Matching structure types
Composite types such as struct' in C or `TYPE
in Fortran 90 (or STRUCTURE
and RECORD
in some versions of Fortran 77) can be represented in Julia by creating a definition of struct
with the same field structure.
When used recursively, the types of isbits' are stored directly. All other types are stored as pointers to data. When representing a structure that is used by value inside another structure in C, never try to copy fields manually: the alignment of the fields will not be preserved. Instead, declare the structure type `isbits
and use it. Structures without a name cannot be converted to Julia.
Packaged structures and join declarations are not supported in Julia.
You can roughly recreate the union
object if the largest field is initially known (you may need to add padding bytes). When converting fields to Julia, declare the field in Julia to have only this type.
Arrays of parameters can be expressed using `NTuple'. For example, a structure written in C as follows:
struct B {
int A[3];
};
b_a_2 = B.A[2];
you can write it in Julia like this:
struct B
A::NTuple{3, Cint}
end
b_a_2 = B.A[3] # обратите внимание на различия в индексировании (в Julia от 1, в C от 0)
Arrays of unknown size (variable-length structures conforming to the C99 standard and specified as []
or [0]
) are not directly supported. Often, the best way to work with them is to operate directly with byte offsets. For example, the C library declares a string type and returns a pointer to it.:
struct String {
int strlen;
char data[];
};
In Julia, you can access individual elements to create a copy of this string.:
str = from_c::Ptr{Cvoid}
len = unsafe_load(Ptr{Cint}(str))
unsafe_string(str + Core.sizeof(Cint), len)
Type Parameters
The type arguments passed to the '@ccall` function and the @cfunction
macro are calculated statically when the method in which they are used is determined. Therefore, such arguments must be a literal tuple, not a variable, and cannot refer to local variables.
This limitation may seem strange, but don’t forget that, unlike Julia, C is not a dynamic language, and its functions can only accept argument types that have a static, fixed signature.
However, although the type structure must be statically specified to define the required ABI C interface, static function parameters are considered part of this static environment. Static function parameters can be used as type parameters in the call signature if they do not affect the type structure. For example, calling f(x::T) where {T} = @ccall valid(x::Ptr{T})::Ptr{T}
is acceptable, since Ptr
is always a primitive type the size of a machine word. In turn, the call g(x::T) where {T} = @ccall notvalid(x::T)::T
is invalid because the structure of type T
is not statically specified.
SIMD Values
Note. This feature is currently implemented only on 64-bit x86 and AArch64 platforms.
If the argument or return value of the subroutine is in C or C++ they belong to the SIMD machine type, the corresponding type in Julia will be a homogeneous tuple of VecElement
elements, which naturally maps to the SIMD type. In particular:
The tuple must be the same size as the SIMD type. For example, the tuple representing
__m128
on the x86 platform must be 16 bytes in size.The type of the tuple elements must be an instance of
VecElement{T}
, whereT
is a primitive type of 1, 2, 4, or 8 bytes in size.
Let’s say there is a C subroutine that uses internal AVX instructions:
#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 the ccall
function:
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 "libdist".dist(a::m256, b::m256)::m256
end
println(call_dist(a,b))
The host computer must have the necessary SIMD registers. For example, the above code will not work on hosts without AVX support.
Memory ownership
malloc
/free
To allocate memory for objects and free it, you need to call the appropriate cleanup routines from the libraries you use, just like in any C program. Do not try to free the memory occupied by an object that was obtained from the C library using the function Libc.free
in Julia. This may cause the free
function to be called from the wrong library and the process to crash. The reverse operation (an attempt to delete from memory an object that was created in Julia’s code in an external library) is also unacceptable.
The difference in the use of T
, Ptr{T}
and Ref{T}
In Julia code, which calls external C routines, ordinary data (not pointers) must be declared with type T
inside the @ccall
call, since they are passed by value. For C code that accepts pointers, as a rule, the following should be used as input argument types: Ref{T}
. This allows pointers managed by the Julia or C environment to be used by implicitly calling Base.cconvert
. In contrast, pointers that are returned by a called C function must be declared as having an output type. Ptr{T}
. This means that the memory they point to is managed only by C. Pointers contained in C structures must be represented by fields of type Ptr'.{T}
in Julia structure types, which recreate the internal structure of the corresponding C structures.
In Julia code, which calls external Fortran routines, all input arguments must be declared with the type Ref{T}
, since in Fortran all variables are passed by pointers to memory locations. The return type must be either 'Cvoid` for Fortran routines, or T
for Fortran functions that return type `T'.
Comparing C and Julia functions
Guide to converting arguments to @ccall
and @cfunction
The list of arguments in C is converted to Julia according to the rules listed below.
-
T
, whereT
is one of the primitive types:char
,int
,long
,short
,float
,double
,complex
,enum
or any of their equivalents `typedef'.-
T
, whereT
is the equivalent Julia bit type (according to the table above). -
If 'T` is an enumeration of
enum', the type of the argument must be equivalent to `Cint
or `Cuint'. -
The value of the argument is copied (passed by value).
-
-
struct T
(including typedef for the structure).-
T
, whereT
is the final type of Julia. -
The value of the argument is copied (passed by value).
-
-
void*
-
Depends on how the parameter is used; first it is converted to the desired pointer type, then the equivalent Julia type is determined according to further rules in the list.
-
This argument can be declared as
Ptr{Cvoid}
, if it’s really just an unknown pointer.
-
-
'jl_value_t*`
-
Any
-
The value of the argument must be a valid Julia object.
-
-
jl_value_t* const*
-
Ref{Any}
-
The value of the argument must be a valid Julia object (or 'C_NULL`).
-
Cannot be used for the output parameter unless the user can arrange for the garbage collector to save the object.
-
-
T*
-
Ref{T}
, whereT
is the Julia type corresponding to `T'. -
The value of the argument is copied if it is of the
inlinealloc
type (this includes theisbits
types), otherwise the value must be a valid Julia object.
-
-
T (*)(...)
(for example, a pointer to a function).-
Ptr{Cvoid}
(you may need to use a macro to create this pointer@cfunction
explicitly)
-
-
...
(for example, vararg).-
Currently not supported by the macro `@cfunction'.
-
'va_arg`
-
Is not supported by the
ccall
function or the@cfunction
macro.
-
Guide to converting return types @ccall
and @cfunction
The returned function types in C are converted to Julia according to the rules listed below.
-
void
-
Cvoid
(returns a separate instance ofnothing::Cvoid
)
-
-
T
, whereT
is one of the primitive types:char
,int
,long
,short
,float
,double
,complex
,enum
or any of their equivalents `typedef'.-
T
, whereT
is the equivalent Julia bit type (according to the table above). -
If 'T` is an enumeration of
enum', the type of the argument must be equivalent to `Cint
or `Cuint'. -
The value of the argument is copied (returned by value).
-
-
struct T
(including typedef for the structure).-
T
, whereT
is the final type of Julia. -
The value of the argument is copied (returned by value).
-
-
void*
-
Depends on how the parameter is used; first it is converted to the desired pointer type, then the equivalent Julia type is determined according to further rules in the list.
-
This argument can be declared as
Ptr{Cvoid}
, if it’s really just an unknown pointer.
-
-
'jl_value_t*`
-
Any
-
The value of the argument must be a valid Julia object.
-
-
jl_value_t**
-
Ptr{Any}
(Ref{Any}
cannot be used as a return type).
-
-
T*
-
If the memory is already managed by Julia or it is a non-zero type of
isbits
:-
Ref{T}
, whereT
is the Julia type corresponding to `T'. -
Return type
Ref{Any}
is not allowed; typeAny
is required (corresponds tojl_value_t*
) orPtr{Any}
(corresponds tojl_value_t**
). -
The C code MUST NOT change the contents of the memory area returned by
Ref{T}
ifT
is the type of `isbits'.
-
-
If the memory is managed by C:
-
Ptr{T}
, whereT
is the Julia type corresponding toT
-
-
-
T (*)(...)
(for example, a pointer to a function).-
Ptr{Cvoid}
, to call directly from Julia, pass this pointer to the function@ccall
as the first argument. See the section Indirect calls.
-
Passing pointers to change input data
Since C does not support multiple return values, C functions often accept pointers to data that is subject to change. To implement this in the @ccall
call, you must first encapsulate the value in an object. Ref{T}
of the appropriate type. When passing this Ref
object as an argument, Julia automatically passes a pointer to C for the encapsulated data.:
width = Ref{Cint}(0)
range = Ref{Cfloat}(0)
@ccall foo(width::Ref{Cint}, range::Ref{Cfloat})::Cvoid
After returning control, the contents of the width
and range
variables can be obtained (if it was changed by the foo
function) using width[]
and range[]
, that is, they act as zero-dimensional arrays.
Examples of C shells
Let’s start with a simple example of a C shell that returns the type Ptr
:
mutable struct gsl_permutation
end
# Соответствующая сигнатура в C имеет вид
# gsl_permutation * gsl_permutation_alloc (size_t n);
function permutation_alloc(n::Integer)
output_ptr = @ccall "libgsl".gsl_permutation_alloc(n::Csize_t)::Ptr{gsl_permutation}
if output_ptr == C_NULL # Не удалось выделить память
throw(OutOfMemoryError())
end
return output_ptr
end
In https://www.gnu.org/software/gsl /[GNU scientific library] (here it is assumed that it is available by the name :libgsl
) the opaque pointer gsl_permutation *
is defined as the return type of the C function gsl_permutation_alloc
. Since the user code does not need to access the gsl_permutation
structure, to implement the corresponding Julia shell, it is enough to declare a new type gsl_permutation
, which has no internal fields and whose only purpose is to specify the type Ptr
in the parameter. For the function ccall
the return type Ptr' has been declared{gsl_permutation}
, because the memory area that is allocated using the output_ptr
pointer and to which it points is controlled by the C environment.
The input argument n
is passed by value, so the signature of the input data of the function has a very simple form: ::Csize_t'. There is no need for `Ref
or Ptr'. (If the shell had called the Fortran function instead, the corresponding input data signature would have been `+::Ref{Csize_t}+
, since variables in Fortran are passed by pointers.) Moreover, n
can have any type that can be converted to the integer value Csize_t'; function `ccall
implicitly calls Base.cconvert(Csize_t, n)
.
Here is another example, this time of the shell of the corresponding destructor:
# Соответствующая сигнатура в C имеет вид
# void gsl_permutation_free (gsl_permutation * p);
function permutation_free(p::Ptr{gsl_permutation})
@ccall "libgsl".gsl_permutation_free(p::Ptr{gsl_permutation})::Cvoid
end
Here is a third example in which Julia arrays are passed:
# Соответствующая сигнатура в C имеет вид
# 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 "libgsl".gsl_sf_bessel_Jn_array(
nmin::Cint, nmax::Cint, x::Cdouble, result_array::Ref{Cdouble})::Cint
if errorcode != 0
error("GSL error code $errorcode")
end
return result_array
end
The wrapped function C returns an integer error code, and the results of calculating the Bessel function J are entered into the Julia result_array
array. This variable is declared as Ref{Cdouble}
, since a block of memory is allocated for it and managed by the Julia code. Implicit challenge Base.cconvert(Ref{Cdouble}, result_array)
decompresses Julia’s pointer to the Julia array data structure into a form understandable to C.
An example shell for Fortran
In the following example, using ccall', a function from the Fortran standard library (libBLAS) is called to calculate the scalar product. Please note: the arguments in this case are compared slightly differently than above, since the comparison is made from Julia to Fortran. For each type of argument, `Ref
or Ptr' is specified. This conversion convention may depend on the specific Fortran compiler and operating system and is most likely not reflected in the documentation. However, many Fortran compiler implementations require enclosing each argument type in a `Ref
(or Ptr
, where applicable):
function compute_dot(DX::Vector{Float64}, DY::Vector{Float64})
@assert length(DX) == length(DY)
n = length(DX)
incx = incy = 1
product = @ccall "libLAPACK".ddot(
n::Ref{Int32}, DX::Ptr{Float64}, incx::Ref{Int32}, DY::Ptr{Float64}, incy::Ref{Int32})::Float64
return product
end
Garbage Collection Security
When transferring data to @ccall
, it is better not to use the function pointer
. Instead, define a method Base.cconvert
and pass the variables to @ccall
directly. @ccall
automatically protects all its arguments from garbage collection until the call returns control. If the C API will store a reference to the memory allocated by the Julia code, after returning control to the @ccall
call, it is necessary to ensure that the object is accessible to the garbage collector. The recommended way is to store these values in a global variable of type Array'.{Ref,1}
, until the C library notifies that work with them is completed.
Whenever you create a pointer to Julia data, that data must exist as long as the pointer is in use. Many methods in Julia, for example unsafe load
and String
, create copies of the data instead of taking control of the buffer. This allows you to safely delete (or modify) the source data from memory so that it does not affect the execution of Julia code. An important exception is the method unsafe_wrap
, which, for performance reasons, uses the base buffer in a shared mode (in other words, takes control of it).
The garbage collector does not guarantee any particular order of disposal of objects. In other words, if object a
contains a reference to object b
and both are subject to garbage collection, there is no guarantee that object b
will be eliminated after a
. If object b
must exist to eliminate object a
, a different approach is required.
Inconsistent function specifications
In some cases, the name or path of the required library is unknown in advance and must be determined at runtime. In such cases, the library component specification can be a function call, for example, find_blas().dgemm'. The call expression will be executed when the `ccall
operation is performed. However, it is assumed that once the library location is determined, it does not change, so the result of the call can be cached and reused. Thus, the expression can be executed any number of times, and returning different values can give an indeterminate result.
If even more flexibility is required, calculated values can be used as function names using the function eval
as follows.
@eval @ccall "lib".$(string("a", "b"))()::Cint
This expression generates a name using a string
, and then substitutes it into a new expression @ccall
, which is then evaluated. Note that the eval
function is a high-level function, so local variables will not be available in this expression (unless their values are substituted with $
). For this reason, the eval
function is usually used to create only top-level definitions, for example, when encapsulating libraries containing many similar functions. A similar example is possible for a macro @cfunction
.
However, such code will run very slowly and with memory leaks, so try not to use this option. The next section explains how to achieve the same result using indirect calls.
Indirect calls
The first argument in the @ccall
call can also be an expression evaluated at runtime. The result of such an expression should be a pointer Ptr
, which will be used as the address of the called native function. This behavior occurs when the first argument @ccall
contains references to non-constant values, such as local variables, function arguments, or non-constant global variables.
For example, you can define a function using dlsym
and then cache it in a shared link within a session. For example:
macro dlsym(lib, func)
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(mylibvar, "myfunc"))()::Cvoid
CFUNCTION closure functions
The first argument @cfunction
can be marked with the symbol $
. In this case, the return value will be a struct CFunction
object with an argument closure. This returned object must exist until its use is completed. The contents and code of the cfunction pointer will be destroyed by the function finalizer
after deleting this link and exiting. This is usually not required, since there is no such functionality in C, but it can be useful when working with poorly designed APIs that do not provide a separate parameter for the closure environment.
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})
# Здесь `callback` — это функция Base.CFunction, которая будет преобразована в Ptr{Cvoid}
# (и защищена от ликвидации) посредством ccall
@ccall qsort(a::Ptr{T}, length(a)::Csize_t, Base.elsize(a)::Csize_t, callback::Ptr{Cvoid})
# Вместо этого можно использовать:
# GC.@preserve callback begin
# use(Base.unsafe_convert(Ptr{Cvoid}, callback))
# end
# если функцию необходимо использовать вне вызова `ccall`
return a
end
Closure function |
Closing the library
Sometimes it is useful to close (unload) a library so that it can be downloaded again. For example, when writing C code to be used with Julia, you may need to compile, call the C code from Julia, then close the library, make changes, compile again, and upload new changes. To do this, you can either restart Julia, or use the Libdl
functions to manage the library explicitly, like this.
lib = Libdl.dlopen("./my_lib.so") # Открываем библиотеку явным образом.
sym = Libdl.dlsym(lib, :my_fcn) # Получаем символ для вызываемой функции.
@ccall $sym(...) # Используем указатель `sym` вместо кортежа library.symbol.
Libdl.dlclose(lib) # Закрываем библиотеку явным образом.
Note that when using @ccall
with input data (for example, @ccall'./my_lib.so ".my_fcn(...)::Cvoid
) the library opens implicitly and may not be explicitly closed.
Function calls with a variable number of arguments
To call C functions with a variable number of arguments, you can separate the required arguments from the arguments of a variable number in the argument list with a semicolon (semicolon
). Below is an example of calling the printf
function.
julia> @ccall printf("%s = %d\n"::Cstring ; "foo"::Cstring, foo::Cint)::Cint
foo = 3
8
Interface link:@id ccall-interface[ccall
]
This is another interface that can serve as an alternative to `@ccall'. It is a little less convenient, but it allows you to set challenge agreement.
Method ccall
has the following arguments:
-
Couple
(:function, "library")
(most often),or
the character of the name
:function
or the string of the name"function"
(for characters in the current process or libc library),or
function pointer (for example, from
dlsym
). -
The return type of the function
-
A tuple of input types corresponding to the function signature. In the singleton tuple of argument types, do not forget to put a comma at the end.
-
The actual values of the arguments to be passed to the function, if any; each value is a separate parameter.
Couple |
The remaining parameters are calculated at compile time if the containing method is defined.
The conversion table between the macro and function interfaces is shown below.
@ccall |
ccall |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The Challenge Agreement
The second argument of the ccall
call (immediately before the return type) can be a call agreement specifier (currently, the @ccall
macro does not allow you to specify a call agreement). If no specifier is specified, the C calling convention adopted by default for the platform is used. In addition, the following conventions are supported: stdcall', `cdecl', `fastcall
and thiscall' (not valid on 64-bit Windows systems). For example, this is what a call to the `gethostname`ccall
function looks like, similar to the one above, but with the correct signature for Windows (code taken from `base/libc.jl'):
hn = Vector{UInt8}(undef, 256)
err = ccall(:gethostname, stdcall, Int32, (Ptr{UInt8}, UInt32), hn, length(hn))
For more information, see https://llvm.org/docs/LangRef.html#calling-conventions [LLVM language reference].
There is another special call agreement llvmcall
, which allows you to directly insert calls into internal LLVM instructions. This can be especially useful when writing code for unusual platforms such as GPGPU. For example, for https://llvm.org/docs/NVPTXUsage.html [CUDA] you may need to get a stream index.:
ccall("llvm.nvvm.read.ptx.sreg.tid.x", llvmcall, Int32, ())
As with any ccall call, it is important to get the correct signature of the arguments. Also, note that there is no compatibility wrapper that ensures that the internal instruction is correct and valid for the current purpose, unlike the Julia functions provided by Core.Intrinsics`.
Access to global variables
Global variables provided by native libraries can be accessed by name using the function cglobal
. Function arguments 'cglobal` are a symbolic specification (same as for the call ccall
) and the type of value stored in the variable:
julia> cglobal((:errno, :libc), Int32)
Ptr{Int32} @0x00007f418d0816b8
The result is a pointer to the address of the value. Using this pointer, you can perform operations with the value using functions unsafe load
and unsafe_store!
.
The `errno' symbol may not be present in the libc library: it depends on the features of the compiler implementation for your system. Usually, the symbols of the standard library should be referred to only by name, so that the compiler substitutes the desired symbol. Also, since the `errno' symbol shown in this example is special in most compilers, the meaning may be different in your case. When compiling similar code in C on any system that supports multithreading, another function is likely to be called (by overloading the macro processor), which will give a result different from the value presented here. |
Access to data via a pointer
The methods presented below are unsafe, as an incorrect pointer or type declaration can cause Julia to suddenly stop working.
If the pointer is Ptr{T}
, the contents of type T
, as a rule, can be copied from the memory area to which it refers to the Julia object by calling unsafe load(ptr, [index])
. The index argument is optional (by default it is 1) and is indexed from 1 according to the convention adopted in Julia. The behavior of this function is intentionally made to resemble the behavior of functions getindex
and 'setindex! (for example, the syntax matches `[]).
A new object is returned containing a copy of the contents of the memory area referenced by the pointer. After that, this memory area can be safely released.
If the type of T
is Any
, it is assumed that the memory area contains a reference to the Julia object ('jl_value_t*). The result will be a reference to this object, and the object itself is not copied. In this case, it is necessary to ensure that the object is accessible to the garbage collector (pointers are not taken into account, unlike a new reference) so that the memory is not cleared ahead of time. Please note: if the object was not originally placed in memory by the Julia code, the new object will not be eliminated by the Julia garbage collector. If the object `Ptr
is itself jl_value_t*
, it can be converted back to a reference to the Julia object using the function unsafe_pointer_to_objref(ptr)
. (Julia v
values can be converted to jl_value_t' pointers*`how `+Ptr{Cvoid}+
by calling pointer_from_objref(v)
.)
Reverse operation (writing data to Ptr{T}
) can be performed using the function unsafe_store!(ptr, value, [index])
. Currently, this feature is supported only for primitive types or other immutable types of structures without pointers (isbits
).
If the operation returns an error, it may not have been implemented yet. This should be reported so that we can fix the problem.
If the pointer represents an array of ordinary data (a primitive type or an immutable structure), the function unsafe wrap(Array, ptr,dims, own = false)
may be more useful. The last parameter must be set to true if the Julia environment is to monitor the base buffer and call free(ptr)
after eliminating the returned Array
object. If the 'own` parameter is omitted or set to false, the caller must ensure the buffer exists while it needs to be accessed.
Arithmetic operations with the type Ptr
in Julia (for example, +
) are performed differently than with pointers in C. When adding an integer to Ptr
in Julia, the pointer is shifted by a certain number of _bytes, not elements. Due to this, the address values obtained as a result of arithmetic operations with pointers do not depend on the types of pointer elements.
Thread safety
Some C libraries make callbacks from another thread, and since Julia is not a thread-safe language, additional precautions need to be taken. In particular, you should set up a two-tier system: the C callback should only schedule the execution of a "real" callback (via the Julia event loop). To do this, create an object AsyncCondition
and apply the function to it wait
:
cond = Base.AsyncCondition()
wait(cond)
The callback passed to C should only make the call 'ccall` applied to :uv_async_send
with passing cond.handle
as an argument. There should be no memory allocation or other interactions with the Julia runtime.
Note that events can be combined, so that multiple calls to uv_async_send
can result in a single notification to activate a condition.
Additional information about callbacks
For more information about passing callbacks to C libraries, see https://julialang.org/blog/2013/05/callback [this blog entry].
C++
Tools for creating C bindings++ can be found in the package https://github.com/JuliaInterop/CxxWrap.jl [CxxWrap].