ccall真的转换指针传递的参数吗?

时间:2018-10-30 00:32:44

标签: arrays pointers julia implicit-conversion ffi

将动态库与此(实现良好的)本地函数一起考虑,该函数可返回数组中所有偶数(32位无符号)的和:

uint32_t sum_of_even(const uint32_t *numbers, size_t length);

我编写了以下Julia(v1.0.1)包装函数:

lib = Libdl.dlopen(libname)
sumofeven_sym = Libdl.dlsym(lib, :sum_of_even)

sumofeven(a) = ccall(
    sumofeven_sym,
    UInt32,
    (Ptr{UInt32}, Csize_t),
    a, length(a)
)

文档多次声明ccall中的参数被转换为与C函数原型兼容:

  

通过自动插入对argvalue的调用,ccall中的每个argtype都将转换为相应的unsafe_convert(argtype, cconvert(argtype, argvalue))。 (有关更多详细信息,另请参见unsafe_convertcconvert的文档。)在大多数情况下,这只会导致调用convert(argtype, argvalue)

此外,当Array{T}Ptr{U}传递给C函数时,如果T和U这两种类型不同,则调用无效,因为没有添加重新解释转换({{ 3}}):

  

当将数组作为Ptr{T}参数传递给C时,不会重新解释:Julia要求数组的元素类型与T匹配,并且第一个元素的地址为通过。

     

因此,如果Array包含错误格式的数据,则必须使用诸如trunc(Int32, a)之类的调用对其进行显式转换。

但是,事实并非如此。如果我故意传递带有另一个类型元素的数组:

println(sumofeven(Float32[1, 2, 3, 4, 5, 6]))

程序执行C函数并触发不确定的行为,从而导致无意义的输出或分段错误。如果我重新定义该函数以接受Ref{UInt32}而不是Ptr{UInt32},则可以正确地阻止我使用浮点数组来调用它:

ERROR: LoadError: MethodError: Cannot `convert` an object of type Array{Float32,1} to an object of type UInt32
Closest candidates are:
  convert(::Type{T<:Number}, !Matched::T<:Number) where T<:Number at number.jl:6
  convert(::Type{T<:Number}, !Matched::Number) where T<:Number at number.jl:7
  convert(::Type{T<:Integer}, !Matched::Ptr) where T<:Integer at pointer.jl:23
  ...

要使示例与Ptr{UInt32}一起使用,需要我将Array{UInt32}指定为输入类型a(静态实施),或者首先将convert设置为数组(对于功能更灵活):

sumofeven(a:: Array{UInt32}) = ccall( # ← either this
    sumofeven_sym,
    UInt32,
    (Ptr{UInt32}, Csize_t),
    convert(Array{UInt32}, a), # ← or this
    length(a))

这样,我仍然觉得我的推理存在差距。当文档说未重新解释传递给C作为Ptr{T}的数组时,该文档真正暗示了什么?为什么茱莉亚让我不经过任何显式转换就传递一个不同元素类型的数组?

1 个答案:

答案 0 :(得分:-1)

这取决于透视图(issue #29850),可能是核心库中的错误,还是非常误导的文档。函数unsafe_convert的行为从0.4版更改为0.5版,其方式比目前建议的更加灵活。

根据此commitunsafe_convert从此更改:

unsafe_convert(::Type{Ptr{Void}}, a::Array) = ccall(:jl_array_ptr, Ptr{Void}, (Any,), a)

对此:

unsafe_convert{S,T}(::Type{Ptr{S}}, a::AbstractArray{T}) = convert(Ptr{S}, unsafe_convert(Ptr{T}, a))

对于数组,这种宽松的实现将使从T数组到另一种S类型的指针的转换成为可能。实际上,与C ++命名法一样,unsafe_convert(cconvert(array))将重新解释并广播数组的基本指针。整个FFI边界处都有一个危险的重新解释的数组。

关键要点是,将数组传递给C函数时需要格外小心,因为C调用函数参数中数组的元素类型不是静态实施的。在适用时使用类型签名和/或显式转换。