I’m calling SimpleBLE which has an API where both the reading and writing functions take (uint8_t** data, size_t* data_length) that are used to return data in the read case and pass data in the write case.
My confusion lies in how one properly prepares a reference to a Julia vector to be passed to C.
How do I prepare, for instance, a string to be passed as the data in the write case? Since RefValue{Vector{UInt8}} can’t convert to Ptr{Ptr{UInt8}}
MethodError: no method matching unsafe_convert(::Type{Ptr{Ptr{UInt8}}}, ::Base.RefValue{Vector{UInt8}})
Would it be (this seems to run at least)
s = "adsf" |> codeunits |> Vector{UInt8}
data_length= Ref(length(s))
data = Ref(pointer(s))
Usually in an API like that the C function will be allocating the array, so you shouldn’t be passing a reference to an existing Julia vector. Instead, you would do something like:
data_ptr = Ref{Ptr{UInt8}}()
data_length = Ref{Csize_t}()
@ccall somelibrary.somefunction(data_ptr::Ref{Ptr{UInt8}}, data_length::Ref{Csize_t})::Cvoid # if it returns void
data = unsafe_wrap(Array, data_ptr[], data_length[], owns=true)
This “sort of works” if the C API wants an existing array but:
you don’t need to convert to a Vector{UInt8}, the codeunits already gives you a vector-like view, and you can just call pointer(s) directly anyway to get a pointer to the bytes of a string
length(s) is wrong — you want sizeof(s), which is the number of bytes. length(s) returns the number of Unicode characters, which is generally ≥ the number of bytes because non-ASCII characters require multiple bytes
the pointer is not rooted so you need to call GC.@preserve on the Julia array (or the string s if you just use a codeunits view or pointer(s)) for the @ccall, to make sure that the array isn’t garbage-collected during the C call
I’m suspicious that the C API wants a uint8_t** — is it planning to modify the pointer in any way, e.g. by calling realloc or free? If so you can’t pass a pointer to a Julia array
Assuming that the C API wants a uint8_t** to an existing array and doesn’t plan to modify the pointer (which makes no sense as an API but whatever), you could just do:
s = "some string"
GC.@preserve s @ccall(somelibrary.somefunction(pointer(s)::Ref{Ptr{UInt8}}, sizeof(s)::Ref{Csize_t})::Cvoid) # assuming it returns void