Interfacing with C
Most of the code a program could use is written in C: the operating system's own interfaces, the C runtime, compression, databases, graphics, cryptography. tiko calls C directly, with no wrapper layer between: a C function is declared, and called like any other. This chapter covers declaring C functions by hand and having the compiler read a C header instead, matching C's types and layouts, passing strings and callbacks across, calling conventions, linking a library — including one shipped inside the project's own tree — and loading one while the program runs.
Every listing here runs on every target. Several print different answers on each, and those differences are the point.
Declaring a C function
'' Two ways to declare a C function. The C runtime is always linked,
'' so neither needs a library named.
'' One at a time: 'cdecl' for C's convention, 'alias' for its exact
'' name, so the tiko name can differ from it.
declare function CLength cdecl alias "strlen" _
( byval s as const zstring ptr ) as uinteger
'' A block: everything inside is cdecl, and keeps its name as written.
extern "C"
declare function toupper( byval c as int32 ) as int32
declare function abs_ alias "abs" ( byval n as int32 ) as int32
end extern
dim as zstring * 16 word = "interface"
print "strlen: "; CLength( @word )
for i as int32 = 0 to len( word ) - 1
word[ i ] = toupper( word[ i ] )
next
print "toupper: "; word
print "abs:"; abs_( -42 )
strlen: 9
toupper: INTERFACE
abs: 42
A C function is declared with declare, like a tiko one, plus three things C needs:
cdecl: C's calling convention. On x86-64 every convention is the same, but sayingcdeclkeeps the declaration honest and matches what the C header says.alias "name": the exact name the linker looks for. tiko's names are case-insensitive and C's are not, soaliasfixes the spelling, and it also lets the tiko name differ:CLengthabove is C'sstrlen.lib "name"(optional): the library the function lives in, which is then linked. The C runtime is always linked, so the listing names none.
extern "C" … end extern declares a block at once. Everything inside is cdecl, and every
name keeps the exact spelling it was written with, so toupper links to C's toupper with no
alias. Inside the block, alias still renames: abs_ is C's abs, renamed because abs is a
tiko function already. extern "C" lib "name" names a library for the whole block.
A declaration is a promise the compiler cannot check: it believes every parameter type and the result type, and a mistake shows up when the program runs, not when it compiles. Copy the types from the C header carefully, which is the first thing the next section is about — or let the compiler read the header itself, which is the section after it.
Matching C's types
#include once "crt/long.bi"
#include once "crt/stddef.bi"
'' C's types do not have the same sizes everywhere.
print "int "; sizeof( int32 )
print "long "; sizeof( clong )
print "long long "; sizeof( int64 )
print "size_t "; sizeof( size_t )
print "void * "; sizeof( any ptr )
print "wchar_t "; sizeof( wchar_t )
print "wstring character "; sizeof( wstring * 1 )
'' The compiler says which C runtime it is building against.
#ifdef __FB_MSVC__
print "C runtime: Microsoft's"
#else
print "C runtime: the system's"
#endif
On Windows:
int 4
long 4
long long 8
size_t 8
void * 8
wchar_t 2
wstring character 2
C runtime: Microsoft's
On Linux:
int 4
long 8
long long 8
size_t 8
void * 8
wchar_t 4
wstring character 4
C runtime: the system's
On macOS:
int 4
long 8
long long 8
size_t 8
void * 8
wchar_t 4
wstring character 4
C runtime: the system's
Most C types have one tiko equivalent on every platform:
| C | tiko |
|---|---|
char, signed char, unsigned char | byte / ubyte |
short, unsigned short | int16 / uint16 |
int, unsigned int | int32 / uint32 |
long long, unsigned long long | int64 / uint64 |
float, double | float32 / float64 |
size_t, intptr_t | uinteger / integer, or size_t from crt/stddef.bi |
T * | T ptr; void * is any ptr |
const char * | const zstring ptr |
| a function pointer | a type alias for a function cdecl( … ) or sub cdecl( … ) |
Two do not:
- C's
longis 4 bytes on Windows and 8 on Linux and macOS. Useclongandculongfromcrt/long.bi, which follow the platform. Writingint32for alongworks on Windows and breaks everywhere else. wchar_tis 2 bytes on Windows (UTF-16) and 4 on Linux and macOS (UTF-32). tiko'swstringfollows the same rule, so awstring ptris awchar_t *everywhere.
long is not int32. A C function returning long declared as int32 loses the top half of
every value on Linux and macOS, and nothing complains — the Windows build, where the two agree, works. The
solution to exercise 2 uses strtol, which returns long, for that reason.
__FB_MSVC__ is defined when the program is built against Microsoft's C runtime — the default
on Windows, the windows-x86_64 target. __FB_WIN32__, __FB_LINUX__ and __FB_DARWIN__ name the platform. The
crt/ headers (crt/string.bi, crt/stdlib.bi, crt/math.bi and the rest, or all of them at
once through crt.bi) declare the C runtime by hand, ready to use.
Structs and their layout
'' A struct C will read must match C's layout byte for byte.
'' Natural alignment: 'value' starts on a 4-byte boundary, as in C.
type Natural
as ubyte tag
as int32 value
end type
'' 'field = 1' packs it, as C's #pragma pack(1) does.
type Packed field = 1
as ubyte tag
as int32 value
end type
print "Natural: size"; sizeof( Natural ); _
", value at"; offsetof( Natural, value )
print "Packed: size"; sizeof( Packed ); _
", value at"; offsetof( Packed, value )
'' A fixed-size zstring is an inline char array, like C's char[8].
type Record
as zstring * 8 name_
as int32 id
end type
print "Record: size"; sizeof( Record ); _
", id at"; offsetof( Record, id )
Natural: size 8, value at 4
Packed: size 5, value at 1
Record: size 12, id at 8
A type lays its fields out the way C lays out a struct on the same platform: each field on a
boundary of its own size, with padding between where needed, and the whole rounded up to its
largest alignment. So a type with the same fields in the same order as a C struct has the same
size and the same offsets, and a pointer to one can be handed to C.
field = 1 packs a type with no padding, as C's #pragma pack(1) does; field = 2 and
field = 4 limit the alignment the same way. Use them only to match a C struct that is packed —
a packed type is slower to use, and an unpacked C struct read through a packed type is garbage.
A fixed-size zstring * n is an inline array of n characters, like C's char name[n]; a
ustring field is not, since it holds a pointer to the text, and never belongs in a struct C
will read. offsetof( T, field ) checks an offset against the C header.
Strings across the boundary
extern "C"
declare function strlen( byval s as const zstring ptr ) as uinteger
declare function strchr( byval s as const zstring ptr, _
byval c as int32 ) as zstring ptr
declare function malloc( byval n as uinteger ) as any ptr
declare sub free( byval p as any ptr )
declare function strcpy( byval dst as zstring ptr, _
byval src as const zstring ptr ) _
as zstring ptr
end extern
'' Into C: a string's own bytes, ending in a zero, through strptr.
dim as string s = "key=value"
print "length from C: "; strlen( strptr( s ) )
'' Out of C: a pointer into memory the C side owns. '*p' copies the
'' characters into a tiko string at once.
dim as zstring ptr eq = strchr( strptr( s ), asc( "=" ) )
if eq <> nullptr then
dim as string rest = *( eq + 1 )
print "after '=': "; rest
end if
'' Memory C allocates, C must free.
dim as zstring ptr copy_ = malloc( len( s ) + 1 )
strcpy( copy_, strptr( s ) )
dim as ustring u = *copy_
free( copy_ )
print "copied into a ustring: "; u
length from C: 9
after '=': value
copied into a ustring: key=value
C's strings are bytes ending in a zero byte, and that is how a tiko string and a zstring are
stored. Into C: strptr( s ) is a pointer to a string's own bytes, valid until the string
next changes; @z does the same for a zstring. Out of C: a zstring ptr points at C's
memory, and *p reads the characters into a tiko string, which copies them. Copy at once; C may
reuse or free that memory.
Memory C allocates, C must free. The listing copies into memory from malloc and then reads
it into a ustring — a copy — before free. A pointer from a C library comes with rules about
who frees it, and the library's documentation says which; the C runtime's free is right only for
memory from the C runtime's malloc.
A ustring holds UTF-8, so *p from a C function that returns UTF-8 gives the right text. A C
interface that takes wchar_t * takes a wstring ptr on every platform; on Windows that is
UTF-16, which is what the W functions of the Windows API expect.
Callbacks
'' C's qsort calls back into the program to compare two elements.
type Compare as function cdecl( byval a as const any ptr, _
byval b as const any ptr ) as int32
extern "C"
declare sub qsort( byval base_ as any ptr, byval n as uinteger, _
byval size as uinteger, byval cmp as Compare )
end extern
'' A named procedure, cdecl, with exactly the parameters C passes.
function Ascending cdecl( byval a as const any ptr, _
byval b as const any ptr ) as int32
return *cptr( const int32 ptr, a ) - *cptr( const int32 ptr, b )
end function
dim nums( 0 to 5 ) as int32 = { 42, 7, 19, 3, 88, 21 }
qsort( @nums( 0 ), 6, sizeof( int32 ), @Ascending )
for i as int32 = 0 to 5
print nums( i );
next
print
'' A lambda that captures nothing is a plain function too.
qsort( @nums( 0 ), 6, sizeof( int32 ), _
function cdecl( byval a as const any ptr, _
byval b as const any ptr ) as int32
return *cptr( const int32 ptr, b ) - _
*cptr( const int32 ptr, a )
end function )
for i as int32 = 0 to 5
print nums( i );
next
print
3 7 19 21 42 88
88 42 21 19 7 3
A C function that takes a function pointer calls back into the program: qsort calls its
comparison function once per comparison. A tiko procedure can be the callback if it is
cdecl and takes exactly the parameters C passes, with the right types. @Ascending is its
address.
A lambda that captures nothing (Chapter 21) is an ordinary function, so it converts to a function pointer and can be passed straight to C, as the second call does. A lambda that captures variables cannot: it carries the captured values with it, and C has nowhere to put them.
C's way to pass state into a callback is the extra void * parameter that most callback APIs
have — user, context or arg. The program passes a pointer to its own data, and the callback
casts it back. The geom listings below use one to count visits.
Variadic functions
#include once "crt/stddef.bi"
'' Calling a variadic C function: declare the fixed part, then '...'.
extern "C"
declare function snprintf( byval buf as zstring ptr, _
byval n as size_t, _
byval fmt as const zstring ptr, _
... ) as int32
end extern
dim as zstring * 64 buf
var n = snprintf( @buf, 64, "%d items at %.2f each: %s", _
3, 4.5, "ok" )
print buf; " ("; n; " characters)"
'' Writing one: cva_list walks the arguments after the fixed ones.
'' The caller has to say how many there are -- here, the first one.
function Total cdecl( byval count as int32, ... ) as int32
dim args as cva_list
cva_start( args, count )
dim as int32 sum = 0
for i as int32 = 1 to count
sum += cva_arg( args, int32 )
next
cva_end( args )
return sum
end function
print "Total:"; Total( 4, 10, 20, 30, 40 )
'' A cva_list is a pointer on Windows and a 24-byte record on Linux.
dim probe as cva_list
print "cva_list is"; sizeof( probe ); " bytes"
On Windows:
3 items at 4.50 each: ok ( 24 characters)
Total: 100
cva_list is 8 bytes
On Linux the last line is:
cva_list is 24 bytes
A variadic C function, like printf and snprintf, is declared with its fixed parameters
followed by .... The arguments after the fixed ones are passed by C's rules: integers as at
least an int, float32 as a float64. Nothing checks them against the format string, so a %d
given a float64 prints nonsense.
A tiko procedure can be variadic too, for C to call or for tiko's own use: declare it cdecl with
..., then walk the arguments with a cva_list: cva_start( list, last ) starts after the
last fixed parameter, cva_arg( list, T ) takes the next one as type T, and cva_end( list )
finishes. Nothing says how many arguments there are or what types they have; the fixed
parameters must, as count does here and the format string does for printf.
A cva_list is 8 bytes on Windows and macOS (a pointer) and 24 on Linux (a small record), so it is not
something to store or pass by value across platforms. Pass it to C's v functions — vsnprintf
and the rest — which take a va_list.
sizeof( cva_list ) names the type directly and gives the same 8 and 24.
Calling conventions and names
On 32-bit x86, C had several calling conventions — cdecl, stdcall, pascal, fastcall and
thiscall — that differed in which side cleaned up the stack and in what order the arguments
went. x86-64 has one per platform: Microsoft's on Windows and the System V convention on
Linux. Apple's arm64, tiko's macOS target, has one too. So cdecl and stdcall mean the same thing and can be used
interchangeably; a Windows header's WINAPI (which is stdcall) and a plain cdecl describe
the same call.
The exception is pascal, which passes its arguments in reverse order on every platform.
Assigning a pascal procedure to a cdecl function pointer is warning TKO0830, because called
through the pointer, its arguments arrive backwards.
Each convention also decides the name the linker sees:
'' Three linkages for one kind of function. --dump-mangle shows the
'' symbol each becomes.
function Plain( byval x as int32 ) as int32
return x + 1
end function
extern "C"
function c_style( byval x as int32 ) as int32
return x + 2
end function
end extern
extern "C++"
namespace geo
function cpp_style( byval x as int32 ) as int32
return x + 3
end function
end namespace
end extern
print Plain( 1 ); c_style( 1 ); geo.cpp_style( 1 )
2 3 4
tiko --dump-mangle mangle.tiko prints the symbol each procedure becomes, the same on both
targets:
PLAIN
c_style
_ZN3geo9cpp_styleEi
A plain procedure's symbol is its name in capitals, because tiko's names are case-insensitive.
Inside extern "C" the name is kept exactly as written, which is what a C library expects to
find. Inside extern "C++" it is mangled the way a C++ compiler would, with the namespace
and the parameter types folded into the symbol, so a tiko declaration can link to a C++ function
that is not extern "C": geo::cpp_style( int ) is _ZN3geo9cpp_styleEi. Only functions link
this way; C++ classes, templates and exceptions do not cross. --dump-mangle is the way to find
out why a link fails with an unresolved symbol: compare its spelling with what the library
exports.
Linking
A declaration says a function exists; the linker needs the library that contains it. There are three ways to name one:
#inclib "name"in the source links the libraryname:libname.aorlibname.soon Linux,libname.aorlibname.dylibon macOS,name.libon Windows.#libpath "dir"adds a directory to search.-l nameand-p diron the command line do the same from outside.lib "name"on a declaration, or on anexternblock, links the library too, as if#inclibhad been written.
The linker searches the directories from -p and #libpath first, in the order given, then the
toolchain's own library directory, and the current directory last, so a stray library there
cannot replace the runtime that was shipped with the compiler. #libpath directories are relative
to where the compiler runs, not to the source file — which is what the next section fixes.
A library inside the project: #foreignlib
'' A hand-written binding: the library, and a declaration per function.
#foreignlib GEOM static "geom/geom"
type geom_point
as float64 x, y
end type
type geom_visit as sub cdecl( byval p as const geom_point ptr, _
byval user as any ptr )
extern "C" lib GEOM
declare function geom_version( ) as const zstring ptr
declare function geom_distance( byval a as geom_point, _
byval b as geom_point ) as float64
declare function geom_centroid( byval pts as const geom_point ptr, _
byval n as int32, _
byval out_ as geom_point ptr ) _
as int32
declare sub geom_each( byval pts as const geom_point ptr, _
byval n as int32, byval fn as geom_visit, _
byval user as any ptr )
end extern
'' A callback C will call: cdecl, and only the parameters C passes.
sub Visit cdecl( byval p as const geom_point ptr, _
byval user as any ptr )
dim as int32 ptr count = user
*count += 1
print " visit"; *count; ": ("; p->x; ","; p->y; " )"
end sub
print *geom_version( )
dim pts( 0 to 2 ) as geom_point = _
{ ( 0, 0 ), ( 4, 0 ), ( 4, 3 ) }
print "distance:"; geom_distance( pts( 0 ), pts( 2 ) )
dim c as geom_point
if geom_centroid( @pts( 0 ), 3, @c ) then
print "centroid: ("; c.x; ","; c.y; " )"
end if
dim as int32 seen = 0
geom_each( @pts( 0 ), 3, @Visit, @seen )
print "visited"; seen
geom 1.0
distance: 5
centroid: ( 2.666666666666667, 1 )
visit 1: ( 0, 0 )
visit 2: ( 4, 0 )
visit 3: ( 4, 3 )
visited 3
geom is a small C library that ships with this chapter: geom.h and geom.c, a build.sh
that builds it, and one prebuilt static library per platform:
geom/geom.h
geom/geom.c
geom/build.sh
geom/geom_windows_x86_64.lib
geom/geom_linux_x86_64.a
geom/geom_macos_arm64.a
#foreignlib GEOM static "geom/geom" binds the name GEOM to that library. Three things set
it apart from #inclib:
- The path is relative to the file the directive is in, not to wherever the compiler is run. A binding can sit in a subdirectory of a project, with its libraries beside it, and work from any working directory.
- The platform picks the file. The compiler adds the target to the name, most specific
first:
geom_windows_x86_64,geom_windows,geom_x86_64, then plaingeom, each with the platform's extensions. One directive serves every platform the project ships libraries for, with no#ifdef. staticordynamicmust be said, and limits which files can match:.aand.libfor static;.so,.dll,.dll.aand.libfor dynamic. On Windows a.libcan be either kind — a static library or the import library of a DLL — so it matches both.
The bound name then goes where a library name would: extern "C" lib GEOM. A #foreignlib
library is linked whether or not anything names it, as #inclib's is.
A spec beginning system: names an installed library instead of a file: "system:m" links
the maths library on Linux and macOS, exactly as #inclib "m" would, and its static or dynamic is not
checked.
When nothing matches, the error lists every file that was tried. On Windows:
'' A library spec that matches no file: the error lists every name
'' that was tried, most specific first.
#foreignlib SHAPES static "geom/shapes"
extern "C" lib SHAPES
declare function shapes_version( ) as const zstring ptr
end extern
print *shapes_version( )
nolib.tiko(3,1): error TKO0329: #foreignlib found no matching library
file: SHAPES static geom/shapes; tried:
geom/shapes_windows_x86_64.a
geom/shapes_windows_x86_64.lib
geom/shapes_windows.a
geom/shapes_windows.lib
geom/shapes_x86_64.a
geom/shapes_x86_64.lib
geom/shapes.a
geom/shapes.lib
(The first line is one line; it is broken here to fit the page.)
The other #foreignlib errors are about the directive itself: TKO0327 and TKO0328 when the
name, static/dynamic or the quoted spec is missing, and TKO0330 when one name is bound twice
to different libraries.
The geom listing passes a geom_point by value to geom_distance. That works because the
type has C's layout, and tiko passes it by the platform's rules for a 16-byte struct — by hidden
reference on Windows, in two floating-point registers on Linux and macOS — exactly as a C compiler would.
Reading the C header: #cinclude
'' The same library, with the declarations taken from its own header.
#cinclude once "geom/geom.h"
#foreignlib GEOM static "geom/geom"
sub Visit cdecl( byval p as const geom_point ptr, _
byval user as any ptr )
print " ("; p->x; ","; p->y; " )"
end sub
print *geom_version( ); ", major version"; GEOM_VERSION_MAJOR
dim pts( 0 to 2 ) as geom_point = _
{ ( 0, 0 ), ( 4, 0 ), ( 4, 3 ) }
print "distance:"; geom_distance( pts( 0 ), pts( 2 ) )
geom_each( @pts( 0 ), 3, @Visit, nullptr )
geom 1.0, major version 1
distance: 5
( 0, 0 )
( 4, 0 )
( 4, 3 )
Writing a binding by hand means copying every type and declaration out of the header, and
getting each one right. #cinclude reads the header itself: it hands geom.h to clang,
the C compiler, and turns what clang reports into tiko declarations, for the platform being
built. The listing has no type, no declare and no extern: geom_point, geom_visit, all
four functions and the constant GEOM_VERSION_MAJOR come from the header. #foreignlib still
does the linking; the two do separate jobs.
What comes across: structs and unions, enums, typedefs, functions (variadic ones included),
function pointer types, global variables, and #defines that are constants. What does not: macros
with parameters, which are C code rather than declarations; C++ headers; and NULL, which is 0 —
write nullptr. Every name keeps its C spelling through alias, and a name that would collide with
a tiko keyword gets an underscore — out becomes out_.
The layout is checked, not trusted. The importer writes tiko declarations and lets tiko lay out each struct, then compares every size, alignment and field offset against clang's. A struct whose layout disagrees is left out rather than imported wrong.
A quoted header is looked for in two places: first under the directory the compiler runs in, then
beside the file that says #cinclude. So this listing compiles from any directory, as
#foreignlib's path does. A header that includes others with angle brackets, as in
#include <lib/other.h>, is a different matter: clang looks for those only in the directories
given with -cinclude, and in the system's own.
A header under the working directory wins over the one beside the file. If the directory you
compile from happens to hold a geom/geom.h of its own, an older copy or another library's, that
is the header #cinclude imports, and nothing says so. #include searches the other way round,
beside the file first. Keep a vendored header's path specific to its library.
#cinclude needs libclang, which comes with the LLVM installation tiko already uses; a program
without #cinclude never loads it. The result is cached, so a large header such as windows.h
is slow the first time and quick after.
Steering the importer
The importer takes options of its own, each passed to clang:
| Option | Does |
|---|---|
-cinclude dir | adds a directory clang searches for headers (C's -I) |
-cdefine name[=value] | defines a C macro before the header is read (C's -D) |
-csysroot dir | the root of the C headers and libraries for the target |
-copt arg | passes any other argument to clang unchanged |
-cnomacros | leaves #define constants out |
-cnoverify | skips the layout check — there is no good reason to |
-cnocache, -ccachedir dir | skip the cache, or keep it somewhere else |
To see what #cinclude would produce, -cimport header -cimport-out file writes the binding
as a file instead (- for the screen), and -cimport-manifest file adds clang's layout of every
struct. For geom.h it writes an extern "C" block holding the struct —
type geom_point
x as double
y as double
end type
— a type geom_visit as sub cdecl( … ) for the callback, one declare … cdecl alias "…" per
function, and const GEOM_VERSION_MAJOR = 1. They are the declarations geom-hand.tiko wrote by
hand. A generated binding can be kept in the project and compared with the next version of the
header, which shows exactly what the library changed.
On Windows, #define UNICODE before #cinclude once "windows.h" makes the unsuffixed Windows
names such as MessageBox mean the W (UTF-16) functions; without it they mean the A ones.
Both suffixed forms are always there.
Loading a library at run time
'' Load a library while the program runs, and find a function in it.
#if defined( __FB_WIN32__ )
const LIBNAME = "ucrtbase.dll"
#elseif defined( __FB_DARWIN__ )
const LIBNAME = "libm.dylib"
#else
const LIBNAME = "libm.so.6"
#endif
type MathFn as function cdecl( byval x as float64 ) as float64
dim as any ptr lib_ = dylibload( LIBNAME )
if lib_ = nullptr then
print "could not load the library"
end 1
end if
'' dylibsymbol returns an address, or nullptr for a name it lacks.
dim as MathFn cosine = dylibsymbol( lib_, "cos" )
dim as MathFn missing = dylibsymbol( lib_, "no_such_function" )
print "found cos: "; cbool( cosine <> nullptr ); _
", found no_such_function: "; cbool( missing <> nullptr )
if cosine <> nullptr then
print "cos( 0 ) ="; cosine( 0.0 )
print "cos( pi ) ="; cosine( atn( 1 ) * 4 )
end if
dylibfree( lib_ )
found cos: true, found no_such_function: false
cos( 0 ) = 1
cos( pi ) =-1
A library linked at build time must be present for the program to start. dylibload( name )
opens a shared library while the program runs and returns a handle, or nullptr if it cannot. A
program can then decide what to do when a plugin or an optional library is missing.
dylibsymbol( handle, "name" ) finds a function or variable by name and returns its address,
or nullptr; assigning it to a function pointer of the right type makes it callable.
dylibfree closes the library, after which every address taken from it is invalid.
The names differ by platform: name.dll on Windows, libname.so (often with a version, as in
libm.so.6) on Linux, libname.dylib on macOS. The listing loads the C runtime's maths library
on each — ucrtbase.dll, libm.so.6 and libm.dylib — and calls its cos.
dylibsymbol returns an untyped address, so the function pointer's type is a promise exactly as a
declare is. Nothing checks it. A plugin system typically agrees on one entry point with a fixed
signature, looks that up, and receives everything else from it.
A tiko program can be the library: tiko -dll plugin.tiko builds a DLL or shared object, and
tiko -lib a static library, whose procedures C or another tiko program can call.
What it costs
- A call to C costs what a call to tiko costs. There is no wrapper and no conversion: the arguments go in registers and on the stack exactly as C would put them.
- Strings are copied when they come back.
*pcopies C's characters into a tiko string.strptrgoing in copies nothing. #cincludecosts compile time on the first build, seconds for a large header, and almost nothing after, from the cache.dylibloadanddylibsymbolcost a search at run time. Look a symbol up once and keep the pointer.
Summary
declare … cdecl alias "name", or anextern "C"…end externblock, declares a C function.lib "name"links its library.intisint32,long longisint64,doubleisfloat64. C'slongisclong: 4 bytes on Windows, 8 on Linux and macOS.wchar_tis 2 bytes on Windows, 4 on Linux and macOS, likewstring.- A
typehas C's struct layout;field = npacks it. Austringnever belongs in a struct C reads. strptrpasses a string's bytes to C;*pcopies C's back. Memory C allocates, C frees.- A callback is a
cdeclprocedure, or a lambda that captures nothing. ...declares a variadic C function;cva_list,cva_start,cva_arg,cva_endwrite one.- x86-64 has one calling convention per platform;
pascalalone reverses its arguments.extern "C++"mangles names as C++ does;--dump-mangleshows them. #inclib,#libpath,-l,-plink installed libraries.#foreignlib name static|dynamic "path"links one inside the project, relative to the declaring file, picked per platform.#cinclude "header.h"turns a C header into declarations through clang, layout-checked; the header is found under the working directory, then beside the file, then in-cincludedirectories.dylibload,dylibsymbolanddylibfreeload a library while the program runs.
Exercises
- Bind C's
bsearchand use it to look values up in a sorted array, with acdeclcomparison function. {solution="ex29-01"} - Bind
strtol, using its end pointer to read every number at the start of a string and report where the numbers stopped. Use the right type for C'slong. {solution="ex29-02"} - Add a function
geom_areatogeom.cthat returns the area of a polygon, rebuild the library withbuild.sh, and call it through#cinclude. What did you have to change on the tiko side? - Rewrite
callbacks.tikoso the comparison sorts by distance from a point chosen at run time. Why can the comparison not be a capturing lambda, and how does theuserpointer ofgeom_eachsolve the same problem? - Build a tiko procedure into a DLL or shared object with
-dll, and load it from another program withdylibload.