调用C和Fortran代码
虽然大多数代码都可以用Julia编写,但有许多高质量,成熟的数字计算库已经用C和Fortran编写。 为了方便使用现有的代码,Julia使调用C和Fortran函数变得简单高效。 Julia有一个"没有样板"的理念:函数可以直接从Julia调用,而不需要任何"胶水"代码,代码生成或编译-甚至从交互式提示符。 这是通过与 @ccall宏(或不太方便 ccall语法,见 ccall 语法部分)。
要调用的代码必须作为共享库可用。 大多数C和Fortran库已经编译为共享库,但如果您使用GCC(或Clang)自己编译代码,则需要使用 -共享 和 -fPIC 选择。 Julia的JIT生成的机器指令与本机C调用相同,因此产生的开销与从C代码调用库函数相同。 脚注:1[C和Julia中的非库函数调用可以内联,因此可能比对共享库函数的调用具有更少的开销。 上面的观点是,实际执行外部函数调用的成本与使用任何一种母语执行调用的成本大致相同。]
默认情况下,Fortran编译器https://en.wikipedia.org/wiki/Name_mangling#Fortran[generate mangled names](例如,将函数名转换为小写或大写,通常附加下划线),因此要调用Fortran函数,您必须传递与Fortran编译器后面的规则相对应的mangled标识符。 此外,在调用Fortran函数时,所有输入都必须作为指向堆或堆栈上分配值的指针传递。 这不仅适用于通常是堆分配的数组和其他可变对象,也适用于标量值,如整数和浮点数,它们通常是堆栈分配的,并且在使用C或Julia调用约定时通常在寄存器中传递。
的语法 @ccall生成对库函数的调用是:
@ccall library.function_name(argvalue1::argtype1, ...)::returntype
@ccall function_name(argvalue1::argtype1, ...)::returntype
@ccall $function_pointer(argvalue1::argtype1, ...)::returntype
哪里 图书馆 是一个字符串常量或字面值(但请参阅 非恒定功能规格下面)。 可以省略库,在这种情况下,函数名在当前进程中解析。 此表单可用于调用C库函数、Julia运行时中的函数或链接到Julia的应用程序中的函数。 还可以指定库的完整路径。 或者, @ccall 也可用于调用函数指针 $函数_pointer,如一个返回 的Libdl。[医]dlsym. 该 argtype,argtypes对应于C函数签名和 [医]银价s是要传递给函数的实际参数值。
|
注意请参阅下文了解如何 将C类型映射到Julia类型。 |
作为一个完整但简单的示例,以下调用 时钟 大多数Unix派生系统上的标准C库函数:
julia> t = @ccall clock()::Int32
2292761
julia> typeof(t)
Int32
时钟 不接受参数并返回 Int32. 致电 [医]盖 函数获取一个指向环境变量值的指针,就像这样调用:
julia> path = @ccall getenv("SHELL"::Cstring)::Cstring
Cstring(@0x00007fff5fbffc45)
julia> unsafe_string(path)
"/bin/bash"
在实践中,特别是在提供可重用功能时,通常会包装 @ccall 在Julia函数中使用,这些函数设置参数,然后以C或Fortran函数指定的任何方式检查错误。 如果发生错误,它将作为正常的Julia异常抛出。 这一点尤其重要,因为C和Fortran Api对于它们如何指示错误条件是出了名的不一致。 例如, [医]盖 C库函数包含在以下Julia函数中,Julia函数是实际定义的简化版本https://github.com/JuliaLang/julia/blob/master/base/env.jl[脧锚脧赂`恩维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 [医]盖 函数通过返回指示错误 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库函数 [医]盖斯特名 使用主机名填充数组。 最后,它需要一个指向主机名缓冲区的指针,并将该指针转换为Julia字符串,假设它是一个以null结尾的C字符串。
C库通常使用这种要求调用者分配内存以传递给被调用者并填充的模式。 像这样从Julia分配内存通常是通过创建一个未初始化的数组并将指向其数据的指针传递给C函数来完成的。 这就是为什么我们不使用 C字符串 type here:由于数组未初始化,它可能包含空字节。 转换为 C字符串 作为 @ccall 检查包含的空字节,因此可能引发转换错误。
取消引用 指针(主机名) 与 unsafe_string 是一个不安全的操作,因为它需要访问分配给 主机名 这可能是在收集垃圾的同时。 宏 GC。@保留防止这种情况发生,从而访问无效的内存位置。
最后,这里是通过路径指定库的示例。 我们创建一个具有以下内容的共享库
#include <stdio.h>
void say_y(int y)
{
printf("Hello from C: got y = %d.\n", y);
}
并编译它与 gcc-fPIC-shared-o mylib.so 米利布。c. 然后可以通过指定(绝对)路径作为库名称来调用它:
julia> @ccall "./mylib.so".say_y(5::Cint)::Cvoid
Hello from C: got y = 5.
创建与C兼容的Julia函数指针
可以将Julia函数传递给接受函数指针参数的本机C函数。 例如,要匹配表单的c原型:
typedef returntype (*functiontype)(argumenttype, ...)
宏 @cfunction为调用Julia函数生成与C兼容的函数指针。 的论点 @cfunction是:
-
Julia函数
-
函数的返回类型
-
输入类型的元组,对应于函数签名
|
注意与 |
|
注意目前,仅支持平台默认的c调用约定。 这意味着 |
|
注意通过 |
一个典型的例子是标准C库 qsort,qsort 函数,声明为:
void qsort(void *base, size_t nitems, size_t size,
int (*compare)(const void*, const void*));
该 基地 参数是指向长度数组的指针 n.尼特斯,与元素 大小 个字节。 比较一下 是一个回调函数,它接受指向两个元素的指针 a 和 b 并返回一个整数小于/大于零,如果 a 应该出现在之前/之后 b (如果允许任何订单,则为零)。
现在,假设我们有一个1-d数组 A 我们要使用Julia中的值进行排序 qsort,qsort 功能(而不是Julia的内置 排序 功能)。 在我们考虑打电话之前 qsort,qsort 和传递参数,我们需要写一个比较函数:
julia> function mycompare(a, b)::Cint
return (a < b) ? -1 : ((a > b) ? +1 : 0)
end;
qsort,qsort 期望一个返回C的比较函数 int型,所以我们将返回类型注释为 辛特.
为了将此函数传递给C,我们使用宏获取其地址 @cfunction:
julia> mycompare_c = @cfunction(mycompare, Cint, (Ref{Cdouble}, Ref{Cdouble}));
@cfunction需要三个参数:Julia函数([医]麦康柏),返回类型(辛特),以及输入参数类型的字面元组,在这种情况下对 Cdouble/Cdouble (漂浮64)元素。
最后的召唤 qsort,qsort 看起来像这样:
julia> A = [1.3, -2.7, 4.4, 3.1];
julia>@ccall qsort(A::Ptr{Cdouble},长度(A)::Csize_t,sizeof(eltype(a))::Csize_t,mycompare_c::Ptr{Cvoid})::Cvoid
朱莉娅>A
4元素向量{Float64}:
-2.7
1.3
3.1
4.4
如示例所示,原始Julia数组 A 现已排序: [-2.7, 1.3, 3.1, 4.4]. 请注意,朱莉娅 负责将数组转换为 Ptr{Cdouble}),以字节为单位计算元素类型的大小,等等。
为了好玩,尝试插入一个 println("mycompare($a,b b)") 排队进入 [医]麦康柏,这将让你看到比较 qsort,qsort 正在执行(并验证它是否真的在调用您传递给它的Julia函数)。
将C类型映射到Julia
将声明的C类型与其在Julia中的声明完全匹配至关重要。 不一致会导致在一个系统上正常工作的代码失败或在另一个系统上产生不确定的结果。
请注意,在调用C函数的过程中,任何地方都没有使用C头文件:您有责任确保您的Julia类型和调用签名准确地反映C头文件中的那些。脚注:2[Clang包可用于从C头文件自动生成Julia代码。]
自动类型转换
Julia自动插入调用 基地。[医转换]函数将每个参数转换为指定的类型。 例如,以下调用:
@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
基地。[医转换 通常只是打电话 转换/转换,但可以定义为返回一个更适合传递给C的任意新对象。这应该用于执行C代码将访问的所有内存分配。 例如,这是用来转换一个 阵列 指向指针数组的对象(例如字符串)。
基地。unsafe_转换处理转换为 Ptr类型。 它被认为是不安全的,因为将对象转换为本机指针可能会从垃圾回收器中隐藏该对象,从而导致其过早释放。
类型对应
首先,让我们回顾一些相关的Julia类型术语:
| 语法/关键字 | 例子: | 资料描述 |
|---|---|---|
|
|
"具体类型"::一组包含类型标记的相关数据,由Julia GC管理,并由object-identity定义。 具体类型的类型参数必须完全定义(否 |
|
|
"Super Type"::无法实例化的超类型(不是具体类型),但可用于描述一组类型。 另请参阅 |
|
|
"类型参数"::类型的特化(通常用于调度或存储优化)。 |
"打字":: |
||
|
|
"原始类型"::没有字段,但有大小的类型。 它由-value存储和定义。 |
|
|
"Struct"::将所有字段定义为常量的类型。 它由-value定义,并且可以与type-tag一起存储。 |
|
"Is-Bits"::A |
|
|
|
"Singleton"::没有字段的具体类型或结构。 |
|
|
"元组"::一个不可变的数据结构,类似于匿名结构类型,或常量数组。 表示为数组或结构。 |
比特类型
有几个特殊类型需要注意,因为没有其他类型可以被定义为行为相同:
* 漂浮物32
完全对应于 浮子,浮子 输入C(或 真实*4 在Fortran中)。
* 漂浮64
完全对应于 双倍 输入C(或 真实*8 在Fortran中)。
* 复杂的32
完全对应于 复杂浮点数 输入C(或 综合大楼*8 在Fortran中)。
* 复杂的f64
完全对应于 复杂的双 输入C(或 综合大楼*16 在Fortran中)。
* 签署
完全对应于 签署 c中的类型注释(或任何 整数 在Fortran中键入)。 任何Julia类型不是 签署被假定为无符号。
* 参考{T}
行为像一个 Ptr{T} 它可以通过Julia GC来管理它的内存。
* 阵列{T,N}
当一个数组作为一个 Ptr{T} 参数,它不是reinterpret-cast:Julia要求数组的元素类型匹配 T,并传递第一个元素的地址。
因此,如果一个 阵列 包含格式错误的数据,必须使用以下调用显式转换 trunc.(Int32,A).
传递数组 A 作为不同类型的指针_without_事先转换数据(例如,传递 漂浮64 数组到对未解释字节操作的函数),您可以将参数声明为 Ptr{Cvoid}.
如果eltype的数组 Ptr{T} 被传递为 Ptr{Ptr{T}} 论点, 基地。[医转换]将尝试首先创建一个以null结尾的数组副本,每个元素都被其替换 基地。[医转换]版本。 这允许,例如,通过一个 [医]argv 类型的指针数组 向量{String} 类型的参数 Ptr{Ptr{Cchar}}.
在我们目前支持的所有系统上,基本的C/C++ 值类型可以转换为Julia类型,如下所示。 每个C类型也有一个同名的Julia类型,以C为前缀。这可以帮助编写可移植代码(并记住 int型 在C是不一样的一个 Int型 在朱莉娅)。
*系统独立类型*
| C名称 | Fortran名称 | 标准Julia别名 | 朱莉娅基型 |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|||
|
|
||
|
|
||
|
|
|
|
|
|
||
|
|
||
|
|
||
|
不支持 |
||
|
|
||
|
|
该 C字符串类型本质上是一个同义词 Ptr{UInt8},除了转换为 C字符串 如果Julia字符串包含任何嵌入的空字符(如果C例程将null视为终止符,则会导致字符串被静默截断),则引发错误。 如果你通过 查尔* 到一个不假设null终止的C例程(例如,因为你传递了一个明确的字符串长度),或者如果你肯定知道你的Julia字符串不包含null并且想要跳过检查,你可以使用 Ptr{UInt8} 作为参数类型。 C字符串 也可用作 ccall返回类型,但在这种情况下,它显然不会引入任何额外的检查,只是为了提高调用的可读性。
*系统相关类型*
| C名称 | 标准Julia别名 | 朱莉娅基型 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
注意当调用Fortran时,所有的输入都必须通过指向堆或堆栈分配值的指针传递,所以上面的所有类型对应都应该包含一个额外的 |
|
字符串参数的警告( |
|
警告朱莉娅的 |
|
注意事项 |
|
注意接受类型参数的c函数
可以通过以下Julia代码调用:
|
|
使用类型可变长度字符串的Fortran函数的注意事项
可以通过以下Julia代码调用,其中附加了长度
|
|
警告Fortran编译器_may_还为指针添加其他隐藏参数,假定形状( |
|
注意一个声明为返回的C函数 |
结构类型对应
复合类型,如 结构体 在C或 类型 在Fortran90(或 结构 / 记录 在F77的一些变体中),可以通过创建一个 结构体 具有相同字段布局的定义。
递归使用时, 轨道,轨道 类型内联存储。 所有其他类型都存储为指向数据的指针。 当镜像c中另一个结构体中按值使用的结构体时,必须不要尝试手动复制字段,因为这不会保留正确的字段对齐方式。 相反,声明一个 轨道,轨道 结构类型并使用它。 在对Julia的翻译中不可能使用未命名的结构。
Julia不支持打包结构和联合声明。
你可以得到一个近似值 工会 如果你知道,先验,将具有最大尺寸的字段(可能包括填充)。 将字段转换为Julia时,请声明Julia字段仅为该类型。
参数数组可以用 N.婴儿. 例如,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)其中 {T} =@ccall有效(x::Ptr{T})::Ptr{T} 是有效的,因为 Ptr 始终是一个字大小的基本类型。 但是, g(x::T)其中 {T} =@ccall notvalid(x::T)::T 无效,因为 T 不是静态已知的。
SIMD值
如果一个C/C++ 例程有一个本机SIMD类型的参数或返回值,相应的Julia类型是 VecElement,VecElement 这自然映射到SIMD类型。 具体来说:
*元组的大小和元素必须与SIMD类型相同。 例如,一个元组表示一个 __m128 在x86必须有16个字节和Float32元素的大小。
+
*元组的元素类型必须是
VecElement{T}哪里T是一个具有2次幂字节数的基元类型(例如1, 2, 4, 8, 16, 等)如Int8或Float64。
例如,考虑这个使用AVX内部函数的c例程:
#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代码调用 区划 使用 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))
函数call_dist(a::m256,b::m256)
@ccall"libdist"。区(a::m256,b::m256)::m256
结束
println(call_dist(a,b))
主机必须具有必需的SIMD寄存器。 例如,上面的代码将无法在没有AVX支持的主机上工作。
内存所有权
*`马洛克`/免费*
这些对象的内存分配和解除分配必须通过调用正在使用的库中的适当清理例程来处理,就像在任何C程序中一样。 不要试图释放从c库接收的对象 Libc。免费在朱莉娅,因为这可能会导致 免费 通过错误的库调用函数并导致进程中止。 相反(传递在Julia中分配的对象以由外部库释放)同样无效。
何时使用 T, Ptr{T} 和 参考{T} -和-Ref{T}}
在Julia代码包装调用外部C例程中,普通(非指针)数据应该声明为类型 T 在 @ccall,因为它们是按值传递的。 对于c代码接受指针, 参考{T}通常应该用于输入参数的类型,允许使用指向由Julia或C通过隐式调用管理的内存的指针 基地。[医转换]. 相反,调用的C函数返回的指针应声明为输出类型 Ptr{T},反映指向的内存仅由C管理。 C结构中包含的指针应表示为类型的字段 Ptr{T} 在相应的Julia结构类型中,旨在模仿相应的C结构的内部结构。
在Julia代码包装调用外部Fortran例程中,所有输入参数都应声明为类型 参考{T},因为Fortran通过指向内存位置的指针传递所有变量。 返回类型应该是 [医]静脉曲张 对于Fortran子例程,或 T 对于返回类型的Fortran函数 T.
将C函数映射到Julia
@ccall / @cfunction 论点翻译指南
用于将C参数列表翻译为Julia:
* T,在哪里 T 是基元类型之一: 查尔, int型, 长, 短, 浮子,浮子, 双倍, 综合体, 枚举 或任何他们的 打字,打字 等价物
** T,在哪里 T 是等效的Julia Bits类型(根据上表)
**如果 T 是一个 枚举,参数类型应等价于 辛特 或 昆特
**参数值将被复制(按值传递)
* 结构T (包括typedef到struct)
** T,在哪里 T 是混凝土Julia类型
**参数值将被复制(按值传递)
* 向量T (或 __属性__vector_size,或一个typedef如 __m128)
** NTuple{N, VecElement{T}},在哪里 T 是正确大小的原始Julia类型,N是向量中的元素数(等于 vector_size/Sizeof T).
* 作废*
**取决于此参数的使用方式,首先将其转换为预期的指针类型,然后使用此列表中的其余规则确定Julia等价物
**此参数可声明为 Ptr{Cvoid} 如果它真的只是一个未知的指针
* jl_value_t*
** 任何
**参数值必须是有效的Julia对象
* jl_value_t*const*
** 参考{Any}
**参数列表必须是有效的Julia对象(或 C_NULL)
**不能用于输出参数,除非用户能够单独安排对象被GC保留
* T*
** 参考{T},在哪里 T Julia类型是否对应于 T
**参数值将被复制,如果它是一个 内联合金 类型(其中包括 等位,等位 否则,该值必须是有效的Julia对象
* T(*)(。..) (例如指向函数的指针)
** Ptr{Cvoid} (您可能需要使用 @cfunction显式创建此指针)
* ... (例如vararg)
**
**
**目前不支持 @cfunction
* va_arg
**不支持 ccall 或 @cfunction
@ccall / @cfunction 返回类型翻译指南
用于将C返回类型转换为Julia:
* 作废
** [医]静脉曲张 (这将返回单例实例 无::Cvoid)
* T,在哪里 T 是基元类型之一: 查尔, int型, 长, 短, 浮子,浮子, 双倍, 综合体, 枚举 或任何他们的 打字,打字 等价物
**与C参数列表相同
**参数值将被复制(按值返回)
* 结构T (包括typedef到struct)
**与C参数列表相同
**参数值将被复制(按值返回)
* 向量T
**与C参数列表相同
* 作废*
**取决于此参数的使用方式,首先将其转换为预期的指针类型,然后使用此列表中的其余规则确定Julia等效项
**此参数可声明为 Ptr{Cvoid} 如果它真的只是一个未知的指针
* jl_value_t*
** 任何
**参数值必须是有效的Julia对象
* jl_value_t**
** Ptr{Any} (参考{Any} 作为返回类型无效)
* T*
**如果内存已经由Julia拥有,或者是 轨道,轨道 类型,并且已知为非null:
*** 参考{T},在哪里 T Julia类型是否对应于 T
***返回类型 参考{Any} 是无效的,它应该是 任何 (对应于 jl_value_t*)或 Ptr{Any} (对应于 jl_value_t**)
***C*不得*修改通过返回的内存 参考{T} 如果 T 是一个 等位,等位 类型
**如果内存由C拥有:
*** Ptr{T},在哪里 T Julia类型是否对应于 T
* T(*)(。..) (例如指向函数的指针)
** Ptr{Cvoid} 要直接从Julia调用它,您需要将其作为第一个参数传递给 @ccall. 见 间接呼叫。
传递用于修改输入的指针
由于C不支持多个返回值,因此通常C函数将获取指向函数将修改的数据的指针。 要在 @ccall,需要先将值封装在一个 参考{T}的适当类型。 当你通过这个 参考书 object作为参数,Julia会自动传递一个c指针给封装的数据:
width = Ref{Cint}(0)
range = Ref{Cfloat}(0)
@ccall foo(width::Ref{Cint}, range::Ref{Cfloat})::Cvoid
返回时, 阔度 和 范围 可以检索(如果它们被 [医]脚)由 宽度[] 和 范围[] 也就是说,它们的作用就像零维数组。
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 "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
该https://www.gnu.org/software/gsl/[GNU科学图书馆](这里假设可以通过 :libgsl)定义不透明指针, gsl_截肢 *,作为C函数的返回类型 gsl_permutation_alloc. 由于用户代码永远不必查看 gsl_截肢 struct,相应的Julia包装器只需要一个新的类型声明, gsl_截肢,它没有内部字段,其唯一目的是放在a的类型参数中 Ptr 类型。 的返回类型 ccall声明为 Ptr{gsl_permutation},由于由分配和指向的内存 输出_ptr 被C控制。
输入 n 通过值传递,因此函数的输入签名被简单地声明为 ::Csize_t 没有任何 参考书 或 Ptr 有必要。 (如果包装器调用的是Fortran函数,则相应的函数输入签名将是 ::Ref{Csize_t},因为Fortran变量是通过指针传递的。)此外, n 可以是可转换为 Csize_t 整数; ccall隐式调用 基地。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返回一个整数错误代码;bessel J函数的实际评估结果填充Julia数组 结果_阵列. 此变量声明为 参考{Cdouble},因为它的内存是由Julia分配和管理的。 隐式调用 基地。cconvert(Ref{Cdouble},result_array)将指向Julia数组数据结构的Julia指针解压缩为C可以理解的形式。
Fortran包装器示例
以下示例使用 ccall 调用通用Fortran库(libBLAS)中的函数来计算点积。 请注意,这里的参数映射与上面的有点不同,因为我们需要从Julia映射到Fortran。 在每个参数类型上,我们指定 参考书 或 Ptr. 此mangling约定可能特定于您的Fortran编译器和操作系统,并且可能未记录。 但是,将每个包裹在 参考书 (或 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
垃圾收集安全
当将数据传递给 @ccall,最好避免使用 指针功能。 而是定义一个 基地。[医转换]方法并将变量直接传递给 @ccall. @ccall 自动安排它的所有参数将从垃圾回收中保留,直到调用返回。 如果C API将存储对Julia分配的内存的引用,则在 @ccall 返回,您必须确保对象对垃圾回收器保持可见。 建议的方法是创建一个类型的全局变量 向量{Ref} 以保持这些值,直到C库通知您完成它们。
每当您创建了一个指向Julia数据的指针时,您必须确保原始数据存在,直到您完成使用该指针。 Julia中的许多方法如 卸载/卸载和 字符串制作数据副本,而不是获取缓冲区的所有权,以便在不影响Julia的情况下释放(或更改)原始数据是安全的。 一个值得注意的例外是 unsafe_wrap出于性能原因,共享(或可以被告知获得所有权)基础缓冲区。
垃圾回收器不保证任何定稿顺序。 也就是说,如果 a 载有对 b 两者兼而有之 a 和 b 是由于垃圾收集,没有保证 b 将在之后最终确定 a. 如果适当的定稿 a 取决于 b 为有效,则必须以其他方式处理。
非恒定功能规格
在某些情况下,所需库的确切名称或路径并不预先知道,必须在运行时计算。 为了处理这种情况,库组件规范可以是函数调用,例如 find_blas()。dgemm. 调用表达式将在 ccall 本身被执行。 然而,假定库位置一旦确定就不改变,因此调用的结果可以被缓存和重用。 因此,表达式执行的次数是未指定的,并且为多个调用返回不同的值会导致未指定的行为。
如果需要更大的灵活性,可以通过以下方式使用计算值作为函数名 埃瓦尔如下:
@eval @ccall "lib".$(string("a", "b"))()::Cint
此表达式使用 字符串,然后将此名称替换为新的 @ccall 表达式,然后对其进行评估。 记住这一点 埃瓦尔 仅在顶层操作,因此在此表达式中,局部变量将不可用(除非它们的值被替换为 $). 为此原因, 埃瓦尔 通常仅用于形成顶级定义,例如在包装包含许多类似函数的库时。 类似的例子可以被构造用于 @cfunction.
但是,这样做也会非常缓慢并泄漏内存,因此您通常应该避免这种情况,而是继续阅读。 下一节讨论如何使用间接调用来有效地实现类似的效果。
间接呼叫
第一个论点 @ccall 也可以是运行时计算的表达式。 在这种情况下,表达式必须计算为 Ptr,其将作为本机函数的地址来调用。 此行为发生在第一 @ccall argument包含对非常量的引用,例如局部变量、函数参数或非常量全局变量。
例如,您可以通过以下方式查找函数 [医]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
闭包功能
第一个论点 @cfunction可以用一个 $,在这种情况下,返回值将改为 结构,结构 这结束了争论。 您必须确保此返回对象保持活动状态,直到完成对其的所有使用。 Cfunction指针上的内容和代码将通过 终结器,终结器当这个引用被删除和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(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
|
注闭 |
关闭图书馆
有时关闭(卸载)库以便可以重新加载它是有用的。 例如,在开发与Julia一起使用的C代码时,可能需要编译,从Julia调用C代码,然后关闭库,进行编辑,重新编译并加载新的更改。 可以重新启动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),库是隐式打开的,可能不会显式关闭。
变量函数调用
调用变分C函数a 分号 可以在参数列表中用于将所需参数与可变参数分开。 一个例子与 printf打印 功能如下所示:
julia> @ccall printf("%s = %d\n"::Cstring ; "foo"::Cstring, foo::Cint)::Cint
foo = 3
8
ccall 界面
还有另一种替代接口 @ccall. 这个接口稍微不太方便,但它确实允许一个人指定一个 调用约定。
的论点 ccall是:
-
A
(:函数,"库")对(最常见), 或 a:功能名称符号或"功能"名称字符串(用于当前进程或libc中的符号), 或 一个函数指针(例如,从[医]dlsym). -
函数的返回类型
-
输入类型的元组,对应于函数签名。 一个常见的错误是忘记了参数类型的1元组必须用尾随逗号编写。
-
要传递给函数的实际参数值(如果有的话);每个值都是一个单独的参数。
|
注意 |
在编译时,定义包含方法时,将评估其余参数。
下面给出了宏和函数接口之间的转换表。
@ccall |
ccall |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
调用约定
第二个论点 ccall (紧接在返回类型之前)可以选择是调用约定说明符( @ccall 宏目前不支持给出调用约定)。 如果没有任何说明符,则使用平台默认的c调用约定。 其他支持的约定有: stdcall, cdecl, 快车道,而 这个球 (64位Windows上的no-op)。 例如(从 基/libc。jl 我们看到的是一样的 [医]盖斯特名``ccall 如上所述,但具有适用于Windows的正确签名:
hn = Vector{UInt8}(undef, 256)
err = ccall(:gethostname, stdcall, Int32, (Ptr{UInt8}, UInt32), hn, length(hn))
如需更多资料,请参阅https://llvm.org/docs/LangRef.html#calling-conventions[LLVM语言参考]。
还有一个额外的特殊调用约定 n.有限责任公司,它允许直接插入对LLVM内部函数的调用。 这在针对Gpgpu等不寻常平台时特别有用。 例如,对于https://llvm.org/docs/NVPTXUsage.html[CUDA],我们需要能够读取线程索引:
ccall("llvm.nvvm.read.ptx.sreg.tid.x", llvmcall, Int32, ())
与任何 ccall,获得参数签名完全正确至关重要。 另外,请注意,不像下面公开的等效Julia函数那样,没有兼容层可以确保内部有意义并在当前目标上工作 核心。内在函数.
访问全局变量
julia> cglobal((:errno, :libc), Int32)
Ptr{Int32} @0x00007f418d0816b8
结果是一个指针给出值的地址。 该值可以通过此指针操作,使用 卸载/卸载和 unsafe_store!.
|
注意这一点 |
通过指针访问数据
以下方法被描述为"不安全",因为错误的指针或类型声明可能导致Julia突然终止。
给定一个 Ptr{T},类型的内容 T 通常可以从引用的内存复制到Julia对象中,使用 unsafe_load(ptr,[索引]). 索引参数是可选的(默认值为1),并遵循基于1的索引的Julia约定。 此函数故意类似于 getindex,getindex和 setindex!(例如 [] 访问语法)。
返回值将是一个初始化为包含引用内存内容副本的新对象。 引用的内存可以安全地释放或释放。
如果 T 是 任何,则假设内存包含对Julia对象的引用(a jl_value_t*),结果将是对此对象的引用,并且不会复制该对象。 在这种情况下,您必须小心,以确保该对象始终对垃圾回收器可见(指针不计算在内,但新引用不计算在内),以确保内存不会过早释放。 请注意,如果对象最初不是由Julia分配的,那么新对象将永远不会被Julia的垃圾收集器最终确定。 如果 Ptr 本身实际上是一个 jl_value_t*,它可以通过以下方式转换回Julia对象引用 unsafe_pointer_to_objref(ptr). (朱莉娅价值观 v 可以转换为 jl_value_t* 指针,如 Ptr{Cvoid},通过调用 pointer_from_objref(v).)
反向操作(将数据写入 Ptr{T}),可以使用 unsafe_store!(ptr,值,[索引]). 目前,这只支持原始类型或其他无指针(轨道,轨道)不可变的结构类型。
任何引发错误的操作可能目前都没有实现,应该作为一个错误发布,以便可以解决它。
如果感兴趣的指针是纯数据数组(原始类型或不可变结构),则函数 unsafe_wrap(Array,ptr,dims,own=false)可能更有用。 如果Julia应该"拥有"底层缓冲区并调用,则最终参数应该为true 免费(ptr) 当返回 阵列 对象最终确定。 如果 自己的 参数被省略或为false,调用方必须确保缓冲区保持存在,直到所有访问完成。
算术上的 Ptr 输入Julia(例如使用 +)的行为与C的指针算术不同。 将整数添加到a Ptr 在Julia中,指针总是移动一些_bytes_,而不是元素。 这样,从指针算术获得的地址值不依赖于指针的元素类型。
螺纹安全
一些C库从不同的线程执行它们的回调,并且由于Julia不是线程安全的,因此您需要采取一些额外的预防措施。 特别是,你需要建立一个两层的系统:C回调应该只_schedule_(通过Julia的事件循环)执行你的"真实"回调。 要做到这一点,创建一个 异步控制对象和 等等!就可以了:
cond = Base.AsyncCondition()
wait(cond)
你传递给C的回调应该只执行一个 ccall到 :uv_async_send,通过 康德。手柄 作为参数,注意避免与Julia运行时的任何分配或其他交互。
请注意,事件可能会合并,因此多次调用 uv_async_发送 可能导致一个单一的唤醒通知的条件。