Calling C
A module marked #![unsafe] at its top can declare C functions with
extern func and call them as Cuarzo functions:
#![unsafe]
extern func strlen(cstr text) c_size
#[symbol("abs")]
extern func absolute(c_int value) c_int
extern func snprintf(ptr[c_char] buffer, c_size size, cstr format, ...) c_int
print(strlen("héllo"), absolute(-42)) // 6 42C can break what Cuarzo promises about memory, so only modules marked unsafe may declare C functions, call them, or use the built-ins for pointers and C memory below. Other modules use what an unsafe module makes public in safe functions of its own, as the standard library does.
Module attributes come first in a file: #![unsafe], and #![link("m")]
for a library the module's C functions are in. Before an extern func,
#[link("user32")] names its library, or a framework of macOS as
#[link("framework:Foundation")], #[symbol("name")] the C name it
calls when that differs from its Cuarzo name, #[header("math.h")] a header
that declares it, which is then included and the function called by its own
name, and #[stdcall] the calling convention of 32-bit Windows. Libraries
that differ by platform go in when branches.
Without a header, the compiler declares the function itself under a name of
its own that means the C symbol, so it never clashes with a declaration in
a header.
The types of a C signature:
| Type | In C | In Cuarzo |
|---|---|---|
c_char, c_schar, c_uchar, c_short, c_ushort, c_int, c_uint, c_longlong, c_ulonglong |
the C types of those names | i8, i8, u8, i16, u16, i32, u32, i64, u64 |
c_long, c_ulong |
long, unsigned long |
i32 and u32 on Windows, i64 and u64 elsewhere |
c_size, c_ssize |
size_t, ptrdiff_t |
u64, i64 |
c_float, c_double |
float, double |
f32, f64 |
bool, c_bool |
_Bool |
bool |
| the Cuarzo numbers | the numbers of their size | themselves |
cstr |
NUL-terminated UTF-8, const char * |
a str, given as it is, without a copy |
wstr |
NUL-terminated UTF-16 | a str, converted for the call |
ptr[T] |
a pointer to a T, or NULL |
a pointer value; ptr[void] stands for any pointer |
Handle |
an opaque pointer | a pointer value that Cuarzo never looks into |
c_func(c_int, cstr) c_int |
a pointer to a C function | a value that can be called |
The C numbers are names for Cuarzo numbers, so they can be used anywhere a
number can. A cstr or wstr result becomes a str, copied, with U+FFFD
for what is not valid text, and null for NULL. A ptr[T] parameter also
takes bytes, which C may only read, and an array of T, which C may read
and write, as where their data is; null fits any pointer, handle, or C
function. out before a parameter's type makes C write it through a
pointer: the call gives a variable, as in strtol(text, out rest, 10), which
holds what C wrote afterwards. ... at the end takes variable arguments,
which C gets as it would: whole numbers as their C types, floats as
double, text as const char *.
These built-ins work with pointers and C memory:
| Built-in | Result |
|---|---|
c_alloc(size), c_realloc(p, size), c_free(p) |
C memory, zeroed by c_alloc, that the program frees itself |
deref(p), store(p, value) |
the T a ptr[T] points to, and writing one there |
offset(p, n) |
the pointer n items further on: bytes for a ptr[void] |
address_of(x) |
where the data of bytes or an array is, as a ptr |
ptr_cast(p) |
a pointer, handle, or C function as the pointer type wanted where it is used |
from_cstr(p), from_wstr(p) |
the text at a C string, copied |
to_cstr(text), to_wstr(text) |
a copy of text in C memory, for c_free |
ptr_to_int(p), int_to_ptr(n) |
a pointer as a number, and back |
load_library(name) |
a Result[Handle]: a library loaded while the program runs |
library_symbol(library, name) |
a Result[ptr[void]]: where a function or value of the library is |
close_library(library) |
unloads it |
A function found with library_symbol is called through a c_func of its
signature, so a program can use a library only when it is there:
Handle library = load_library("libz.so.1")?
c_func(cstr) cstr version = ptr_cast(library_symbol(library, "zlibVersion")?)
print(version())A pointer into the data of a Cuarzo value is good only while the value
lives and does not grow; reading through deref a pointer that is null
stops the program, but nothing checks any other pointer.
C structs, unions, and arrays
extern struct and extern union declare types with the layout of C:
extern struct Rect do
c_long left
c_long top
c_long right
c_long bottom
end
#[link("user32")]
extern func GetClientRect(Handle window, ptr[Rect] area) c_int
Rect area = Rect() // zeroed
GetClientRect(window, area) // C writes it
print(area.right - area.left)Fields have the types of C signatures, other C structs, held in place, and
fixed-size arrays, c_array[c_int, 4], which are read and written an item
at a time with their index checked, and whose len is their size.
#[packed] before the declaration leaves out the padding between fields,
and #[align(16)] aligns the type. With #[header("x.h")] the type is the
one the header defines, struct Rect or the name #[symbol("RECT")]
gives, and its fields are those of the header; functions that a header
declares take and give such structs by value. A value of a C struct is a reference to
its memory: Rect() makes one zeroed, with the fields given in order or by
name set, as in Rect(right: 10); reading a field that is a struct or an
array gives a reference to it, in place, so shape.corner.x = 7 changes
shape; assigning a struct to a field copies it. A C struct is given to a
parameter of its type by value, and to a ptr of it as where it is; a
struct C gives by value is copied into a new one. deref of a pointer to a
struct is the struct where the pointer points, in C memory, and store
copies one there; address_of gives where a struct is.
size_of(T) and align_of(T) give the size and the alignment of a C type,
as size_of(Rect) or size_of(c_array[c_short, 3]).
Callbacks and what C keeps
c_callback(f) makes a C function that calls the Cuarzo function f, for
C that calls back, as a comparison for qsort, a window procedure, or an
event handler. Its signature is the c_func type wanted where it is used:
extern func qsort(ptr[void] items, c_size count, c_size size, c_func(ptr[void], ptr[void]) c_int compare)
c_func(ptr[void], ptr[void]) c_int ascending = c_callback(func(ptr[void] a, ptr[void] b) c_int do
ptr[i32] x = ptr_cast(a)
ptr[i32] y = ptr_cast(b)
return deref(x) - deref(y)
end)
i32[] numbers = [5, 3, 9]
qsort(numbers, len(numbers), 4, ascending)
release_callback(ascending)A callback's frames lead to the Cuarzo code that called C, in runtime
traces. Up to 32 callbacks of one signature can be alive at once;
release_callback frees one that C will not call again, and a callback
called after that, or on a thread other than the program's, stops the
program. An extern function's name used as a value is its C function, as
in c_func(cstr) c_size measure = strlen.
C may keep a pointer to a Cuarzo value only while something keeps the
value alive: keep_alive(value) gives a Handle that does, which can be
given to C as its user data, kept(handle) gives the value back, as the
type wanted where it is used, and release_kept(handle) lets it go. A handle
used after its release, or for another type, stops the program. Values do
not move, so a kept value is also pinned.
on_collect(value, f) runs f once the collector finds value
unreachable, to free what C gave for it, as in
on_collect(image, func() do c_free(pixels) end). f must not use value
itself, which would keep it reachable, and runs when the collector next
runs, while the program allocates, so a close method that frees at once
is better where the program knows when it is done. Without the collector
(--no-gc), finalizers never run.
C enums are numbers: constants such as const c_int WM_CLOSE = 0x0010, or
the values a header defines. extern const reads a constant of C, and
extern var reads and writes a global variable; with #[header] they are
the names the header defines, macros too:
#[header("limits.h")]
extern const c_int INT_MAX
#[header("errno.h")]
extern var c_int errnoA header in #[header] is found on GCC's include path, as <name> is,
unless its path starts with ./ or ../, which are from the folder of
the module, or is a full path.
Bindings from a header
cuarzo bindgen writes the Cuarzo declarations of a C header: its
functions, structs and unions, enums and #define constants, and global
variables.
cuarzo bindgen /usr/include/gtk-4.0/gtk/gtk.h --prefix gtk_ --link gtk-4 -o gtk.czGCC preprocesses the header, so its macros and includes are worked out as
a C compiler would, and -I <dir> says where its includes are. The
declarations written are those of the header and of the other headers in
its folder and the folders in it, as a library's headers are;
--prefix gtk_ keeps only those whose names start with it, in any case,
so GTK_ constants too. The structs that the declarations use are written
wherever they come from. --link gtk-4 adds #![link("gtk-4")], and -o
writes to a file instead of to standard output.
Each C type becomes the type of a C signature: int is c_int,
size_t is c_size, int32_t is i32, const char * is cstr, char *, which C may write, is ptr[c_char], a pointer to a struct the header
defines is ptr[S] and to one it does not is Handle, and a pointer to a
function is a c_func. An array in a struct is a c_array, and an array
of arrays one c_array of all their items. A struct inside another
without a name of its own in C is named after the struct and the field
that hold it. A name that Cuarzo has already, as a keyword, a type, or a
built-in such as abs, gets _ after it, with #[symbol] saying the C
name; so does a function named as a struct, since C keeps struct tags
apart (struct stat and stat()). What Cuarzo has no type for, as
bit-fields, long double, and structs packed to 2 or 4 bytes, is left
out, with a comment at the end that says why.
With --header <name>, each declaration has #[header(name)], so the
program is compiled against the header itself: its static inline functions
and macros can be called, and its structs are its own types, which
extern struct with #[header] and #[symbol("type")] names. The
program is then built with the header's includes found where GCC looks,
such as the folders CPATH names.