Вызов кода C и Fortran
Хотя большая часть кода может быть написана на Julia, существует множество высококачественных, зрелых библиотек для численных вычислений, уже написанных на C и Fortran. Чтобы обеспечить легкое использование этого существующего кода, Julia упрощает и повышает эффективность вызова функций C и Fortran. Julia придерживается философии «без лишнего кода»: функции можно вызывать непосредственно из Julia без какого-либо «промежуточного» кода, генерации кода или компиляции — даже из интерактивного приглашения. Это достигается просто путем выполнения соответствующего вызова с помощью синтаксиса ccall, который выглядит как обычный вызов функции.
Вызываемый код должен быть доступен в виде динамической библиотеки. Большинство библиотек C и Fortran поставляются уже скомпилированными в виде динамических библиотек, но если вы компилируете код самостоятельно с помощью GCC (или Clang), вам потребуется использовать опции -shared и -fPIC. Машинные инструкции, сгенерированные JIT-компилятором Julia, такие же, как и в случае вызова нативного C, поэтому накладные расходы в результате вызова функции из библиотеки такие же, как и при вызове функции из кода C. [1]
Динамические библиотеки и функции ссылаются на кортеж вида (:function, "library") или ("function", "library"), где function — имя экспортированной функции C, а library — имя динамической библиотеки. Динамические библиотеки, доступные в пути загрузки (зависит от платформы), будут разрешаться по имени. Полный путь к библиотеке также можно указать.
Имя функции может использоваться самостоятельно вместо кортежа (просто :function или "function"). В этом случае имя разрешается в рамках текущего процесса. Этот формат можно использовать для вызова функций библиотеки C, функций среды выполнения Julia или функций приложения, связанного с Julia.
По умолчанию компиляторы Fortran генерируют именованные имена (например, преобразуя имена функций в нижний или верхний регистр, часто добавляя символ подчеркивания), и поэтому, чтобы вызвать функцию Fortran через ccall, вы должны передать замаскированный идентификатор, соответствующий правилу, используемому вашим компилятором Fortran. Кроме того, при вызове функции Fortran все входные данные должны передаваться как указатели на выделенные значения в куче или стеке. Это относится не только к массивам и другим изменяемым объектам, которые обычно размещаются в куче, но также и к скалярным значениям, таким как целые числа и числа с плавающей запятой, которые обычно размещаются в стеке и обычно передаются в регистрах при использовании соглашений о вызовах C или Julia.
Наконец, вы можете использовать ccall для фактического генерации вызова функции библиотеки. Аргументы для ccall таковы:
-
Пара
(:function, "library")(самый распространенный вариант),ИЛИ
имя символа
:functionили строковое имя"function"(для символов в текущем процессе или libc),ИЛИ
указатель на функцию (например, из
dlsym). Тип возвращаемого значения функции
Кортеж типов входных данных, соответствующий сигнатуре функции
Фактические значения аргументов, которые необходимо передать функции, если таковые имеются; каждое — отдельный параметр.
Пара (:function, "library"), тип возвращаемого значения и типы входных данных должны быть литеральными константами (т. е. они не могут быть переменными, но см. Спецификации функций без констант ниже).
Остальные параметры оцениваются во время компиляции, когда определен содержащий метод.
См. ниже, как отобразить типы C в типы Julia.
В качестве полного, но простого примера, следующий вызов функции clock из стандартной библиотеки C на большинстве систем Unix-подобных систем:
julia> t = ccall(:clock, Int32, ()) 2292761 julia> t 2292761 julia> typeof(t) Int32
clock не принимает аргументов и возвращает Int32. Распространенная ошибка — забывать, что кортеж типов аргументов длиной 1 должен записываться с последующей запятой. Например, для вызова функции getenv для получения указателя на значение переменной среды выполняется вызов такого типа:
julia> path = ccall(:getenv, Cstring, (Cstring,), "SHELL") Cstring(@0x00007fff5fbffc45) julia> unsafe_string(path) "/bin/bash"
Обратите внимание, что кортеж типов аргументов должен быть записан как (Cstring,), а не как (Cstring). Это потому, что (Cstring) — это просто выражение Cstring в скобках, а не кортеж длины 1, содержащий Cstring:
julia> (Cstring) Cstring julia> (Cstring,) (Cstring,)
На практике, особенно при предоставлении многократно используемых функций, обычно используется оболочка ccall в функциях Julia, которые настраивают аргументы и затем проверяют ошибки так, как это предписывается функцией C или Fortran. При возникновении ошибки она обрабатывается как обычное исключение Julia. Это особенно важно, поскольку API C и Fortran отличаются непостоянством в том, как они указывают на условия возникновения ошибок. Например, функция C getenv обернута в следующую функцию Julia, которая является упрощенной версией фактического определения из env.jl:
function getenv(var::AbstractString)
val = ccall(:getenv, Cstring, (Cstring,), var)
if val == C_NULL
error("getenv: undefined variable: ", var)
end
return unsafe_string(val)
end
Функция C getenv указывает на ошибку, возвращая NULL, но другие стандартные функции C указывают на ошибки различными способами, включая возвращение -1, 0, 1 и других специальных значений. Эта оболочка генерирует исключение, четко указывающее на проблему, если вызывающий процесс пытается получить несуществующую переменную среды:
julia> getenv("SHELL")
"/bin/bash"
julia> getenv("FOOBAR")
getenv: undefined variable: FOOBAR
Вот несколько более сложный пример, который определяет имя хоста локальной машины. В этом примере предполагается, что код библиотеки сетевых служб находится в динамической библиотеке с именем «libc». На практике эта функция обычно является частью стандартной библиотеки C, поэтому часть «libc» следует опустить, но мы хотим продемонстрировать здесь использование данного синтаксиса.
function gethostname()
hostname = Vector{UInt8}(undef, 256) # MAXHOSTNAMELEN
err = ccall((:gethostname, "libc"), Int32,
(Ptr{UInt8}, Csize_t),
hostname, sizeof(hostname))
Base.systemerror("gethostname", err != 0)
hostname[end] = 0 # ensure null-termination
return 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 предотвращает это, а значит, доступ к недействительному участку памяти.
Создание указателей на функции Julia, совместимые с C
Можно передавать функции Julia в функции нативного C, которые принимают аргументы-указатели на функции. Например, для соответствия прототипам C следующего вида:
typedef returntype (*functiontype)(argumenttype, ...)
Макрос @cfunction генерирует указатель на функцию, совместимый с C, для вызова функции Julia. Аргументы для @cfunction таковы:
- Функция Julia
- Тип возвращаемого значения функции
- Кортеж типов входных данных, соответствующий сигнатуре функции
Как и в случае с ccall, тип возвращаемого значения и кортеж типов входных данных должны быть литеральными константами.
В настоящее время поддерживается только платформа-зависимое соглашение C о вызовах. Это означает, что указатели, сгенерированные @cfunction, не могут использоваться в вызовах, где WINAPI ожидает функцию stdcall в 32-разрядных системах Windows, но могут использоваться в WIN64 (где stdcall унифицированы с соглашением о вызовах C).
Функции обратного вызова, экспонированные через @cfunction, не должны генерировать ошибки, так как это неожиданно вернет управление в среду выполнения Julia и может оставить программу в неопределенном состоянии.
Классический пример — функция стандартной библиотеки C qsort:
void qsort(void *base, size_t nmemb, size_t size,
int (*compare)(const void*, const void*));
Аргумент base — указатель на массив длины nmemb, с элементами по size байтам каждый. compare — функция обратного вызова, которая принимает указатели на два элемента a и b и возвращает целое число меньше/больше нуля, если a должен предшествовать/следовать за b (или ноль, если какой-либо порядок допустим).
Теперь предположим, что у нас есть одномерный массив A значений в Julia, которые мы хотим отсортировать с помощью функции qsort (а не встроенной функцией Julia sort). Прежде чем рассмотреть вызов qsort и передачу аргументов, нам необходимо написать функцию сравнения:
julia> function mycompare(a, b)::Cint
return (a < b) ? -1 : ((a > b) ? +1 : 0)
end
mycompare (generic function with 1 method)
qsort ожидает функцию сравнения, которая возвращает C int, поэтому мы аннотируем тип возвращаемого значения как 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]
4-element Vector{Float64}:
1.3
-2.7
4.4
3.1
julia> ccall(:qsort, Cvoid, (Ptr{Cdouble}, Csize_t, Csize_t, Ptr{Cvoid}),
A, length(A), sizeof(eltype(A)), mycompare_c)
julia> A
4-element Vector{Float64}:
-2.7
1.3
3.1
4.4
Как показывает пример, исходный массив Julia A теперь отсортирован: [-2.7, 1.3, 3.1, 4.4]. Обратите внимание, что Julia берет на себя преобразование массива в Ptr{Cdouble}), вычисление размера типа элемента в байтах и т. д.
Для интереса попробуйте вставить строку println("mycompare($a, $b)") в mycompare, что позволит вам увидеть сравнения, которые выполняет qsort (и убедиться, что она действительно вызывает функцию Julia, которую вы ей передали).
Сопоставление типов C с Julia
Необходимо точно сопоставить объявленный тип C с его объявлением в Julia. Несоответствия могут привести к тому, что код, корректно работающий на одной системе, может потерпеть неудачу или дать неопределенные результаты на другой системе.
Обратите внимание, что никакие заголовочные файлы C не используются в процессе вызова функций C: вы несете ответственность за то, чтобы ваши типы Julia и сигнатуры вызовов точно отражали типы и сигнатуры в заголовочном файле C.[2]
Автоматическое преобразование типов
Julia автоматически вставляет вызовы функции Base.cconvert для преобразования каждого аргумента к указанному типу. Например, следующий вызов:
ccall((:foo, "libfoo"), Cvoid, (Int32, Float64), x, y)
будет вести себя так, как если бы он был написан следующим образом:
ccall((:foo, "libfoo"), Cvoid, (Int32, Float64),
Base.unsafe_convert(Int32, Base.cconvert(Int32, x)),
Base.unsafe_convert(Float64, Base.cconvert(Float64, y)))
Base.cconvert обычно просто вызывает convert, но может быть определён для возвращения произвольного нового объекта, более подходящего для передачи в C. Это должно использоваться для всех выделений памяти, к которым будет обращаться код C. Например, это используется для преобразования Array объектов (например, строк) в массив указателей.
Base.unsafe_convert обрабатывает преобразование в типы Ptr. Это считается небезопасным, поскольку преобразование объекта в исходный указатель может скрыть объект от сборщика мусора, что приведёт к преждевременному освобождению памяти.
Соответствия типов
Сначала давайте рассмотрим некоторые релевантные термины типов Julia:
| Синтаксис / Ключевое слово | Пример | Описание |
|---|---|---|
mutable struct |
BitSet |
"Листовой тип" :: Группа связанных данных, которая включает тег типа, управляется сборщиком мусора Julia и определяется тождеством объекта. Параметры типа листового типа должны быть полностью определены (не допускаются TypeVars), чтобы экземпляр мог быть создан. |
abstract type |
Any, AbstractArray{T, N}, Complex{T}
|
"Супертип" :: Супертип (не листовой тип), который нельзя создать, но который может быть использован для описания группы типов. |
T{A} |
Vector{Int} |
"Параметр типа" :: Специализация типа (обычно используется для диспетчеризации или оптимизации хранения). |
"TypeVar" :: T в объявлении параметра типа называется TypeVar (сокращение от type variable). |
||
primitive type |
Int, Float64
|
"Примитивный тип" :: Тип без полей, но с размером. Он хранится и определяется по значению. |
struct |
Pair{Int, Int} |
"Структура" :: Тип со всеми полями, определёнными как константы. Он определяется по значению и может храниться с тегом типа. |
ComplexF64 (isbits) |
"Битовое представление" :: primitive type, или тип struct, где все поля являются другими isbits типами. Он определяется по значению и хранится без тега типа. |
|
struct ...; end |
nothing |
"Синглтон" :: Листовой тип или структура без полей. |
(...) или tuple(...)
|
(1, 2, 3) |
"Кортеж" :: Неизменяемая структура данных, похожая на анонимный тип структуры или постоянный массив. Представлен как массив или структура. |
Типы битов
Необходимо знать несколько специальных типов, так как ни один другой тип не может быть определён таким же образом:
-
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Точно соответствует анотации типа
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попытается сначала создать нуль-терминированную копию массива, заменяя каждый элемент егоBase.cconvertверсией. Это позволяет, например, передать массив указателейargvтипаVector{String}в аргумент типаPtr{Ptr{Cchar}}.
На всех поддерживаемых нами системах базовые типы значений C/C++ могут быть преобразованы в типы Julia следующим образом. Каждый тип C также имеет соответствующий тип Julia с тем же именем, но префикс C. Это может помочь при написании портативного кода (и помните, что int в C не эквивалентен Int в Julia).
Независимые от системы типы
| Имя в C | Имя в Fortran | Стандартный псевдоним Julia | Базовый тип Julia |
|---|---|---|---|
unsigned char |
CHARACTER |
Cuchar |
UInt8 |
bool (_Bool в C99+) |
Cuchar |
UInt8 |
|
short |
INTEGER*2, LOGICAL*2
|
Cshort |
Int16 |
unsigned short |
Cushort |
UInt16 |
|
int, BOOL (C, типичный) |
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 и [[noreturn]] или _Noreturn
|
Union{} |
||
void* |
Ptr{Cvoid} (или аналогично Ref{Cvoid}) |
||
T* (где T представляет собой соответствующим образом определённый тип) |
Ref{T} (T может быть безопасно изменён только в том случае, если T — тип isbits) |
||
char* (или char[], например, строка) |
CHARACTER*N |
Cstring если завершается нулём, или Ptr{UInt8} если нет |
|
char** (или *char[]) |
Ptr{Ptr{UInt8}} |
||
jl_value_t* (любой тип Julia) |
Any |
||
jl_value_t* const* (ссылка на значение Julia) |
Ref{Any} (const, поскольку изменение потребовало бы барьер записи, что невозможно правильно вставить) |
||
va_arg |
Не поддерживается | ||
... (спецификация функции с переменным числом аргументов) |
T... (где T — один из вышеперечисленных типов при использовании функции ccall) |
||
... (спецификация функции с переменным числом аргументов) |
; va_arg1::T, va_arg2::S, etc. (поддерживается только с макросом @ccall) |
Тип Cstring по существу является синонимом Ptr{UInt8}, за исключением того, что преобразование в Cstring генерирует ошибку, если строка Julia содержит какие-либо встроенные символы NUL (что привело бы к неявной обрезке строки, если C-функция рассматривает NUL как терминатор). Если вы передаёте char* в C-функцию, которая не предполагает завершение нулём (например, потому, что вы передаёте явную длину строки), или если вы точно знаете, что ваша строка Julia не содержит NUL и хотите пропустить проверку, вы можете использовать Ptr{UInt8} в качестве типа аргумента. Cstring также может использоваться в качестве типа возвращаемого значения ccall, но в этом случае он, очевидно, не вводит дополнительных проверок и предназначен только для повышения читабельности вызова.
Зависимые от системы типы
| Имя в C | Стандартный псевдоним Julia | Базовый тип Julia |
|---|---|---|
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 (если ожидаются данные, завершённые NUL), или 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 приведёт к ошибке, если сама строка содержит символы NUL).
Функции C, принимающие аргумент типа char** , могут вызываться с использованием типа Ptr{Ptr{UInt8}} в Julia. Например, функции C вида:
int main(int argc, char **argv);
могут вызываться следующим кодом Julia:
argv = [ "a.out", "arg1", "arg2" ]
ccall(:main, Int32, (Int32, Ptr{Ptr{UInt8}}), length(argv), argv)
Для функций Fortran, принимающих строки переменной длины типа character(len=*), длины строк предоставляются как скрытые аргументы. Тип и позиция этих аргументов в списке зависят от компилятора, при этом поставщики компиляторов обычно по умолчанию используют тип Csize_t и добавляют скрытые аргументы в конец списка аргументов. Хотя это поведение закреплено для некоторых компиляторов (GNU), другие по желанию разрешают размещение скрытых аргументов непосредственно после аргумента символа (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 в 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)
Параметры типов
Аргументы типа для ccall и @cfunction вычисляются статически, когда определён метод, содержащий использование. Поэтому они должны иметь вид литеральной кортежи, а не переменной, и не могут ссылаться на локальные переменные.
Это может показаться странным ограничением, но помните, что так как C — не динамический язык, как Julia, его функции могут принимать только типы аргументов со статически известной, фиксированной сигнатурой.
Однако, хотя структура типа должна быть статически известна для вычисления предполагаемого C ABI, статические параметры функции считаются частью этой статической среды. Статические параметры функции могут использоваться как параметры типа в сигнатуре вызова, если они не влияют на расположение типа. Например, f(x::T) where {T} = ccall(:valid, Ptr{T}, (Ptr{T},), x) допустимо, так как Ptr всегда является примитивным типом со значением размера слова. Но g(x::T) where {T} = ccall(:notvalid, T, (T,), x) недопустимо, так как расположение типа T статически неизвестно.
Значения SIMD
Примечание: Эта функция в настоящее время реализована только на 64-битных платформах x86 и AArch64.
Если у процедуры C/C++ есть аргумент или возвращаемое значение, являющееся родным типом SIMD, соответствующий тип Julia — это однородная кортеж из VecElement , который естественным образом отображается на тип SIMD. В частности:
- Кортеж должен иметь такой же размер, как и тип SIMD. Например, кортеж, представляющий
__m128на x86, должен иметь размер 16 байт.- Тип элемента кортежа должен быть экземпляром
VecElement{T}, гдеT— примитивный тип, размер которого составляет 1, 2, 4 или 8 байт.
Например, рассмотрим эту процедуру 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((:dist, "libdist"), m256, (m256, m256), a, b)
end
println(call_dist(a,b))
У машины-хоста должны быть необходимые регистры SIMD. Например, приведенный выше код не будет работать на машинах-хостах без поддержки AVX.
Владение памятью
malloc/free
Выделение и освобождение памяти таких объектов должны обрабатываться вызовами соответствующих процедур очистки в используемых библиотеках, как и в любом программе на C. Не пытайтесь освободить объект, полученный из библиотеки C, с помощью Libc.free в Julia, так как это может привести к тому, что функция free будет вызвана через неправильную библиотеку и приведёт к завершению процесса. Обратное (передача объекта, выделенного в Julia, для освобождения внешней библиотекой) также неверно.
Когда использовать T, Ptr{T} и 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.
Сопоставление функций C с Julia
ccall / @cfunction руководство по переводу аргументов
Для перевода списка аргументов 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 - значение аргумента будет скопировано (передано по значению)
-
-
void*- зависит от того, как используется этот параметр, сначала переведите его в нужный тип указателя, затем определите эквивалент Julia, используя оставшиеся правила в этом списке
- этот аргумент может быть объявлен как
Ptr{Cvoid}, если это действительно просто неизвестный указатель
-
jl_value_t*Any- значение аргумента должно быть допустимым объектом Julia
-
jl_value_t* const*Ref{Any}- список аргументов должен быть допустимым объектом Julia (или
C_NULL) - не может использоваться для параметра вывода, если пользователь не может отдельно организовать сохранение объекта GC
-
T*-
Ref{T}, гдеT— тип Julia, соответствующийT - значение аргумента будет скопировано, если это тип
inlinealloc(который включаетisbitsиначе значение должно быть допустимым объектом Julia
-
-
T (*)(...)(например, указатель на функцию)-
Ptr{Cvoid}(может потребоваться явно использовать@cfunctionдля создания этого указателя)
-
-
...(например, vararg)- [для
ccall]:T..., гдеT— единственный тип Julia всех оставшихся аргументов - [для
@ccall]:; va_arg1::T, va_arg2::S, etc, гдеTиS— тип Julia (то есть разделите обычные аргументы и varargs с помощью;) - в настоящее время не поддерживается
@cfunction
- [для
-
va_arg- не поддерживается
ccallили@cfunction
- не поддерживается
ccall / @cfunction руководство по переводу типов возвращаемых значений
Для перевода типа возвращаемого значения C в Julia:
-
void-
Cvoid(это вернёт единственный экземплярnothing::Cvoid)
-
-
T, гдеT— один из примитивных типов:char,int,long,short,float,double,complex,enumили любой из ихtypedefэквивалентов-
T, гдеT— эквивалентный тип данных Julia Bits (см. таблицу выше) - если
T—enum, тип аргумента должен быть эквивалентенCintилиCuint - значение аргумента будет скопировано (возвращается по значению)
-
-
struct T(включая typedef для структуры)-
T, гдеT— тип данных Julia Leaf - значение аргумента будет скопировано (возвращается по значению)
-
-
void*- зависит от того, как используется этот параметр, сначала переведите его в нужный тип указателя, затем определите эквивалент в Julia, используя оставшиеся правила в этом списке
- данный аргумент может быть объявлен как
Ptr{Cvoid}, если это действительно просто неизвестный указатель
-
jl_value_t*Any- значение аргумента должно быть допустимым объектом 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. См. Непрямые вызовы.
-
Передача указателей для изменения входных данных
Поскольку C не поддерживает несколько возвращаемых значений, часто функции C принимают указатели на данные, которые функция будет изменять. Для достижения этого в ccall необходимо сначала заключить значение в Ref{T} соответствующего типа. При передаче этого объекта Ref в качестве аргумента, Julia автоматически передаст указатель C на заключённые данные:
width = Ref{Cint}(0)
range = Ref{Cfloat}(0)
ccall(:foo, Cvoid, (Ref{Cint}, Ref{Cfloat}), width, range)
После возврата содержимое width и range можно получить (если они были изменены foo) с помощью width[] и range[]; то есть они ведут себя как нульмерные массивы.
Примеры обёртки C
Начнём с простого примера обёртки 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(
(:gsl_permutation_alloc, :libgsl), # name of C function and library
Ptr{gsl_permutation}, # output type
(Csize_t,), # tuple of input types
n # name of Julia variable to pass in
)
if output_ptr == C_NULL # Could not allocate memory
throw(OutOfMemoryError())
end
return output_ptr
end
Библиотека GNU Scientific Library (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::Ref{gsl_permutation})
ccall(
(:gsl_permutation_free, :libgsl), # name of C function and library
Cvoid, # output type
(Ref{gsl_permutation},), # tuple of input types
p # name of Julia variable to pass in
)
end
Здесь входной параметр p объявлен как Ref{gsl_permutation}, что означает, что память, на которую указывает p, может управляться Julia или C. Указатель на память, выделенную C, должен быть типа Ptr{gsl_permutation}, но его можно преобразовать с использованием Base.cconvert, и поэтому
Теперь, если вы достаточно пристально посмотрите на этот пример, вы можете заметить, что он неверен, учитывая наше вышеизложенное объяснение предпочтительных типов объявления. Видите? Функция, которую мы вызываем, освобождает память. Этот тип операции не может быть задан для объекта Julia (это приведёт к сбою или к нарушению целостности памяти). Поэтому предпочтительнее объявить тип p как Ptr{gsl_permutation }, чтобы затруднить пользователю ошибочно передать другой объект, чем тот, который получен через gsl_permutation_alloc.
Если C-обёртка никогда не ожидает, что пользователь передаст указатели на память, управляемую Julia, то использование p::Ptr{gsl_permutation} для сигнатуры метода обёртки, и аналогично в ccall, также допустимо.
Вот третий пример передачи массивов 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(
(:gsl_sf_bessel_Jn_array, :libgsl), # name of C function and library
Cint, # output type
(Cint, Cint, Cdouble, Ref{Cdouble}),# tuple of input types
nmin, nmax, x, result_array # names of Julia variables to pass in
)
if errorcode != 0
error("GSL error code $errorcode")
end
return result_array
end
C-функция, которая оборачивается, возвращает целочисленный код ошибки; результаты фактического вычисления функции Бесселя J заполняют массив Julia result_array. Эта переменная объявлена как Ref{Cdouble}, так как её память выделяется и управляется Julia. Неявный вызов Base.cconvert(Ref{Cdouble}, result_array) распаковывает указатель Julia на структуру данных массива Julia в форму, понятную C.
Пример обёртки Fortran
Следующий пример использует ccall для вызова функции в общей библиотеке Fortran (libBLAS) для вычисления скалярного произведения. Обратите внимание, что сопоставление аргументов здесь немного отличается от вышеуказанного, так как нам нужно сопоставить из Julia в Fortran. Для каждого типа аргумента мы указываем Ref или Ptr. Эта соглашение именования может быть специфична для вашего компилятора Fortran и операционной системы и, скорее всего, не документирована. Однако обертывание каждого из них в unsafe_load (или Ptr, где эквивалентно) — это частое требование реализаций компиляторов Fortran:
function compute_dot(DX::Vector{Float64}, DY::Vector{Float64})
@assert length(DX) == length(DY)
n = length(DX)
incx = incy = 1
product = ccall((:ddot_, "libLAPACK"),
Float64,
(Ref{Int32}, Ptr{Float64}, Ref{Int32}, Ptr{Float64}, Ref{Int32}),
n, DX, incx, DY, incy)
return product
end
Безопасность при сборе мусора
При передаче данных в ccall лучше избегать использования функции pointer. Вместо этого определите метод преобразования и передайте переменные непосредственно в 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 действителен, это необходимо обрабатывать другими способами.
Спецификации функций, не являющихся константными
В некоторых случаях точное имя или путь к необходимой библиотеке не известны заранее и должны вычисляться во время выполнения. Для обработки таких случаев компонент библиотеки в спецификации (name, library) может быть вызовом функции, например (:dgemm_, find_blas()). Вызов выражения будет выполнен при выполнении самого ccall . Однако предполагается, что расположение библиотеки не меняется после определения, поэтому результат вызова можно кэшировать и повторно использовать. Поэтому количество раз, когда выполняется выражение, не определено, а возвращение разных значений для нескольких вызовов приводит к неопределённому поведению.
Если требуется ещё большая гибкость, можно использовать вычисленные значения в качестве имён функций, используя eval следующим образом:
@eval ccall(($(string("a", "b")), "lib"), ...
Это выражение создаёт имя с помощью string, затем подставляет это имя в новое выражение ccall, которое затем вычисляется. Имейте в виду, что eval работает только на верхнем уровне, поэтому локальные переменные внутри этого выражения недоступны (если их значения не подставлены с помощью $). По этой причине eval обычно используется только для формирования определений верхнего уровня, например, при обертывании библиотек, содержащих много похожих функций. Аналогичный пример можно построить для @cfunction.
Однако, это будет очень медленно и вызовет утечку памяти, поэтому обычно следует этого избегать и продолжать чтение. В следующем разделе обсуждается, как использовать косвенные вызовы для эффективного достижения аналогичного эффекта.
Косвенные вызовы
Первый аргумент для ccall также может быть выражением, вычисляемым во время выполнения. В этом случае выражение должно вычисляться в Ptr, которое будет использоваться в качестве адреса вызываемой нативной функции. Это поведение возникает, когда первый аргумент ccall содержит ссылки на неконстанты, такие как локальные переменные, аргументы функций или неконстантные глобальные переменные.
Например, вы можете найти функцию с помощью dlsym, затем кешировать её в общей ссылке для этой сессии. Например:
macro dlsym(func, lib)
z = Ref{Ptr{Cvoid}}(C_NULL)
quote
let zlocal = $z[]
if zlocal == C_NULL
zlocal = dlsym($(esc(lib))::Ptr{Cvoid}, $(esc(func)))::Ptr{Cvoid}
$z[] = zlocal
end
zlocal
end
end
end
mylibvar = Libdl.dlopen("mylib")
ccall(@dlsym("myfunc", mylibvar), Cvoid, ())
Закрытия cfunctions
Первый аргумент для @cfunction может быть помечен с помощью $, в этом случае возвращаемое значение вместо этого будет struct CFunction, которое замыкает аргумент. Вы должны убедиться, что этот возвращаемый объект сохраняется до тех пор, пока все его использования не будут завершены. Содержимое и код по указателю cfunction будут удалены с помощью finalizer при освобождении этой ссылки и atexit. Обычно это не требуется, так как такой функциональности нет в 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, Cvoid, (Ptr{T}, Csize_t, Csize_t, Ptr{Cvoid}),
a, length(a), Base.elsize(a), callback)
# We could instead use:
# GC.@preserve callback begin
# use(Base.unsafe_convert(Ptr{Cvoid}, callback))
# end
# if we needed to use it outside of a `ccall`
return a
end
Закрытия @cfunction полагаются на LLVM-трамплины, которые недоступны на всех платформах (например, ARM и PowerPC).
Закрытие библиотеки
Иногда бывает полезно закрыть (разгрузить) библиотеку, чтобы её можно было перезагрузить. Например, при разработке кода 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 (symbol, library) tuple (remaining arguments are the same).
Libdl.dlclose(lib) # Close the library explicitly.
Обратите внимание, что при использовании ccall с кортежем в качестве входных данных (например, ccall((:my_fcn, "./my_lib.so"), ...)) библиотека открывается неявно, и её может не потребоваться явно закрывать.
Конвенции вызовов
Второй аргумент для ccall необязательно может быть спецификатором конвенции вызова (непосредственно перед типом возврата). Без спецификатора используется платформа-стандартная конвенция вызова C. Другие поддерживаемые конвенции: stdcall, cdecl, fastcall, и thiscall (нет операции на 64-битной Windows). Например (из base/libc.jl) мы видим тот же gethostnameccall, что и выше, но с правильной сигнатурой для Windows:
hn = Vector{UInt8}(undef, 256)
err = ccall(:gethostname, stdcall, Int32, (Ptr{UInt8}, UInt32), hn, length(hn))
Для получения дополнительной информации см. Справочник по языку LLVM.
Существует одна дополнительная специальная конвенция вызова llvmcall, которая позволяет вставлять вызовы LLVM-интринсиков непосредственно. Это может быть особенно полезно при нацеливании на необычные платформы, такие как GPGPU. Например, для CUDA нам нужно уметь считывать индекс потока:
ccall("llvm.nvvm.read.ptx.sreg.tid.x", llvmcall, Int32, ())
Как и с любым ccall, важно получить абсолютно правильную сигнатуру аргументов. Также обратите внимание, что нет уровня совместимости, который гарантирует, что интринсик имеет смысл и работает на текущей целевой платформе, в отличие от эквивалентных функций Julia, доступных через Core.Intrinsics.
Доступ к глобальным переменным
К глобальным переменным, экспортируемым нативными библиотеками, можно получить доступ по имени, используя функцию cglobal. Аргументами cglobal являются символьные спецификации, идентичные тем, что используются в ccall, и тип, описывающий значение, хранящееся в переменной:
julia> cglobal((:errno, :libc), Int32)
Ptr{Int32} @0x00007f418d0816b8
Результат — указатель, предоставляющий адрес значения. К значению можно обращаться через этот указатель, используя unsafe_load и unsafe_store!.
Этот errno символ может отсутствовать в библиотеке под названием «libc», так как это реализационная деталь вашего компилятора. Обычно символы стандартных библиотек следует получать по имени, позволяя компилятору заполнить правильный. Также, однако, символ errno в данном примере является специальным для большинства компиляторов, поэтому увиденное здесь значение, вероятно, не то, что вы ожидаете или хотите. Компиляция эквивалентного кода на C в любой системе с поддержкой многопоточности обычно фактически вызовет другую функцию (через перегрузку препроцессора макроса) и может дать другой результат, чем показанное здесь устаревшее значение.
Доступ к данным через указатель
Следующие методы описаны как «неопасные», поскольку неправильный указатель или объявление типа могут привести к неожиданному завершению работы 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 всегда перемещает указатель на определённое количество байтов, а не элементов. Таким образом, значения адресов, полученные из арифметики указателей, не зависят от типов элементов указателей.
Безопасность потоков
Некоторые библиотеки C выполняют свои обратные вызовы из другого потока, и поскольку Julia не является потокобезопасной, вам необходимо принять дополнительные меры предосторожности. В частности, вам необходимо создать двухслойную систему: обратный вызов C должен только планировать (через цикл событий Julia) выполнение вашего «реального» обратного вызова. Для этого создайте объект AsyncCondition и вызовите wait на нём:
cond = Base.AsyncCondition() wait(cond)
Обратный вызов, который вы передаёте в C, должен выполнять только ccall для :uv_async_send, передавая cond.handle в качестве аргумента, заботясь об избегании выделения памяти или других взаимодействий с временем выполнения Julia.
Обратите внимание, что события могут быть объединены, поэтому несколько вызовов uv_async_send могут привести к единственному уведомлению об активации условия.
Подробнее об обратных вызовах
Для получения более подробной информации о том, как передавать обратные вызовы в библиотеки C, ознакомьтесь с этой статьёй в блоге.
C++
Для прямого взаимодействия с C++, см. пакет Cxx. Для инструментов создания C++-связей см. пакет CxxWrap.
- 1Вызовы функций, не относящихся к библиотеке, как в C, так и в Julia, могут быть встроены, и, следовательно, у них может быть даже меньше накладных расходов, чем вызовы функций из общей библиотеки. Суть в том, что стоимость фактического вызова внешней функции примерно такая же, как и вызова в любом из родных языков.
- 2Пакет Clang может использоваться для автоматического генерации кода Julia из заголовочного файла C.
© 2009–2022 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.8/manual/calling-c-and-fortran-code/