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. argtypes соответствуют сигнатуре C-функции, а argvalues являются фактическими значениями аргументов, которые будут переданы функции.

Note

Смотрите ниже, как 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 следующие:

  1. Функция Julia
  2. Тип возвращаемого значения функции
  3. Кортеж типов входных данных, соответствующий сигнатуре функции
Note

Как и в случае с @ccall, тип возвращаемого значения и типы входных данных должны быть литеральными константами.

Note

В настоящее время поддерживается только стандартный C вызов по умолчанию для платформы. Это означает, что указатели, сгенерированные с помощью @cfunction, не могут использоваться в вызовах, где WINAPI ожидает функцию stdcall на 32-битных Windows, но могут использоваться на WIN64 (где stdcall объединен с C вызовом).

Note

Функции обратного вызова, предоставляемые через @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 / KeywordExampleDescription
mutable structBitSet"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 typeAny, 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 typeInt, Float64"Primitive Type" :: A type with no fields, but a size. It is stored and defined by-value.
structPair{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 ...; endnothing"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 nameFortran nameStandard Julia AliasJulia Base Type
unsigned charCHARACTERCucharUInt8
bool (_Bool in C99+)CucharUInt8
shortINTEGER*2, LOGICAL*2CshortInt16
unsigned shortCushortUInt16
int, BOOL (C, typical)INTEGER*4, LOGICAL*4CintInt32
unsigned intCuintUInt32
long longINTEGER*8, LOGICAL*8ClonglongInt64
unsigned long longCulonglongUInt64
intmax_tCintmax_tInt64
uintmax_tCuintmax_tUInt64
floatREAL*4iCfloatFloat32
doubleREAL*8CdoubleFloat64
complex floatCOMPLEX*8ComplexF32Complex{Float32}
complex doubleCOMPLEX*16ComplexF64Complex{Float64}
ptrdiff_tCptrdiff_tInt
ssize_tCssize_tInt
size_tCsize_tUInt
voidCvoid
void and [[noreturn]] or _NoreturnUnion{}
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*NCstring 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_argNot 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 nameStandard Julia AliasJulia Base Type
charCcharInt8 (x86, x86_64), UInt8 (powerpc, arm)
longClongInt (UNIX), Int32 (Windows)
unsigned longCulongUInt (UNIX), UInt32 (Windows)
wchar_tCwchar_tInt32 (UNIX), UInt16 (Windows)
Note

При вызове Fortran все входные данные должны передаваться по указателям на значения, выделенные в куче или на стеке, поэтому все соответствия типов выше должны содержать дополнительную обертку Ptr{..} или Ref{..} вокруг их спецификации типа.

Warning

Для строковых аргументов (char*) тип Julia должен быть Cstring (если ожидаются данные с нулевым терминатором) или Ptr{Cchar} или Ptr{UInt8} в противном случае (эти два типа указателей имеют одинаковый эффект), как описано выше, а не String. Аналогично, для аргументов массивов (T[] или T*) тип Julia снова должен быть Ptr{T}, а не Vector{T}.

Warning

Тип Char в Julia занимает 32 бита, что не соответствует типу широких символов (wchar_t или wint_t) на всех платформах.

Warning

Тип возвращаемого значения Union{} означает, что функция не будет возвращать значение, т.е. C++11 [[noreturn]] или C11 _Noreturn (например, jl_throw или longjmp). Не используйте это для функций, которые не возвращают значение (void), но действительно возвращают, для таких используйте Cvoid вместо.

Note

Для аргументов wchar_t* тип Julia должен быть Cwstring (если C-рутина ожидает строку, заканчивающуюся нулем), или Ptr{Cwchar_t} в противном случае. Также обратите внимание, что данные строк в формате UTF-8 в Julia внутренне заканчиваются нулем, поэтому их можно передавать в C-функции, ожидающие данные, заканчивающиеся нулем, без создания копии (но использование типа Cwstring вызовет ошибку, если сама строка содержит нулевые символы).

Note

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
Note

Для функций 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))
Warning

Компиляторы Fortran могут также добавлять другие скрытые аргументы для указателей, массивов с предполагаемой формой (:) и массивов с предполагаемым размером (*). Такое поведение можно избежать, используя ISO_C_BINDING и включая bind(c) в определение подпрограммы, что настоятельно рекомендуется для совместимого кода. В этом случае не будет скрытых аргументов, но за счет некоторых возможностей языка (например, только character(len=1) будет разрешено для передачи строк).

Note

Функция 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
  • 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
Note

Закрытие @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 следующие:

  1. Пара (:function, "library") (наиболее распространенная),

    ИЛИ

    символ имени :function или строка имени "function" (для символов в текущем процессе или libc),

    ИЛИ

    указатель на функцию (например, из dlsym).

  2. Тип возвращаемого значения функции

  3. Кортеж типов входных данных, соответствующий сигнатуре функции. Одна распространенная ошибка заключается в том, что забывают, что 1-кортеж типов аргументов должен быть записан с завершающей запятой.

  4. Фактические значения аргументов, которые должны быть переданы функции, если таковые имеются; каждый является отдельным параметром.

Note

Пара (:function, "library"), тип возвращаемого значения и типы входных данных должны быть буквальными константами (т.е. они не могут быть переменными, но см. Non-constant Function Specifications).

Оставшиеся параметры оцениваются во время компиляции, когда определяется содержащий метод.

Ниже представлена таблица переводов между макро- и функциональными интерфейсами.

@ccallccall
@ccall clock()::Int32ccall(:clock, Int32, ())
@ccall f(a::Cint)::Cintccall(:a, Cint, (Cint,), a)
@ccall "mylib".f(a::Cint, b::Cdouble)::Cvoidccall((:f, "mylib"), Cvoid, (Cint, Cdouble), (a, b))
@ccall $fptr.f()::Cvoidccall(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)::Cintccall(: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) мы видим тот же gethostnameccall, как и выше, но с правильной сигнатурой для 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!.

Note

Этот символ 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.