Pass structure containing String to C code

ccall

#1

pass structure containing string to C code

type simcase
    a::Int32
    b::Int32
    c::Cstring
end

n = 2
m = 3
x = randn(n,m)
s = simcase(3,4,Cstring("hello"))
ccall((:printArray, "./test_array.so"), Void, ( Ptr{Float64}, Int32, Int32, simcase), x, Int32(n), Int32(m), s)

produces:

julia> ERROR: LoadError: MethodError: Cannot `convert` an object of type String to an object of type Cstring
This may have arisen from a call to the constructor Cstring(...),
since type constructors fall back to convert methods.
 in Cstring(::String) at ./sysimg.jl:53
 in eval_user_input(::Any, ::Base.REPL.REPLBackend) at ./REPL.jl:64
 in macro expansion at ./REPL.jl:95 [inlined]
 in (::Base.REPL.##3#4{Base.REPL.REPLBackend})() at ./event.jl:68
while loading /home/nbecker/julia-test/test_array.jl, in expression starting on line 10

What’s the correct way to do this? I want the C code to get a structure containing a null-terminated C-string.


#2

Define Base.cconvert, Base.unsafe_convert to convert a normal julia object with a julia string to a C compatible object with Cstring field.
e.g. with immutable simcase define Base.cconvert(::Type{simcase}, v::Tuple) = (v[1], v[2], Base.cconvert(Cstring, v[3])) and Base.unsafe_convert(::Type{simcase}, v::Tuple) = simcase(v[1], v[2], Base.unsafe_convert(Cstring, v[3])) and call the function with ccall(..., (..., simcase), .., (3, 4, "hello"))


#3

I’m still struggling here. Let me try to ask again more clearly.

I need to pass a structure to C. One of the fields in the structure has to be a string which the C code will see as a C-string: an array of null-terminated bytes. So, in julia:

type simcase
a::Int32
s:: ???
end

So I don’t know, maybe s is Array{UInt8,1}?

All julia has to do is fill in this field from a string literal.

Somehow when passed to C it needs to see:
struct {
int a;
char* s;
};


#4

That’s exactly the question I’m answering. Your original simcase is the correct type definition (just change it to immutable to simplify the unsafe_convert def a little bit).


#5

OK, so I have this:

immutable simcase
n::Int32
m::Int32
s::Cstring
end

n = 2
m = 3
x = randn(n,m)

Base.cconvert(::Type{simcase}, v::Tuple) = (v[1], v[2], Base.cconvert(Cstring, v[3]))
Base.unsafe_convert(::Type{simcase}, v::Tuple) = simcase(v[1], v[2], Base.unsafe_convert(Cstring, v[3]))
ccall((:printArray, “./test_array.so”), Void, ( Ptr{Float64}, simcase), x, (n, m, “hello”)) << this works
ccall((:printArray, “./test_array.so”), Void, ( Ptr{Float64}, simcase), x, simcase(n, m, “hello”)) << this doesn’t work

So I can manage to pass the structure to C as you show. But I will also need to create and manipulate simcase objects, which I don’t know how to do. And it’s not clear to me what’s different about the ccall above the works, and the ccall that doesn’t work (because explicitly tries to create a simcase object first).

For example:
simcase (2, 3, “hi”)
simcase(2,3,“hi”)
ERROR: MethodError: Cannot convert an object of type String to an object of type Cstring
This may have arisen from a call to the constructor Cstring(…),
since type constructors fall back to convert methods.
in simcase(::Int64, ::Int64, ::String) at /home/nbecker/julia-test/test_array.jl:2
in eval_user_input(::Any, ::Base.REPL.REPLBackend) at ./REPL.jl:64
in macro expansion at ./REPL.jl:95 [inlined]
in (::Base.REPL.##3#4{Base.REPL.REPLBackend})() at ./event.jl:68


#6

Right, you have to convert the object to C compatible layout when you pass it to C. The solution I gave just use a tuple as the input type for simplicity. If you want to use a special type for it instead of a tuple, you can just replace the tuple with it in the input of cconvert

type simcase_julia
    a::Int32
    b::Int32
    c::String
end
Base.cconvert(::Type{simcase}, v::simcase_julia) = (v.a, v.b, Base.cconvert(Cstring, v.c))

P.S. Please quote your code. See @ihnorton’s edit of your first post.


#7

Thanks! This seems to work:

immutable simcase_c
    n::Int32
    m::Int32
    s::Cstring
end

type simcase
    a::Int32
    b::Int32
    c::String
end
Base.cconvert(::Type{simcase_c}, v::simcase) = (v.a, v.b, Base.cconvert(Cstring, v.c))

n = 2
m = 3
x = randn(n,m)
Base.unsafe_convert(::Type{simcase_c}, v::Tuple) = simcase_c(v[1], v[2], Base.unsafe_convert(Cstring, v[3]))
ccall((:printArray, "./test_array.so"), Void, ( Ptr{Float64}, simcase_c), x, simcase(n, m, "hello"))


Although it works, I don’t understand what this means

Base.cconvert(::Type{simcase_c}, v::simcase) = (v.a, v.b, Base.cconvert(Cstring, v.c))

The result on the RHS is just a tuple.


#8

See the document for what unsafe_convert and cconvert do.

I general, the return value of unsafe_convert should match what C want and that of cconvert should keep all julia references alive. The return value of cconvert will be given to unsafe_convert as argument. Since cconvert doesn’t have to match any ABI and only need to keep cconvert(Cstring, ...) alive, I’m just using a Tuple as return value for inconvenience (same reason why I used a tuple as input type for cconvert too).