May 14, 2021 Julia
The called code should be in the format of a shared library. M
ost C and Fortran libraries have been compiled into shared libraries. I
f you compile your own code using GCC (or Clang), you need to
-shared
and
-fPIC
Julia calls these libraries at the same cost as the local C language.
When calling shared libraries and functions, use a multi-group format:
(:function, "library")
("function", "library")
function
the name of the export function of C and the library is the name of the shared
library
Shared libraries are resolved by name, and paths are determined by environment variables, sometimes with direct indication.
There are times when there are only function names
:function
"function"
A
t this point, the function name is resolved by the current process.
This form can be used to call C library functions, Julia runtime functions, and functions linked to Julia's app.
Use
ccall
generate library function calls.
ccall
are as follows:
Int32
Int64
Float64
or a pointer to any type of
T
Ptr{T}
Ptr
void*
typeless pointer void
The following example calls the clock in the standard C
clock
julia> t = ccall( (:clock, "libc"), Int32, ())
2292761
julia> t
2292761
julia> typeof(ans)
Int32
clock
function has no arguments and
Int32
type. I
f there is only one type entered, it is often written as a unit of multiple groups, followed by a comma. F
or example, to
getenv
function to get a pointer to an environment variable, you should call this way:
julia> path = ccall( (:getenv, "libc"), Ptr{Uint8}, (Ptr{Uint8},), "SHELL")
Ptr{Uint8} @0x00007fff5fbffc45
julia> bytestring(path)
"/bin/bash"
Note that the parameters of the type plural group must
(Ptr{Uint8},)
(Ptr{Uint8})
T
his
(Ptr{Uint8})
is equivalent
Ptr{Uint8}
and it's not a unit of units that contain
Ptr{Uint8}
julia> (Ptr{Uint8})
Ptr{Uint8}
julia> (Ptr{Uint8},)
(Ptr{Uint8},)
In practice, when you want to provide reusable code, you
ccall
set parameters, and then examine any errors that might occur in the C or Fortran functions as an exception passed to Julia's function caller. I
n the following example,
getenv
C library function is encapsulated
in the Julia function in env.jl:
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
In the example above, if the function caller tries to read a nonexistent environment variable, the encapsulation throws an exception:
julia> getenv("SHELL")
"/bin/bash"
julia> getenv("FOOBAR")
getenv: undefined variable: FOOBAR
The following example is slightly more complex and shows the host name of the local machine:
function gethostname()
hostname = Array(Uint8, 128)
ccall( (:gethostname, "libc"), Int32,
(Ptr{Uint8}, Uint),
hostname, length(hostname))
return bytestring(convert(Ptr{Uint8}, hostname))
end
This example assigns an array of bytes, then calls the C library function
gethostname
fill the array with the host name, take a pointer to the host name buffer, and convert it to a Julia string, which defaults to an empty end C string. C
-library functions typically pass the requested memory from the function caller in this way, and then populate it.
Allocating memory in Julia typically involves building a non-initialized array and passing a pointer to the data to the C function.
When the Fortran function is called, all input must be passed by reference.
&
The prefix description passes a pointer to the gauge parameter, not the gauge itself.
The following example uses the BLAS function to calculate the dot product:
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
The meaning of
&
is different from that in C. A
ny changes to the referenced variable are not visible to Julia.
&
real address operator and can be used
&f(x)
in any
&0
such as . . . and .
Note that C's header file can be placed anywhere during processing. J
ulia's structure and other non-base types cannot be passed to the C library at this time. B
y passing a pointer to generate and use a C function of an opaque structure type, you can return to Julia
Ptr{Void}
which is called by other C functions in the form of
Ptr{Void}
You can allocate and free an object in memory, just like any C program, by calling the corresponding program in the library.
Julia automatically calls
convert
function to convert the argument to the specified type. F
or example:
ccall( (:foo, "libfoo"), Void, (Int32, Float64),
x, y)
will do as follows:
ccall( (:foo, "libfoo"), Void, (Int32, Float64),
convert(Int32, x), convert(Float64, y))
If the calibration value is
&
argument of
Ptr{T}
type, the value is first converted to
T
type.
When an array is passed
Ptr{T}
argument, it is not converted. J
ulia checks only if the element type is
T
then passes the address of the first element.
Doing so avoids unnecessary copying of the entire array.
Therefore,
Array
data in Array is not in the right format, use an explicit transformation, such as
int32(a)
If you want to pass
an array as
a different type of pointer without conversion, either declare the argument as the
Ptr{Void}
type, or
convert(Ptr{T}, pointer(A))
The underlying C/C?type is compared to the Julia type below. E ach C type also has a Julia type with a corresponding name, but is prefixed C. This helps you write easy code (but int in C is different from Int in Julia).
Not related to the system:
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 correctly represents a defined bit type) | Ptr'T' (called in the parameter list using variable_name ) | |
struct T (T correctly represents a defined bit type) | T (called in the argument list using variable_name and called) | |
jl_value_t (any Julia type) | Ptr{Any} |
The Julia type that corresponds to the string parameter
char*
Ptr{Uint8}
ASCIIString
T
here are
char**
the type char in the parameter, and you should use the type
Ptr{Ptr{Uint8}}
Julia. F
or example, the C function:
int main(int argc, char **argv);
This should be called in Julia:
argv = [ "a.out", "arg1", "arg2" ]
ccall(:main, Int32, (Int32, Ptr{Ptr{Uint8}}), length(argv), argv)
wchar_t*
the Julia type is
Ptr{Wchar_t}
and the data can be converted to the original Julia string (equivalent
utf16(s)
wstring(s)
utf32(s)
on
Cwchar_t
Also note that ASCII, UTF-8, UTF-16, and UTC-32 string data end in NUL inside Julia, so it can pass data ending in NUL in the C function without having to make another copy.
The following methods are "unsafe" because a bad pointer or type declaration can cause accidental termination or corruption of any process memory.
Specifies
Ptr{T}
which often
unsafe_ref(ptr, [index])
method to copy
T
from referenced memory to the Julia object.
index
parameter is optional (1 by default), and it is an index value that starts at 1.
This function is similar
getindex()
and
setindex!()
as
[]
The return value is a new object that is initialized and contains a shallow copy of the referenced memory content. The referenced memory is safely freed.
If
T
any
Any
the referenced memory is considered to contain a reference to the Julia object
jl_value_t*
resulting in a reference to the object, which is not copied. C
are needs to be taken to ensure that objects are always visible to the garbage collection mechanism (pointers are not important, important is new references) to ensure that memory is not released prematurely. N
ote that if the memory was not originally requested by Julia, the new object will never be released by Julia's garbage collection mechanism. I
f
Ptr
jl_value_t*
you
unsafe_pointer_to_objref(ptr)
Julia object reference using the ptr. (
You can convert pointer_from_objref value
v
to jl_value_t
jl_value_t*
pointer
Ptr{Void}
by calling the button
pointer_from_objref(v)
)
Inverse operations (writing data to Ptr'T' can
unsafe_store!(ptr, value, [index])
Currently, only bit types and other pointerless
isbits
types are supported.
Now any operation that throws an exception is not yet complete. Let's write a post to report the bug, and someone will fix it.
If the pointer of interest is an array of target data (bit type or imm changeable),
pointer_to_array(ptr,dims,[own])
s own) function is very useful. I
f you want Julia to "control" the underlying buffer
Array
free(ptr)
when the returned Array is released, the last argument should be true.
If the own argument
own
or it is false, the caller needs to ensure that the buffer remains in existence until all reads are over.
Ptr
arithmetic
+
(such as , ) and C's pointer arithmetic are different, and an integer on
Ptr
moves the pointer a distance
byte
instead of an element.
This way, the address obtained from the pointer operation does not depend on the pointer type.
Because C does not support multiple return values, C functions typically modify values with pointers. T
o
ccall
you need to put the values in the appropriate type of array.
When you pass the entire array with
Ptr
Julia automatically passes a C pointer to the value:
width = Cint[0]
range = Cfloat[0]
ccall(:foo, Void, (Ptr{Cint}, Ptr{Cfloat}), width, range)
This is widely used on Julia's LAPACK interface, where info of the
info
is passed to LAPACK by reference, and then returned to success.
When passing data to ccall, it is a good idea to
pointer()
function. Y
ou should define a conversion method that passes variables directly to ccall. c
call is automatically scheduled so that all its parameters are not processed by the garbage collection mechanism until the call returns. I
f the C API wants to store a reference to memory allocated by Julia, when ccall returns, it needs to set itself to keep the object visible to the garbage collection mechanism.
The recommended method is to save these values in a global variable
Array{Any,1}
the C interface informs it that it has been processed.
Whenever a pointer to Julia data is constructed, the raw data must be guaranteed to exist until the pointer is used. M
any methods in
unsafe_ref()
bytestring()
copy the data instead of controlling the buffer, so that the raw data can be safely released (or modified) without affecting Julia.
One exception is that for performance reasons, the
pointer_to_array()
(or control) the underlying buffer.
Garbage collection does not guarantee the order of collection. F
or example,
a
contains a reference to
b
and both are garbage collected, there is no guarantee
b
a
after a.
This needs to be handled in other ways.
(name, library)
should be a constant expression. T
he result of the calculation can be used as a function name by
eval
@eval ccall(($(string("a","b")),"lib"), ...
The expression
string
the name with string and then replaces the name
ccall
expression for evaluation. N
ote
eval
only runs at the top level, so you cannot use a local variable within an expression (unless the value of the
$
with $).
eval
typically used as a top-level definition, for example, encapsulating libraries that contain multiple similar functions.
The first argument of
ccall
can be an expression evaluated at runtime. A
t this point, the value of the expression
Ptr
type, pointing to the address of the native function to be called.
This attribute is used when the first argument of
ccall
contains a reference to an unsumed amount (local variable or function argument).
ccall
parameter of the ccall specifies how the call is made (before the value is returned). I
f not specified, the operating system's default C call is used. O
ther supported calls are:
stdcall
cdecl
fastcall
thiscall
For example (from base/libc.jl):
hn = Array(Uint8, 256)
err=ccall(:gethostname, stdcall, Int32, (Ptr{Uint8}, Uint32), hn, length(hn))
For more information, please refer to LLVM Language Reference
When global variables are exported to a local library,
cglobal
method to access them by name.
cglobal
and
ccall
are the same symbols, and they represent 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.
You can manipulate this value with this pointer, but you need
unsafe_load
and
unsafe_store
You can pass a Julia function to a local function as long as it has pointer arguments.
A typical example is the standard C library
qsort
described as follows:
void qsort(void *base, size_t nmemb, size_t size,
int(*compare)(const void *a, const void *b));
base
parameter is a pointer to an array
nmemb
with each element size being
size
compare
is a callback function with
a
to two elements a
b
and
a
before or after
b
an integer greater than or less than 0 is returned (resulting in 0 if any order is allowed). N
ow suppose we have a one-dimensional array A in the Julia
A
and we want to sort the array
qsort
Julia's built-in function).
Before we
qsort
and pass arguments, we need to write a comparison function to accommodate any type of 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
Note that we have to be aware of the return value type:
qsort
int
the return value, so we have to call
convert
to make sure that
Cint
In order to be able to pass this function to C, we'll get its address by
cfunction
const mycompare_c = cfunction(mycompare, Cint, (Ptr{Cdouble}, Ptr{Cdouble}))
cfunction
three parameters: the Julia function
mycompare
the return value type
Cint
and a metagroup of the parameter type, in which case the array of
cdouble
(Float64) element is sorted.
The final
qsort
is as follows:
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)
After doing this,
A
to sort
[ -2.7, 1.3, 3.1, 4.4]
N
ote Julia knows how to convert an array to
Ptr{Cdouble}
how to calculate byte size (the same
sizeof
as C), and so on.
If you are interested,
mycompare
a
println("mycompare($a,$b)")
will allow you to view
qsort
(and make sure it does call the Julia function you passed).
Some Cs execute their callback functions from different threads, and Julia doesn't contain thread safety, so you need to do some extra precautions. I
n particular, you need to set up a two-tier system: A callback to C should only schedule (through Julia's time loop) the execution of your "real" callback function.
Your callback requires two inputs (which you'll probably
SingleAsyncWork
cb = Base.SingleAsyncWork(data -> my_real_callback(args))
The callback you pass to C should simply
ccall
:uv_async_send
cb.handle
the argument.
For more details on how to pass callbacks to the C library, refer to the blog post.
The Cpp and Clang expansion packages provide limited support for C.
When working with different platform libraries, special functions are often provided for special platforms. T
he variable is often used
OS_NAME
I
n addition, there are some commonly used
@windows
@unix
@linux
@osx
N
ote that linux and osx are not intersecting subsets of unix.
Macros are used like thyme conditional operators.
Simple call:
ccall( (@windows? :_fopen : :fopen), ...)
Complex calls:
@linux? (
begin
some_complicated_thing(a)
end
: begin
some_different_thing(a)
end
)
Chain calls (parentheses can be omitted, but for readability, it's best to add):
@windows? :a : (@osx? :b : :c)