Converting Ptr of subtype to Ref of supertype in ccall

ccall

#1

My question comes down to, should this be an error:

julia> Base.cconvert(Ref{Real}, Ptr{Integer}(1))
ERROR: MethodError: Cannot `convert` an object of type Ptr{Integer} to an object of type Real
This may have arisen from a call to the constructor Real(...),
since type constructors fall back to convert methods.
Stacktrace:
 [1] Base.RefValue{Real}(::Ptr{Integer}) at .\refpointer.jl:42
 [2] convert(::Type{Ref{Real}}, ::Ptr{Integer}) at .\refpointer.jl:53
 [3] cconvert(::Type{T} where T, ::Ptr{Integer}) at .\essentials.jl:306

In the documentation it states that:

Here, the input p is declared to be of type Ref{gsl_permutation}, meaning that the memory that p points to may be managed by Julia or by C. A pointer to memory allocated by C should be of type Ptr{gsl_permutation}, but it is convertable using Base.cconvert() and therefore can be used in the same (covariant) context of the input argument to a ccall.

I do follow that, but now I have the additional complication that I want to also allow Refs to subtypes. Mockup example:

abstract type Mammal end
abstract type Bear <: Mammal end

# here is a pointer that comes from a ccall
Ptr{Bear}(1)

# now it needs to go into a function that should work for any Mammal
# and since memory might also be owned by Julia, it is Ref
function staywarm(m::Ref{<:Mammal})
    # so far so good, the Ptr{Bear} can enter this function
    ccall(:zoo, Void, (Ref{Mammal},), m)
    # but it cannot do the ccall since it will fail to Base.cconvert(Ref{Mammal}, m)
end

Looking in essentials.jl I see this hits the top line. If I change the Ptr to Ref in the second line it does work for me, but seems to cause chaos in the test suite.

cconvert(T::Type, x) = convert(T, x) # do the conversion eagerly in most cases
cconvert(::Type{<:Ptr}, x) = x # but defer the conversion to Ptr to unsafe_convert

#2

You can’t type the ccall with a Ref to an abstract type. What is C supposed to do with an Abstract Julia type?
I think this can be changed to:

function staywarm(m::Ref{T}) where T <: Mammal
    ccall(:zoo, Void, (Ptr{T},), m)
end

Of course this means, that there must be a zoo function in C that works with all mammals…


#3

Thanks Simon, the (Ptr{T},) works indeed, but is that safe if the memory is owned by Julia? If I try your solution with (Ref{T},) I get:

error compiling staywarm: ccall: argument type Ref should have an element type, not Ref{T}

In the end they are all just opaque pointers, but with the abstract types I try to tell Julia that functions like this:


Can accept subtypes of GDALMajorObjectH, which they can also in the C library.

Because Julia is told it is a subtype:

This is working well as-is, but I wanted to switch the input types from Ptr{T} to Ref{T} because I believe it’s more general and possibly allow Julia owned objects to be used as input. Or am I mistaken in this?


#4

In a cruel turn of events, Ref is actually an abstract type! :wink:

No, I meant how is ccall supposed to work with an Abstract type? The annotation in ccall is for annotating the function 1:1 like the types that you would put into a C header file - and you wouldn’t (usually) find an abstract Julia type in a C header.
So since your C function expects a pointer, you need to use Ptr and not Ref.
The pair of ccall_type::Ptr{T} => ccall_arg::Ref{T} is already the semantic for using Julia owned objects.
So Ref is just a container for a Julia object which can be savely converted to a C Ptr, while still being owned by Julia.
That’s also why Ref is abstract, because different julia object need different Ref types (e.g. an Array).


#5

Many thanks, it’s starting to dawn on me. If it should be 1:1 I should just always use Ptr{Void} as the ccall input type right? Since in C these are all typedef void *

function staywarm(m::Ref{<:Mammal})
    ccall(:zoo, Void, (Ptr{Void},), m)
end

And then if I want to use it with the right C-compatible Julia structs instead:

struct Cat <: Mammal  # I believe Simon likes cats
    a::UInt8
    b::Float64
end

Then I can use this function as follows:

a_cat = Cat(0x01, 2.0)
ref_cat = Ref(a_cat)
staywarm(ref_cat)