How to create a Cstring from a String?

I’m trying to implement callback functions for a C library, to get and set the clipboard text. My functions look like this:

clipboard_get(::Ptr{Cvoid})::Cstring = string(clipboard())
const CLIPBOARD_FN_GET = @cfunction(clipboard_get, Cstring, (Ptr{CVoid{, ))

clipboard_set(::Ptr{Cvoid}, value::Cstring)::Cvoid = clipboard(unsafe_string(value))
const CLIPBOARD_FN_SET = @cfunction(clipboard_set, Cvoid, (Ptr{Cvoid}, Cstring))

I’m testing the functions with a manual C-style call:

println("Clipboard is: ", ccall(CLIPBOARD_FN_GET, Cstring, (Ptr{Cvoid}, ), Ptr{Cvoid}(C_NULL)))

However, I get a method error on the call:


ERROR: LoadError: MethodError: Cannot `convert` an object of type String to an object of type Cstring
Closest candidates are:
  convert(::Type{Cstring}, ::Union{Ptr{Int8}, Ptr{Nothing}, Ptr{UInt8}}) at C:\Users\manni\Documents\Dev Tools\Julia\v1.7.2\share\julia\base\c.jl:167       
  convert(::Type{T}, ::T) where T at C:\Users\manni\Documents\Dev Tools\Julia\v1.7.2\share\julia\base\essentials.jl:218

So what is the correct way to create a Cstring from a String?

How do you want to manage the memory for this? If Julia allocates a String object and returns a pointer to the data to C (which is possible via unsafe_convert), then the C code could crash if Julia garbage-collects the String before the C pointer is used (hence the “unsafe”). If you are okay with this — if you know for certain that the Julia string will not be garbage-collected before you use it (e.g. because you hold onto a reference to it), then you can call Base.unsafe_convert(Cstring, s) on s::String.

If C expects a malloc-ed string that C will take responsibility to free, then you’ll need to explicitly call malloc and copy the data yourself. e.g.

function malloc_cstring(s::String)
    n = sizeof(s)+1 # size in bytes + NUL terminator
    return GC.@preserve s @ccall memcpy(Libc.malloc(n)::Cstring,
                                        s::Cstring, n::Csize_t)::Cstring
end

Conversely, when you declare an argument of a ccall as a Cstring (as is done in the call to memcpy here), then Julia can simply pass a pointer to the string data (assuming the data only needs to stick around for the duration of the ccall), first checking that the string contains no NUL bytes (as otherwise NUL-termination will not work; internally, Julia String data is already NUL-terminated so that it can be passed to C functions without copying).

2 Likes

Worth noting that Julia has a built-in clipboard function: julia/clipboard.jl at master · JuliaLang/julia · GitHub

1 Like

Thanks. The C library is not taking ownership of the string, so I guess I need to hold onto a persistent buffer.

I don’t have a lot of control over when/where the C library invokes this callback, but I do know when the library starts up and when it closes. So is it reasonable to manuallly Libc.malloc a buffer when starting the library, and Libc.free it when closing the library? Otherwise I’m not sure how to keep the memory @preserve-d as needed

If you are managing the memory yourself, I wouldn’t bother with malloc/free. Just store it as a String in Julia, keep it somewhere in Julia so that it isn’t garbage-collected (e.g. have an global array of strings), and pass the pointer via Base.unsafe_convert(Cstring, mystring). When you are done with the library you can remove the string(s) from your global array to allow them to be garbage-collected.

1 Like