C interop, 2 of 7
Strings and buffers
Pass text to C as CStrings, keep a C string alive with CString.From, and share memory with C through a Buffer.
What you’ll build
A program that hands text and memory to C functions in three ways: a String passed straight to a
CString parameter, a C string that you make, change, and free yourself, and a Buffer that C
fills and FutureBlitz reads, sharing one block of memory without copying it.
Strings for a call
A String passed for a CString parameter is copied, with a NUL after it, right before the call,
and freed right after it. That suits nearly every C function that reads text.
Import std.core
Extern "C"
Function strlen(text: CString): UInt64
Function strstr(text: CString, part: CString): String?
End Extern
Print(strlen("hello, world"))
Print(strstr("hello, world", "wor") ?? "not found")12 world
Because the copy is freed after the call, C mustn’t keep the pointer. A function that gives back a
pointer into its argument, as strstr does, is declared to give a String?, so its result is
copied into a String before the argument’s copy goes. Declared to give a CString instead, it
would only take CStrings you made yourself, which is the next step.
A C string of your own
CString.From(text) makes a C string that lasts until its Free(), for a C function that keeps
the pointer or writes into it. Only Unsafe code reads its text, with ToString(), or frees it,
since using one after it’s freed would break memory.
Import std.core
Import std.ffi
Extern "C"
Function toupper(c: Int32): Int32
End Extern
Local name = CString.From("ada")
Unsafe
Local letters = name As Ptr<UInt8>
letters[0] = toupper(letters[0] As Int32) As UInt8
Print(name.ToString())
name.Free()
End UnsafeAda
name As Ptr<UInt8> sees the C string as its bytes, which the code changes in place. A CString’s
zero is Null, C’s NULL, and ToString() on a Null one panics.
A Buffer that C fills
std.ffi’s Buffer<T> is memory for a number of Ts, all 0 at first, at an address that doesn’t
move. C reads and writes it through its Pointer(), and FutureBlitz code reads and sets it with
[], which checks each index in every build.
Import std.core
Import std.ffi
Extern "C"
Function memset(target: Ptr<Void>, value: Int32, count: UInt64): Ptr<Void>
End Extern
Local bytes = Buffer<UInt8>(8)
Unsafe
memset(bytes.Pointer(), 42, 5)
End Unsafe
Print(bytes.ToArray())
bytes[7] = 1
Print(bytes[7], bytes.Count)
Print(bytes.Slice(2, 6).ToArray())[42, 42, 42, 42, 42, 0, 0, 0] 1 8 [42, 42, 42, 0]
Only Unsafe code gets the Pointer(), which is good while the Buffer is. Slice(start, finish)
is a view of part of it: unlike an array’s slice, which copies, a Buffer’s Slice shares its
memory, so a change through either one shows in the other, and the view keeps the Buffer alive.
ToArray() copies its contents into an array. A Buffer frees its memory once nothing refers to
it, as any object does.
What you learned
- A String passed for a
CStringparameter is copied for the call and freed after it. CString.Frommakes a C string that lasts untilFree(), which Unsafe code calls.Buffer<T>is memory that C and FutureBlitz share, checked by[]and freed automatically.Pointer()hands a Buffer to C, from Unsafe code.- A Buffer’s
Sliceis a view that shares its memory, where an array’s slice is a copy.
The rules are in the language reference.
Report an issue with this page or with FutureBlitz on GitHub.