Calling C and Fortran Code
Хотя большинство кода можно написать на Julia, уже существует множество высококачественных, зрелых библиотек для численных вычислений, написанных на C и Fortran. Чтобы упростить использование этого существующего кода, Julia делает вызов функций C и Fortran простым и эффективным. У Julia есть философия "без шаблонного кода": функции можно вызывать напрямую из Julia без какого-либо "связывающего" кода, генерации кода или компиляции – даже из интерактивного приглашения. Это достигается просто с помощью соответствующего вызова с макросом @ccall
(или менее удобным синтаксисом ccall
, см. ccall
syntax section).
Код, который должен быть вызван, должен быть доступен в виде общей библиотеки. Большинство библиотек C и Fortran уже поставляются в виде скомпилированных общих библиотек, но если вы компилируете код самостоятельно с помощью GCC (или Clang), вам нужно будет использовать параметры -shared
и -fPIC
. Машинные инструкции, сгенерированные JIT Julia, такие же, как и при вызове нативной функции C, поэтому накладные расходы будут такими же, как при вызове функции библиотеки из кода C. [1]
По умолчанию компиляторы Fortran generate mangled names (например, преобразование имен функций в нижний или верхний регистр, часто с добавлением подчеркивания), и поэтому для вызова функции Fortran вы должны передать искаженный идентификатор, соответствующий правилу, следуемому вашим компилятором Fortran. Также при вызове функции Fortran все входные данные должны передаваться как указатели на выделенные значения в куче или стеке. Это относится не только к массивам и другим изменяемым объектам, которые обычно выделяются в куче, но и к скалярным значениям, таким как целые числа и числа с плавающей запятой, которые обычно выделяются в стеке и часто передаются в регистрах при использовании соглашений о вызове C или Julia.
Синтаксис для @ccall
для генерации вызова функции библиотеки следующий:
@ccall library.function_name(argvalue1::argtype1, ...)::returntype
@ccall function_name(argvalue1::argtype1, ...)::returntype
@ccall $function_pointer(argvalue1::argtype1, ...)::returntype
где library
является строковой константой или литералом (но см. Non-constant Function Specifications ниже). Библиотеку можно опустить, в этом случае имя функции разрешается в текущем процессе. Эта форма может быть использована для вызова функций C-библиотеки, функций в среде выполнения Julia или функций в приложении, связанном с Julia. Также может быть указан полный путь к библиотеке. В качестве альтернативы, @ccall
также может быть использован для вызова указателя на функцию $function_pointer
, такого как тот, который возвращается Libdl.dlsym
. argtype
s соответствуют сигнатуре C-функции, а argvalue
s являются фактическими значениями аргументов, которые будут переданы функции.
Смотрите ниже, как map C types to Julia types.
В качестве полного, но простого примера, следующий код вызывает функцию clock
из стандартной библиотеки C на большинстве систем, производных от Unix:
julia> t = @ccall clock()::Int32
2292761
julia> typeof(t)
Int32
clock
не принимает аргументов и возвращает Int32
. Чтобы вызвать функцию getenv
для получения указателя на значение переменной окружения, нужно сделать вызов, как показано ниже:
julia> path = @ccall getenv("SHELL"::Cstring)::Cstring
Cstring(@0x00007fff5fbffc45)
julia> unsafe_string(path)
"/bin/bash"
На практике, особенно при предоставлении повторно используемой функциональности, обычно оборачивают использование @ccall
в функции Julia, которые настраивают аргументы, а затем проверяют наличие ошибок в том виде, который указывает функция C или Fortran. И если происходит ошибка, она выбрасывается как обычное исключение Julia. Это особенно важно, поскольку API C и Fortran известны своей непоследовательностью в том, как они указывают условия ошибки. Например, функция библиотеки C getenv
обернута в следующую функцию Julia, которая является упрощенной версией фактического определения из 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
Функция C getenv
указывает на ошибку, возвращая C_NULL
, но другие стандартные функции C указывают на ошибки различными способами, включая возврат -1, 0, 1 и других специальных значений. Этот обертка выбрасывает исключение, указывающее на проблему, если вызывающий пытается получить несуществующую переменную окружения:
julia> getenv("SHELL")
"/bin/bash"
julia> getenv("FOOBAR")
ERROR: getenv: undefined variable: FOOBAR
Вот немного более сложный пример, который определяет имя хоста локальной машины.
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 # ensure null-termination
return GC.@preserve hostname unsafe_string(pointer(hostname))
end
Этот пример сначала выделяет массив байтов. Затем он вызывает функцию библиотеки C gethostname
, чтобы заполнить массив именем хоста. Наконец, он берет указатель на буфер имени хоста и преобразует указатель в строку Julia, предполагая, что это строка C с нулевым окончанием.
Часто библиотеки C используют этот шаблон, требуя от вызывающего выделения памяти, которая будет передана вызываемому и заполнена. Выделение памяти из Julia таким образом обычно осуществляется путем создания неинициализированного массива и передачи указателя на его данные в функцию C. Вот почему мы не используем тип Cstring
здесь: поскольку массив неинициализирован, он может содержать нулевые байты. Преобразование в Cstring
в рамках @ccall
проверяет наличие нулевых байтов и, следовательно, может вызвать ошибку преобразования.
Разыменование pointer(hostname)
с помощью unsafe_string
является небезопасной операцией, так как требует доступа к памяти, выделенной для hostname
, которая могла быть в это время собрана сборщиком мусора. Макрос GC.@preserve
предотвращает это и, следовательно, доступ к недопустимому участку памяти.
Наконец, вот пример указания библиотеки через путь. Мы создаем общую библиотеку со следующим содержимым
#include <stdio.h>
void say_y(int y)
{
printf("Hello from C: got y = %d.\n", y);
}
и скомпилируйте его с помощью gcc -fPIC -shared -o mylib.so mylib.c
. Затем его можно вызвать, указав (абсолютный) путь в качестве имени библиотеки:
julia> @ccall "./mylib.so".say_y(5::Cint)::Cvoid
Hello from C: got y = 5.
Creating C-Compatible Julia Function Pointers
Возможно передавать функции Julia в нативные функции C, которые принимают аргументы указателей на функции. Например, чтобы соответствовать прототипам C следующего вида:
typedef returntype (*functiontype)(argumenttype, ...)
Макрос @cfunction
генерирует указатель на функцию, совместимый с C, для вызова функции Julia. Аргументы для 4d61726b646f776e2e436f64652822222c2022406366756e6374696f6e2229_40726566
следующие:
- Функция Julia
- Тип возвращаемого значения функции
- Кортеж типов входных данных, соответствующий сигнатуре функции
Как и в случае с @ccall
, тип возвращаемого значения и типы входных данных должны быть литеральными константами.
В настоящее время поддерживается только стандартный C вызов по умолчанию для платформы. Это означает, что указатели, сгенерированные с помощью @cfunction
, не могут использоваться в вызовах, где WINAPI ожидает функцию stdcall
на 32-битных Windows, но могут использоваться на WIN64 (где stdcall
объединен с C вызовом).
Функции обратного вызова, предоставляемые через @cfunction
, не должны вызывать ошибки, так как это неожиданно вернет управление в среду выполнения Julia и может оставить программу в неопределенном состоянии.
Классическим примером является стандартная библиотека C функция qsort
, объявленная как:
void qsort(void *base, size_t nitems, size_t size,
int (*compare)(const void*, const void*));
Аргумент base
является указателем на массив длиной nitems
, с элементами размером size
байт каждый. compare
- это функция обратного вызова, которая принимает указатели на два элемента a
и b
и возвращает целое число меньше/больше нуля, если a
должен появляться перед/после b
(или ноль, если любой порядок разрешен).
Теперь предположим, что у нас есть 1-мерный массив A
значений в Julia, который мы хотим отсортировать, используя функцию qsort
(вместо встроенной функции sort
в Julia). Прежде чем мы рассмотрим возможность вызова qsort
и передачи аргументов, нам нужно написать функцию сравнения:
julia> function mycompare(a, b)::Cint
return (a < b) ? -1 : ((a > b) ? +1 : 0)
end;
qsort
ожидает функцию сравнения, которая возвращает int
C, поэтому мы аннотируем возвращаемый тип как Cint
.
Чтобы передать эту функцию в C, мы получаем ее адрес, используя макрос @cfunction
:
julia> mycompare_c = @cfunction(mycompare, Cint, (Ref{Cdouble}, Ref{Cdouble}));
@cfunction
требует три аргумента: функция Julia (mycompare
), тип возвращаемого значения (Cint
) и литерал кортежа типов входных аргументов, в данном случае для сортировки массива элементов Cdouble
(Float64
).
Последний вызов qsort
выглядит так:
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
Как показывает пример, оригинальный массив Julia A
теперь отсортирован: [-2.7, 1.3, 3.1, 4.4]
. Обратите внимание, что Julia takes care of converting the array to a Ptr{Cdouble}
), вычисление размера типа элемента в байтах и так далее.
Для веселья попробуйте вставить строку println("mycompare($a, $b)")
в mycompare
, что позволит вам увидеть сравнения, которые выполняет qsort
(и проверить, что он действительно вызывает функцию Julia, которую вы ему передали).
Mapping C Types to Julia
Критически важно точно соответствовать объявленному типу C его объявлению в Julia. Несоответствия могут привести к тому, что код, который работает правильно на одной системе, будет давать сбой или производить неопределенные результаты на другой системе.
Обратите внимание, что в процессе вызова функций C не используются файлы заголовков C: вы несете ответственность за то, чтобы ваши типы Julia и сигнатуры вызовов точно отражали их в файле заголовка C.[2]
Automatic Type Conversion
Юлия автоматически вставляет вызовы функции Base.cconvert
, чтобы преобразовать каждый аргумент в указанный тип. Например, следующий вызов:
@ccall "libfoo".foo(x::Int32, y::Float64)::Cvoid
будет вести себя так, как если бы это было написано так:
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
Base.cconvert
обычно просто вызывает convert
, но может быть определен для возврата произвольного нового объекта, более подходящего для передачи в C. Это должно использоваться для выполнения всех выделений памяти, которые будут доступны коду C. Например, это используется для преобразования Array
объектов (например, строк) в массив указателей.
Base.unsafe_convert
обрабатывает преобразование в Ptr
типы. Это считается небезопасным, потому что преобразование объекта в указатель на нативный тип может скрыть объект от сборщика мусора, что приведет к его преждевременному освобождению.
Type Correspondences
Сначала давайте рассмотрим некоторые соответствующие термины типа Julia:
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
Существует несколько специальных типов, о которых следует знать, так как ни один другой тип не может быть определен так, чтобы вести себя аналогично:
Float32
Точно соответствует типу
float
в C (илиREAL*4
в Fortran).Float64
Точно соответствует типу
double
в C (илиREAL*8
в Fortran).ComplexF32
Точно соответствует типу
complex float
в C (илиCOMPLEX*8
в Fortran).ComplexF64
Точно соответствует типу
complex double
в C (илиCOMPLEX*16
в Fortran).Подписано
Точно соответствует аннотации типа
signed
в C (или любому типуINTEGER
в Fortran). Любой тип Julia, который не является подтипомSigned
, предполагается как беззнаковый.Ref{T}
Ведет себя как
Ptr{T}
, который может управлять своей памятью через сборщик мусора Julia.Array{T,N}
Когда массив передается в C как аргумент
Ptr{T}
, он не переопределяется: Julia требует, чтобы тип элемента массива соответствовалT
, и передается адрес первого элемента.Следовательно, если
Array
содержит данные в неправильном формате, его придется явно преобразовать с помощью вызова, такого какtrunc.(Int32, A)
.Чтобы передать массив
A
в качестве указателя другого типа без предварительного преобразования данных (например, чтобы передать массивFloat64
в функцию, которая работает с неинтерпретированными байтами), вы можете объявить аргумент какPtr{Cvoid}
.Если массив типа
Ptr{T}
передан как аргументPtr{Ptr{T}}
,Base.cconvert
сначала попытается создать копию массива с нулевым терминатором, заменив каждый элемент на его версию4d61726b646f776e2e436f64652822222c2022426173652e63636f6e766572742229_40726566
. Это позволяет, например, передавать массив указателейargv
типаVector{String}
в аргумент типаPtr{Ptr{Cchar}}
.
На всех системах, которые мы в настоящее время поддерживаем, базовые типы значений C/C++ могут быть переведены в типы Julia следующим образом. Каждый тип C также имеет соответствующий тип Julia с тем же именем, предваряемым C. Это может помочь при написании переносимого кода (и помнить, что int
в C не то же самое, что Int
в Julia).
Системно независимые типы
C name | Fortran name | Standard Julia Alias | Julia Base Type |
---|---|---|---|
unsigned char | CHARACTER | Cuchar | UInt8 |
bool (_Bool in C99+) | 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} (or similarly Ref{Cvoid} ) | ||
T* (where T represents an appropriately defined type) | Ref{T} (T may be safely mutated only if T is an isbits type) | ||
char* (or char[] , e.g. a string) | CHARACTER*N | Cstring if null-terminated, or Ptr{UInt8} if not | |
char** (or *char[] ) | Ptr{Ptr{UInt8}} | ||
jl_value_t* (any Julia Type) | Any | ||
jl_value_t* const* (a reference to a Julia value) | Ref{Any} (const, since mutation would require a write barrier, which is not possible to insert correctly) | ||
va_arg | Not supported | ||
... (variadic function specification) | T... (where T is one of the above types, when using the ccall function) | ||
... (variadic function specification) | ; va_arg1::T, va_arg2::S, etc. (only supported with @ccall macro) |
Тип Cstring
по сути является синонимом Ptr{UInt8}
, за исключением того, что преобразование в Cstring
вызывает ошибку, если строка Julia содержит любые встроенные нулевые символы (что может привести к тому, что строка будет тихо обрезана, если C-рутина рассматривает нуль как терминатор). Если вы передаете char*
в C-рутину, которая не предполагает нулевое завершение (например, потому что вы передаете явную длину строки), или если вы точно знаете, что ваша строка Julia не содержит нулей и хотите пропустить проверку, вы можете использовать Ptr{UInt8}
в качестве типа аргумента. Cstring
также может использоваться в качестве типа возвращаемого значения ccall
, но в этом случае он, очевидно, не вводит никаких дополнительных проверок и предназначен только для улучшения читаемости вызова.
Зависимые типы системы
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) |
При вызове Fortran все входные данные должны передаваться по указателям на значения, выделенные в куче или на стеке, поэтому все соответствия типов выше должны содержать дополнительную обертку Ptr{..}
или Ref{..}
вокруг их спецификации типа.
Для строковых аргументов (char*
) тип Julia должен быть Cstring
(если ожидаются данные с нулевым терминатором) или Ptr{Cchar}
или Ptr{UInt8}
в противном случае (эти два типа указателей имеют одинаковый эффект), как описано выше, а не String
. Аналогично, для аргументов массивов (T[]
или T*
) тип Julia снова должен быть Ptr{T}
, а не Vector{T}
.
Тип Char
в Julia занимает 32 бита, что не соответствует типу широких символов (wchar_t
или wint_t
) на всех платформах.
Тип возвращаемого значения Union{}
означает, что функция не будет возвращать значение, т.е. C++11 [[noreturn]]
или C11 _Noreturn
(например, jl_throw
или longjmp
). Не используйте это для функций, которые не возвращают значение (void
), но действительно возвращают, для таких используйте Cvoid
вместо.
Для аргументов wchar_t*
тип Julia должен быть Cwstring
(если C-рутина ожидает строку, заканчивающуюся нулем), или Ptr{Cwchar_t}
в противном случае. Также обратите внимание, что данные строк в формате UTF-8 в Julia внутренне заканчиваются нулем, поэтому их можно передавать в C-функции, ожидающие данные, заканчивающиеся нулем, без создания копии (но использование типа Cwstring
вызовет ошибку, если сама строка содержит нулевые символы).
C функции, которые принимают аргумент типа char**
, могут быть вызваны с использованием типа Ptr{Ptr{UInt8}}
в Julia. Например, C функции следующей формы:
int main(int argc, char **argv);
может быть вызван с помощью следующего кода Julia:
argv = [ "a.out", "arg1", "arg2" ]
@ccall main(length(argv)::Int32, argv::Ptr{Ptr{UInt8}})::Int32
Для функций Fortran, принимающих строки переменной длины типа character(len=*)
, длины строк передаются как скрытые аргументы. Тип и позиция этих аргументов в списке зависят от компилятора, при этом вендоры компиляторов обычно по умолчанию используют Csize_t
в качестве типа и добавляют скрытые аргументы в конец списка аргументов. Хотя это поведение фиксировано для некоторых компиляторов (GNU), другие опционально позволяют размещать скрытые аргументы сразу после аргумента типа character (Intel, PGI). Например, подпрограммы Fortran формы
subroutine test(str1, str2)
character(len=*) :: str1,str2
может быть вызван с помощью следующего кода Julia, где длины добавляются
str1 = "foo"
str2 = "bar"
ccall(:test, Cvoid, (Ptr{UInt8}, Ptr{UInt8}, Csize_t, Csize_t),
str1, str2, sizeof(str1), sizeof(str2))
Компиляторы Fortran могут также добавлять другие скрытые аргументы для указателей, массивов с предполагаемой формой (:
) и массивов с предполагаемым размером (*
). Такое поведение можно избежать, используя ISO_C_BINDING
и включая bind(c)
в определение подпрограммы, что настоятельно рекомендуется для совместимого кода. В этом случае не будет скрытых аргументов, но за счет некоторых возможностей языка (например, только character(len=1)
будет разрешено для передачи строк).
Функция C, объявленная для возврата Cvoid
, вернет значение nothing
в Julia.
Struct Type Correspondences
Составные типы, такие как struct
в C или TYPE
в Fortran90 (или STRUCTURE
/ RECORD
в некоторых вариантах F77), могут быть отражены в Julia путем создания определения struct
с такой же компоновкой полей.
Когда используются рекурсивно, типы isbits
хранятся встроенно. Все остальные типы хранятся как указатель на данные. При зеркалировании структуры, используемой по значению внутри другой структуры в C, крайне важно не пытаться вручную копировать поля, так как это не сохранит правильное выравнивание полей. Вместо этого объявите тип структуры isbits
и используйте его. Безымянные структуры невозможны при переводе на Julia.
Упакованные структуры и объявления объединений не поддерживаются в Julia.
Вы можете получить приближенную оценку union
, если заранее знаете, какое поле будет иметь наибольший размер (возможно, включая выравнивание). При переводе ваших полей в Julia объявите поле Julia только этого типа.
Массивы параметров можно выразить с помощью NTuple
. Например, структура в нотации C записывается как
struct B {
int A[3];
};
b_a_2 = B.A[2];
может быть написано на Julia как
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)
Массивы неизвестного размера (структуры переменной длины, соответствующие C99, указанные с помощью []
или [0]
) не поддерживаются напрямую. Часто лучший способ справиться с ними — это работать с байтовыми смещениями напрямую. Например, если библиотека C объявила правильный тип строки и вернула указатель на него:
struct String {
int strlen;
char data[];
};
В Julia мы можем получить доступ к частям независимо, чтобы создать копию этой строки:
str = from_c::Ptr{Cvoid}
len = unsafe_load(Ptr{Cint}(str))
unsafe_string(str + Core.sizeof(Cint), len)
Type Parameters
Аргументы типа для @ccall
и @cfunction
оцениваются статически, когда метод, содержащий использование, определяется. Поэтому они должны иметь форму литерального кортежа, а не переменной, и не могут ссылаться на локальные переменные.
Это может показаться странным ограничением, но помните, что поскольку C не является динамическим языком, как Julia, его функции могут принимать только аргументы с известными статически фиксированными типами.
Однако, хотя макет типа должен быть известен статически для вычисления предполагаемого C ABI, статические параметры функции считаются частью этой статической среды. Статические параметры функции могут использоваться в качестве параметров типа в сигнатуре вызова, при условии, что они не влияют на макет типа. Например, f(x::T) where {T} = @ccall valid(x::Ptr{T})::Ptr{T}
является допустимым, поскольку Ptr
всегда является примитивным типом размером в слово. Но g(x::T) where {T} = @ccall notvalid(x::T)::T
недопустим, поскольку макет типа T
не известен статически.
SIMD Values
Если у процедуры C/C++ есть аргумент или возвращаемое значение, которое является нативным SIMD типом, соответствующий тип Julia — это однородный кортеж VecElement
, который естественно сопоставляется с SIMD типом. В частности:
- Кортеж должен иметь такой же размер и элементы, как и тип SIMD. Например, кортеж, представляющий
__m128
на x86, должен иметь размер 16 байт и элементы Float32.- Тип элемента кортежа должен быть экземпляром
VecElement{T}
, гдеT
является примитивным типом с количеством байт, равным степени двойки (например, 1, 2, 4, 8, 16 и т.д.), таким как Int8 или Float64.
Например, рассмотрим эту C-рутину, которая использует встроенные функции AVX:
#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)));
}
Следующий код на Julia вызывает dist
с помощью 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 "libdist".dist(a::m256, b::m256)::m256
end
println(call_dist(a,b))
Хост-машина должна иметь необходимые регистры SIMD. Например, приведенный выше код не будет работать на хостах без поддержки AVX.
Memory Ownership
malloc
/free
Выделение и освобождение памяти таких объектов должно обрабатываться вызовами соответствующих процедур очистки в используемых библиотеках, так же как и в любой программе на C. Не пытайтесь освободить объект, полученный из библиотеки C с Libc.free
в Julia, так как это может привести к вызову функции free
через неправильную библиотеку и вызвать завершение процесса. Обратное (передача объекта, выделенного в Julia, для освобождения внешней библиотекой) также недопустимо.
When to use T
, Ptr{T}
and Ref{T}
В коде Julia, оборачивающем вызовы внешних C-рутин, обычные (не указатели) данные должны быть объявлены как тип T
внутри @ccall
, так как они передаются по значению. Для C-кода, принимающего указатели, Ref{T}
обычно следует использовать для типов входных аргументов, что позволяет использовать указатели на память, управляемую как Julia, так и C, через неявный вызов Base.cconvert
. В отличие от этого, указатели, возвращаемые вызываемой C-функцией, должны быть объявлены как выходной тип Ptr{T}
, что отражает то, что память, на которую указывает указатель, управляется только C. Указатели, содержащиеся в C-структурах, должны быть представлены как поля типа Ptr{T}
внутри соответствующих типов структур Julia, предназначенных для имитации внутренней структуры соответствующих C-структур.
В коде Julia, оборачивающем вызовы внешних подпрограмм Fortran, все входные аргументы должны быть объявлены как тип Ref{T}
, так как Fortran передает все переменные по указателям на адреса в памяти. Тип возвращаемого значения должен быть либо Cvoid
для подпрограмм Fortran, либо T
для функций Fortran, возвращающих тип T
.
Mapping C Functions to Julia
@ccall
/ @cfunction
argument translation guide
Для перевода списка аргументов C в Julia:
T
, гдеT
является одним из примитивных типов:char
,int
,long
,short
,float
,double
,complex
,enum
или любым из ихtypedef
эквивалентовT
, гдеT
является эквивалентным типом битов Julia (согласно таблице выше)- если
T
являетсяenum
, тип аргумента должен быть эквивалентенCint
илиCuint
- аргумент будет скопирован (передан по значению)
struct T
(включая typedef для структуры)T
, гдеT
— это тип листа Julia- аргумент будет скопирован (передан по значению)
vector T
(или__attribute__ vector_size
, или тип, такой как__m128
)NTuple{N, VecElement{T}}
, гдеT
— это примитивный тип Julia правильного размера, а N — это количество элементов в векторе (равноеvector_size / sizeof T
).
void*
- в зависимости от того, как этот параметр используется, сначала переведите это в предполагаемый тип указателя, затем определите эквивалент Julia, используя оставшиеся правила в этом списке
- этот аргумент может быть объявлен как
Ptr{Cvoid}
, если он действительно является просто неизвестным указателем
jl_value_t*
Любой
- аргумент должен быть действительным объектом Julia
jl_value_t* const*
Ref{Any}
- список аргументов должен быть допустимым объектом Julia (или
C_NULL
) - не может быть использован в качестве выходного параметра, если пользователь не может отдельно организовать сохранение объекта в сборщике мусора
T*
Ref{T}
, гдеT
— это тип Julia, соответствующийT
- значение аргумента будет скопировано, если это тип
inlinealloc
(который включаетisbits
), в противном случае значение должно быть допустимым объектом Julia
T (*)(...)
(например, указатель на функцию)Ptr{Cvoid}
(вам может понадобиться использовать@cfunction
явно для создания этого указателя)
...
(например, vararg)- [for
ccall
]:T...
, гдеT
— это единственный тип Julia для всех оставшихся аргументов - [for
@ccall
]:; va_arg1::T, va_arg2::S, и т.д.
, гдеT
иS
— это типы Julia (т.е. разделите обычные аргументы от varargs с помощью;
) - в настоящее время не поддерживается
@cfunction
- [for
va_arg
- не поддерживается
ccall
или@cfunction
- не поддерживается
@ccall
/ @cfunction
return type translation guide
Для перевода типа возвращаемого значения C в Julia:
пусто
Cvoid
(это вернет единственный экземплярnothing::Cvoid
)
T
, гдеT
является одним из примитивных типов:char
,int
,long
,short
,float
,double
,complex
,enum
или любым из ихtypedef
эквивалентов- так же, как список аргументов C
- значение аргумента будет скопировано (возвращено по значению)
struct T
(включая typedef для структуры)- так же, как список аргументов C
- значение аргумента будет скопировано (возвращено по значению)
вектор T
- так же, как список аргументов C
void*
- зависит от того, как этот параметр используется, сначала переведите это в предполагаемый тип указателя, затем определите эквивалент Julia, используя оставшиеся правила в этом списке
- этот аргумент может быть объявлен как
Ptr{Cvoid}
, если он действительно является просто неизвестным указателем
jl_value_t*
Любой
- аргумент должен быть действительным объектом Julia
jl_value_t**
Ptr{Any}
(Ref{Any}
недопустим как тип возвращаемого значения)
T*
Если память уже принадлежит Julia, или является типом
isbits
, и известно, что она не равна нулю:Ref{T}
, гдеT
— это тип Julia, соответствующийT
- тип возврата
Ref{Any}
недопустим, он должен быть либоAny
(соответствующийjl_value_t*
), либоPtr{Any}
(соответствующийjl_value_t**
) - C НЕ ДОЛЖЕН изменять память, возвращаемую через
Ref{T}
, еслиT
является типомisbits
Если память принадлежит C:
Ptr{T}
, гдеT
— это тип Julia, соответствующийT
T (*)(...)
(например, указатель на функцию)Ptr{Cvoid}
чтобы вызвать это напрямую из Julia, вам нужно передать это в качестве первого аргумента к@ccall
. См. Indirect Calls.
Passing Pointers for Modifying Inputs
Поскольку C не поддерживает множественные возвращаемые значения, часто функции C будут принимать указатели на данные, которые функция будет изменять. Чтобы достичь этого в @ccall
, вам сначала нужно инкапсулировать значение внутри Ref{T}
соответствующего типа. Когда вы передаете этот объект Ref
в качестве аргумента, Julia автоматически передаст указатель C на инкапсулированные данные:
width = Ref{Cint}(0)
range = Ref{Cfloat}(0)
@ccall foo(width::Ref{Cint}, range::Ref{Cfloat})::Cvoid
По возвращении содержимое width
и range
можно получить (если они были изменены foo
) с помощью width[]
и range[]
; то есть они ведут себя как нульмерные массивы.
C Wrapper Examples
Давайте начнем с простого примера обертки на C, которая возвращает тип Ptr
:
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 "libgsl".gsl_permutation_alloc(n::Csize_t)::Ptr{gsl_permutation}
if output_ptr == C_NULL # Could not allocate memory
throw(OutOfMemoryError())
end
return output_ptr
end
GNU Scientific Library (здесь предполагается, что он доступен через :libgsl
) определяет непрозрачный указатель, gsl_permutation *
, как возвращаемый тип функции C gsl_permutation_alloc
. Поскольку пользовательский код никогда не должен заглядывать внутрь структуры gsl_permutation
, соответствующий обертка на Julia просто требует нового объявления типа, gsl_permutation
, который не имеет внутренних полей и единственной целью которого является размещение в параметре типа типа Ptr
. Возвращаемый тип ccall
объявлен как Ptr{gsl_permutation}
, поскольку память, выделенная и на которую указывает output_ptr
, контролируется C.
Входное значение n
передается по значению, и поэтому сигнатура входного параметра функции просто объявляется как ::Csize_t
без необходимости в Ref
или Ptr
. (Если бы обертка вызывала функцию Fortran, соответствующая сигнатура входного параметра функции была бы ::Ref{Csize_t}
, так как переменные Fortran передаются по указателям.) Кроме того, n
может быть любого типа, который можно преобразовать в целое число Csize_t
; ccall
неявно вызывает Base.cconvert(Csize_t, n)
.
Вот второй пример, оборачивающий соответствующий деструктор:
# The corresponding C signature is
# 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
Вот третий пример передачи массивов Julia:
# 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 "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
Функция C wrapped
возвращает целочисленный код ошибки; результаты фактической оценки функции Бесселя J заполняют массив Julia result_array
. Эта переменная объявлена как Ref{Cdouble}
, так как ее память выделяется и управляется Julia. Неявный вызов Base.cconvert(Ref{Cdouble}, result_array)
распаковывает указатель Julia в структуру данных массива Julia в форму, понятную C.
Fortran Wrapper Example
Следующий пример использует ccall
для вызова функции в общей библиотеке Fortran (libBLAS) для вычисления скалярного произведения. Обратите внимание, что сопоставление аргументов здесь немного отличается от вышеуказанного, так как нам нужно сопоставить с Julia на Fortran. Для каждого типа аргумента мы указываем Ref
или Ptr
. Эта конвенция именования может быть специфична для вашего компилятора Fortran и операционной системы и, вероятно, не задокументирована. Тем не менее, обертывание каждого в Ref
(или Ptr
, где это эквивалентно) является частым требованием реализаций компилятора Fortran:
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 Safety
При передаче данных в @ccall
лучше избегать использования функции pointer
. Вместо этого определите метод Base.cconvert
и передайте переменные напрямую в @ccall
. @ccall
автоматически обеспечивает сохранение всех своих аргументов от сборки мусора до тех пор, пока вызов не завершится. Если C API будет хранить ссылку на память, выделенную Julia, после возврата @ccall
, вы должны убедиться, что объект остается видимым для сборщика мусора. Рекомендуемый способ сделать это — создать глобальную переменную типа Array{Ref,1}
, чтобы хранить эти значения до тех пор, пока C библиотека не уведомит вас о том, что она закончила с ними.
Каждый раз, когда вы создаете указатель на данные Julia, вы должны убедиться, что оригинальные данные существуют до тех пор, пока вы не закончите использовать указатель. Многие методы в Julia, такие как unsafe_load
и String
, создают копии данных вместо того, чтобы брать на себя право собственности на буфер, так что безопасно освобождать (или изменять) оригинальные данные, не затрагивая Julia. Замечательное исключение составляет unsafe_wrap
, который, по причинам производительности, делит (или может быть настроен на получение права собственности на) основной буфер.
Сборщик мусора не гарантирует никакого порядка финализации. То есть, если a
содержал ссылку на b
, и оба a
и b
подлежат сборке мусора, нет гарантии, что b
будет финализирован после a
. Если правильная финализация a
зависит от того, что b
является действительным, это должно обрабатываться другими способами.
Non-constant Function Specifications
В некоторых случаях точное имя или путь к необходимой библиотеке заранее не известны и должны вычисляться во время выполнения. Чтобы справиться с такими случаями, спецификация компонента библиотеки может быть вызовом функции, например, find_blas().dgemm
. Выражение вызова будет выполнено, когда сам ccall
будет выполнен. Однако предполагается, что местоположение библиотеки не изменится после его определения, поэтому результат вызова может быть кэширован и повторно использован. Таким образом, количество раз, когда выражение выполняется, не указано, и возврат различных значений для нескольких вызовов приводит к неопределенному поведению.
Если нужна еще большая гибкость, можно использовать вычисляемые значения в качестве имен функций, пройдя через eval
, как показано ниже:
@eval @ccall "lib".$(string("a", "b"))()::Cint
Это выражение создает имя, используя string
, а затем подставляет это имя в новое выражение @ccall
, которое затем оценивается. Имейте в виду, что eval
работает только на верхнем уровне, поэтому в этом выражении локальные переменные не будут доступны (если их значения не подставлены с помощью $
). По этой причине eval
обычно используется только для формирования определений верхнего уровня, например, при обертывании библиотек, которые содержат много похожих функций. Похожий пример можно построить для @cfunction
.
Однако, делать это будет также очень медленно и приведет к утечке памяти, поэтому обычно следует избегать этого и вместо этого продолжать чтение. В следующем разделе обсуждается, как использовать косвенные вызовы для эффективного достижения аналогичного эффекта.
Indirect Calls
Первый аргумент к @ccall
также может быть выражением, вычисляемым во время выполнения. В этом случае выражение должно оцениваться в Ptr
, который будет использоваться в качестве адреса вызываемой нативной функции. Это поведение происходит, когда первый аргумент @ccall
содержит ссылки на неконстанты, такие как локальные переменные, аргументы функций или неконстантные глобальные переменные.
Например, вы можете найти функцию с помощью dlsym
, а затем кэшировать её в общем ссылочном объекте для этой сессии. Например:
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
Closure cfunctions
Первый аргумент к @cfunction
может быть помечен знаком $
, в этом случае возвращаемое значение будет вместо этого struct CFunction
, который замыкает аргумент. Вы должны убедиться, что этот возвращаемый объект остается в памяти до тех пор, пока все его использования не будут завершены. Содержимое и код по указателю cfunction будут стерты с помощью finalizer
, когда эта ссылка будет удалена и при завершении программы. Это обычно не требуется, поскольку эта функциональность отсутствует в C, но может быть полезна для работы с плохо спроектированными API, которые не предоставляют отдельный параметр окружения замыкания.
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(a::Ptr{T}, length(a)::Csize_t, Base.elsize(a)::Csize_t, callback::Ptr{Cvoid})
# 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
Закрытие @cfunction
зависит от тромполинов LLVM, которые недоступны на всех платформах (например, ARM и PowerPC).
Closing a Library
Иногда полезно закрыть (разгрузить) библиотеку, чтобы ее можно было перезагрузить. Например, при разработке кода на C для использования с Julia может потребоваться скомпилировать, вызвать код на C из Julia, затем закрыть библиотеку, внести изменения, перекомпилировать и загрузить новые изменения. Можно либо перезапустить Julia, либо использовать функции Libdl
для явного управления библиотекой, такие как:
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 library.symbol tuple.
Libdl.dlclose(lib) # Close the library explicitly.
Обратите внимание, что при использовании @ccall
с входными данными (например, @ccall "./my_lib.so".my_fcn(...)::Cvoid
), библиотека открывается неявно, и ее может не быть явно закрыто.
Variadic function calls
Чтобы вызывать вариативные функции C, в списке аргументов можно использовать точку с запятой
для разделения обязательных аргументов от вариативных аргументов. Пример с функцией printf
приведен ниже:
julia> @ccall printf("%s = %d\n"::Cstring ; "foo"::Cstring, foo::Cint)::Cint
foo = 3
8
ccall
interface
Существует другой альтернативный интерфейс к @ccall
. Этот интерфейс немного менее удобен, но он позволяет указать calling convention.
Аргументы к ccall
следующие:
Пара
(:function, "library")
(наиболее распространенная),ИЛИ
символ имени
:function
или строка имени"function"
(для символов в текущем процессе или libc),ИЛИ
указатель на функцию (например, из
dlsym
).Тип возвращаемого значения функции
Кортеж типов входных данных, соответствующий сигнатуре функции. Одна распространенная ошибка заключается в том, что забывают, что 1-кортеж типов аргументов должен быть записан с завершающей запятой.
Фактические значения аргументов, которые должны быть переданы функции, если таковые имеются; каждый является отдельным параметром.
Пара (:function, "library")
, тип возвращаемого значения и типы входных данных должны быть буквальными константами (т.е. они не могут быть переменными, но см. Non-constant Function Specifications).
Оставшиеся параметры оцениваются во время компиляции, когда определяется содержащий метод.
Ниже представлена таблица переводов между макро- и функциональными интерфейсами.
@ccall | ccall |
---|---|
@ccall clock()::Int32 | ccall(:clock, Int32, ()) |
@ccall f(a::Cint)::Cint | ccall(:a, Cint, (Cint,), a) |
@ccall "mylib".f(a::Cint, b::Cdouble)::Cvoid | ccall((:f, "mylib"), Cvoid, (Cint, Cdouble), (a, b)) |
@ccall $fptr.f()::Cvoid | ccall(fptr, f, Cvoid, ()) |
@ccall printf("%s = %d\n"::Cstring ; "foo"::Cstring, foo::Cint)::Cint | <unavailable> |
@ccall printf("%s = %s\n"::Cstring ; "2 + 2"::Cstring, "5"::Cstring)::Cint | ccall(:printf, Cint, (Cstring, Cstring...), "%s = %s\n", "2 + 2", "5") |
<unavailable> | ccall(:gethostname, stdcall, Int32, (Ptr{UInt8}, UInt32), hn, length(hn)) |
Calling Convention
Второй аргумент для ccall
(непосредственно перед типом возвращаемого значения) может быть опционально спецификатором соглашения о вызове (макрос @ccall
в настоящее время не поддерживает указание соглашения о вызове). Без какого-либо спецификатора используется соглашение о вызове C по умолчанию для платформы. Другие поддерживаемые соглашения: stdcall
, cdecl
, fastcall
и thiscall
(не выполняется на 64-битных Windows). Например (из base/libc.jl
) мы видим тот же gethostname
ccall
, как и выше, но с правильной сигнатурой для Windows:
hn = Vector{UInt8}(undef, 256)
err = ccall(:gethostname, stdcall, Int32, (Ptr{UInt8}, UInt32), hn, length(hn))
Для получения дополнительной информации, пожалуйста, смотрите LLVM Language Reference.
Существует одно дополнительное специальное соглашение о вызовах llvmcall
, которое позволяет вставлять вызовы к LLVM встроенным функциям напрямую. Это может быть особенно полезно при нацеливании на необычные платформы, такие как GPGPU. Например, для CUDA нам нужно иметь возможность считывать индекс потока:
ccall("llvm.nvvm.read.ptx.sreg.tid.x", llvmcall, Int32, ())
Как и в любом ccall
, крайне важно точно указать сигнатуру аргументов. Также обратите внимание, что нет совместимого слоя, который гарантирует, что встроенная функция имеет смысл и работает на текущей цели, в отличие от эквивалентных функций Julia, предоставляемых Core.Intrinsics
.
Accessing Global Variables
Глобальные переменные, экспортируемые нативными библиотеками, могут быть доступны по имени с помощью функции cglobal
. Аргументы для 4d61726b646f776e2e436f64652822222c202263676c6f62616c2229_40726566
представляют собой спецификацию символа, идентичную той, что используется в ccall
, и тип, описывающий значение, хранящееся в переменной:
julia> cglobal((:errno, :libc), Int32)
Ptr{Int32} @0x00007f418d0816b8
Результат представляет собой указатель, который дает адрес значения. Значение можно манипулировать через этот указатель, используя unsafe_load
и unsafe_store!
.
Этот символ errno
может не быть найден в библиотеке с именем "libc", так как это деталь реализации вашего системного компилятора. Обычно символы стандартной библиотеки должны быть доступны просто по имени, позволяя компилятору подставить правильный. Однако символ errno
, показанный в этом примере, является специальным в большинстве компиляторов, и поэтому значение, которое вы видите здесь, вероятно, не то, что вы ожидаете или хотите. Компиляция эквивалентного кода на C на любой системе с поддержкой многопоточности обычно будет фактически вызывать другую функцию (через перегрузку макроса препроцессора) и может дать другой результат, чем устаревшее значение, напечатанное здесь.
Accessing Data through a Pointer
Следующие методы описываются как "небезопасные", потому что неправильный указатель или объявление типа могут привести к внезапному завершению работы Julia.
Данный Ptr{T}
позволяет копировать содержимое типа T
из ссылочной памяти в объект Julia с помощью unsafe_load(ptr, [index])
. Аргумент индекса является необязательным (по умолчанию 1) и следует конвенции Julia о 1-индексации. Эта функция намеренно похожа на поведение getindex
и setindex!
(например, синтаксис доступа []
).
Возвращаемое значение будет новым объектом, инициализированным для хранения копии содержимого ссылочной памяти. Ссылочную память можно безопасно освободить или удалить.
Если T
— это Any
, то предполагается, что память содержит ссылку на объект Julia ( jl_value_t*
), результатом будет ссылка на этот объект, и объект не будет скопирован. В этом случае вы должны быть осторожны, чтобы убедиться, что объект всегда был виден сборщику мусора (указатели не учитываются, но новая ссылка учитывается), чтобы гарантировать, что память не будет освобождена преждевременно. Обратите внимание, что если объект изначально не был выделен Julia, новый объект никогда не будет завершен сборщиком мусора Julia. Если Ptr
на самом деле является jl_value_t*
, его можно преобразовать обратно в ссылку на объект Julia с помощью unsafe_pointer_to_objref(ptr)
. (Значения Julia v
могут быть преобразованы в указатели jl_value_t*
, как Ptr{Cvoid}
, вызвав pointer_from_objref(v)
.)
Обратная операция (запись данных в Ptr{T}
) может быть выполнена с использованием unsafe_store!(ptr, value, [index])
. В настоящее время это поддерживается только для примитивных типов или других структур без указателей (isbits
), которые являются неизменяемыми.
Любая операция, которая вызывает ошибку, вероятно, в настоящее время не реализована и должна быть отправлена как ошибка, чтобы ее можно было решить.
Если указатель интереса является массивом простых данных (примитивный тип или неизменяемая структура), функция unsafe_wrap(Array, ptr,dims, own = false)
может быть более полезной. Последний параметр должен быть истинным, если Julia должна "взять на себя ответственность" за основной буфер и вызвать free(ptr)
, когда возвращаемый объект Array
будет завершен. Если параметр own
опущен или ложен, вызывающая сторона должна гарантировать, что буфер остается в существовании до завершения всех доступов.
Арифметика над типом Ptr
в Julia (например, использование +
) не ведет себя так же, как арифметика указателей в C. Прибавление целого числа к Ptr
в Julia всегда перемещает указатель на определенное количество байтов, а не элементов. Таким образом, адресные значения, полученные в результате арифметики указателей, не зависят от типов элементов указателей.
Thread-safety
Некоторые C-библиотеки выполняют свои обратные вызовы из другого потока, и поскольку Julia не является потокобезопасной, вам нужно будет принять дополнительные меры предосторожности. В частности, вам нужно будет настроить двухуровневую систему: C-обратный вызов должен только планировать (через цикл событий Julia) выполнение вашего "реального" обратного вызова. Для этого создайте объект AsyncCondition
и wait
на нем:
cond = Base.AsyncCondition()
wait(cond)
Обратный вызов, который вы передаете в C, должен выполнять только ccall
для :uv_async_send
, передавая cond.handle
в качестве аргумента, заботясь о том, чтобы избежать любых аллокаций или других взаимодействий с средой выполнения Julia.
Обратите внимание, что события могут быть объединены, поэтому несколько вызовов uv_async_send
могут привести к одному уведомлению о пробуждении для условия.
More About Callbacks
Для получения дополнительной информации о том, как передавать обратные вызовы в библиотеки C, смотрите этот blog post.
C++
Для инструментов создания привязок C++ смотрите пакет CxxWrap.
- 1Non-library function calls in both C and Julia can be inlined and thus may have even less overhead than calls to shared library functions. The point above is that the cost of actually doing foreign function call is about the same as doing a call in either native language.
- 2The Clang package can be used to auto-generate Julia code from a C header file.