Repository navigation
Using C Libraries Tutorial
Home › Using C Libraries
📘 Step by step, one library each: SQLite · raylib · GLFW · SDL3
📖 In the book: Chapter 30, Using C Libraries covers the same ground in one chapter. Every program on these pages is one of its listings, compiled and run on Windows and Linux with tiko.
Most of the code a program might want already exists as a C library: databases, graphics, sound, compression, networking. tiko uses a C library directly. #cinclude reads the library's own header, so you write no declarations, and #foreignlib links the library's file. This page covers what is the same for every library. The four tutorials above then take one library each from its download page to a running program.
| Windows | Linux | macOS | |
|---|---|---|---|
| tiko and LLVM | as for any tiko program | as for any tiko program | as for any tiko program (Homebrew's llvm) |
C headers for #cinclude
|
Visual Studio Build Tools, with Desktop development with C++ and the Windows SDK | your distribution's C compiler (build-essential on Ubuntu) |
Xcode's Command Line Tools, which every macOS link needs anyway |
| building libraries from source | CMake | CMake, plus the library's -dev packages |
CMake, or the library from Homebrew |
tiko itself does not need Visual Studio, but #cinclude does. On Windows, clang reads the C runtime's headers (stdio.h and the rest) from the Visual Studio installation, and almost every library header includes one. Without the Build Tools you see this:
note: windows-x86_64 imports against the installed MSVC and Windows SDK
headers; install Visual Studio Build Tools with the Windows SDK, or name
the directory with -cinclude
| File | Windows | Linux | macOS | Used by |
|---|---|---|---|---|
| header | name.h |
name.h |
name.h |
#cinclude, when you compile |
| static library | name.lib |
libname.a |
libname.a |
the linker: the code is copied into your program |
| import library |
name.lib or namedll.lib
|
(none) | (none) | the linker: says which DLL holds each function |
| shared library | name.dll |
libname.so.N |
libname.N.dylib |
your program, every time it starts |
On Windows, a static library and an import library are both .lib. The package's documentation, or the file size, tells you which is which: raylib's static raylib.lib is 5 MB, and its import library raylibdll.lib is 400 KB.
A DLL with no import library is fine. When #foreignlib ... dynamic finds only a .dll, tiko reads the DLL's table of exports and makes the import library itself while linking, naming the DLL as the DLL names itself. The SQLite tutorial uses that.
Choose the right download. tiko builds 64-bit programs only, so skip anything marked win32, x86, i386 or i686. On Windows, take the package built for Visual C++ (msvc, VC, vc2022), because tiko's Windows target follows Visual C++'s conventions and uses its C runtime. The mingw packages are for a different toolchain.
| Static | Dynamic | |
|---|---|---|
| your program is | one file | the program plus the DLL or .so
|
| size (raylib example, Windows) | 608 kB | 27 kB + 1,905 kB DLL |
| update the library by | relinking | replacing the DLL |
| system libraries | you link the ones the library calls | the DLL already links them |
| can go wrong when | linking | the program starts |
All four libraries in these tutorials allow both. Check the licence of any other library first: some, such as the LGPL, ask more of a static link than of a dynamic one.
-
Windows looks in the program's own directory first, then the system directories, then the current directory and
PATH. Put the DLL beside the.exe. If it is missing, the program exits immediately with code-1073741515(0xC0000135, DLL not found) and prints nothing. -
Linux looks in
LD_LIBRARY_PATH, then the system's library directories. It does not look beside the program. It asks for the library's soname, such aslibraylib.so.600, which is recorded inside the.so. If the library is missing:error while loading shared libraries: libraylib.so.600: cannot open shared object file: No such file or directoryFor a library kept inside your project, run with
LD_LIBRARY_PATH=. ./programfor a quick test. To make it last, record an rpath in the program: a directory the loader searches before the system's.$ORIGINmeans "the directory this program is in", and tiko's-Wloption hands it to the linker:#cmdline "-Wl -rpath,$ORIGIN"
The program and its
.sofiles can then be copied anywhere together.-Wlpasses its value to the linker, split at commas. In a shell, single-quote it so the shell leaves$ORIGINalone:tiko game.tiko -Wl '-rpath,$ORIGIN'. In#cmdlineor intiko.toml'soptions, no quoting is needed. Put#cmdlinelines that only make sense on Linux inside#ifdef __FB_LINUX__: on Windows,-Wltalks tolld-link, which has no-rpath. -
macOS loads a library by its install name, recorded in the program when it links. Homebrew's libraries name their own absolute path (
/opt/homebrew/opt/sdl3/lib/libSDL3.0.dylib), so they load from there. A library you build names@rpath/libname.dylib, and a program that links a.dylibfrom#foreignlibhas the run path@loader_path, its own directory: put the library beside the program. If it is missing, dyld stops the program before it starts (exit 134):dyld[<pid>]: Library not loaded: @rpath/libname.dylibHeaders and libraries installed by Homebrew are under
/opt/homebrew/includeand/opt/homebrew/lib. A macOS link searches/opt/homebrew/libafter your own paths, sosystem:libraries from Homebrew need no-p.
⚠️ A missing library is not always an error on Linux. If the system has its own copy under the same soname, the program silently loads that one. A program linked with SQLite 3.54.0 printsSQLite 3.54.0withLD_LIBRARY_PATH=.andSQLite 3.45.1without it, because Ubuntu 24.04 ships 3.45.1. An rpath of$ORIGINputs your copy first. Ask the library for its version when it matters.
A static library is only that library's code. Whatever it calls in the operating system, your program must link. Each one goes after the library in #foreignlib, as "system:name":
#foreignlib RAYLIB static "vendor/raylib/raylib", _
"system:winmm", "system:gdi32", "system:user32", "system:shell32"To find the list, check the library's documentation first. If it doesn't say, link without any and read the errors:
lld-link: error: undefined symbol: __declspec(dllimport) SetupDiGetClassDevsA
lld-link: error: undefined symbol: __declspec(dllimport) CoInitializeEx
On Windows, search Microsoft's documentation for each function. The Requirements table at the bottom names its library: SetupDiGetClassDevsA is in setupapi.lib, so add "system:setupapi". Repeat until the link succeeds.
Put the library in a vendor/ folder of the project. Anyone can then build the project without installing anything, and the version never changes behind your back.
#foreignlib was designed for this. Its path is relative to the file that contains it, not to where you run the compiler. It also picks the file by target: for "vendor/raylib/raylib" it tries raylib_windows_x86_64, raylib_windows, raylib_x86_64, then raylib, each with the target's extensions. So rename the files the linker reads, and both platforms can share one folder:
vendor/raylib/raylib.h
vendor/raylib/raylib_windows_x86_64.lib static, Windows
vendor/raylib/raylib_linux_x86_64.a static, Linux
vendor/raylib/raylibdll_windows_x86_64.lib import library, Windows
vendor/raylib/raylibdll_linux_x86_64.so shared, Linux (renamed)
raylib.dll beside the program
libraylib.so.600 beside the program
Never rename the files the program loads when it runs. A DLL keeps the name its import library records. A .so keeps its soname.
Use the target tags whenever two platforms share a folder. On Windows a static search tries .a before .lib, so an untagged Linux raylib.a would be picked up on Windows.
#cinclude "vendor/raylib/raylib.h" finds the header under the directory you compile from, and then beside the file that names it. When the header itself includes others with angle brackets (#include <SDL3/SDL_stdinc.h>), name its include folder with -cinclude.
⚠️ Search order: a header under the working directory wins over the one beside the file. That is the reverse of#include. Keep vendored header paths specific (vendor/raylib/raylib.h, notraylib.h), so that no stray copy can shadow them.
Linux. The package manager puts headers in /usr/include and libraries in /usr/lib/x86_64-linux-gnu. A library built from source and installed with sudo cmake --install build goes to /usr/local/include and /usr/local/lib. clang and the linker search all of these by default, so the program names no paths:
#cinclude once "sqlite3.h"
#foreignlib SQLITE dynamic "system:sqlite3""system:sqlite3" passes -lsqlite3 to the linker. The static/dynamic word is not checked for a system: spec, and the linker prefers libsqlite3.so over libsqlite3.a when both exist. After installing into /usr/local/lib yourself, run sudo ldconfig once.
Windows has no system-wide place for C libraries, so make one, such as C:\tiko-libs. Then write a binding header (next section), and point each project at the folder with -i.
Move the #cinclude and #foreignlib lines into a .ti file next to the library. Both paths in it are relative to the .ti itself, so the folder works wherever it is:
'' raylib.ti -- raylib 6.0 for tiko: its header, and the library to
'' link, in one file a program includes. Both paths are relative to
'' this file, so the folder it sits in can live anywhere: inside one
'' project, or in a single place that every program shares.
#pragma once
#cinclude once "raylib/raylib.h"
#ifdef __FB_WIN32__
#foreignlib RAYLIB static "raylib/raylib", _
"system:winmm", "system:gdi32", "system:user32", _
"system:shell32"
#else
#foreignlib RAYLIB static "raylib/raylib", _
"system:GL", "system:m", "system:pthread", "system:dl", _
"system:rt", "system:X11"
#endifA program then needs one line, #include once "raylib.ti", and the project says where the bindings are:
# A program that uses raylib without saying where raylib is: the
# -i below names the folder of bindings, relative to this file.
[package]
name = "game"
[target.game]
main = "main.tiko"
options = ["-i", "../vendor"]To share the bindings between every program on the machine, put raylib.ti and its raylib/ folder in C:\tiko-libs, and use options = ["-i", "C:/tiko-libs"] in each tiko.toml (or -i C:\tiko-libs on the command line). The raylib tutorial shows the whole project.
A binding can also be a module. Inside a module, write public #cinclude: the header's names then travel with the module, and every program that #imports it sees them as if it had read the header itself. A plain #cinclude in a module stays private.
'' raylib as a module. 'public #cinclude' makes raylib.h's names the
'' names of every program that imports the module, and the module's
'' own #foreignlib links the library into each of them. Both paths are
'' relative to this file.
#module "raylib"
public #cinclude once "../../vendor/raylib/raylib.h"
#ifdef __FB_WIN32__
#foreignlib RAYLIB static "../../vendor/raylib/raylib", _
"system:winmm", "system:gdi32", "system:user32", _
"system:shell32"
#else
#foreignlib RAYLIB static "../../vendor/raylib/raylib", _
"system:GL", "system:m", "system:pthread", "system:dl", _
"system:rt", "system:X11"
#endifA program then needs one line, #import "raylib". Install the module for every program by putting its folder in a directory named in TIKO_PATH (;-separated on Windows, : on Linux and macOS). A module may not use #cmdline, so a library whose headers need -cinclude (SDL3's do) is still best kept as a binding header. The raylib tutorial has the whole project.
| What you see | Cause | Fix |
|---|---|---|
TKO0336: unable to import C header + clang file not found
|
the header, or one it includes, is not found | check the path; add -cinclude <dir> for <...> includes |
| the same + a note about the Windows SDK | no Visual Studio Build Tools | install them with the Windows SDK |
TKO0329: #foreignlib found no matching library file + a list |
no file has any of the listed names | rename the file to one of them, with its target tag |
TKO0172: cannot make an import library for '...': it is not a 64-bit x86-64 DLL |
a .dll with no import library was linked, and it is the 32-bit build |
download the 64-bit package |
undefined symbol: ... when linking statically |
a system library the static library needs is missing | add "system:name" (section 3) |
program exits at once, code -1073741515
|
Windows cannot find the DLL, or the import library has no DLL name | put the DLL beside the .exe; check the import library |
error while loading shared libraries, exit 127 |
Linux cannot find the .so
|
-Wl -rpath,$ORIGIN, LD_LIBRARY_PATH=., or install it |
| the program reports an older library version | Linux loaded the system's copy | -Wl -rpath,$ORIGIN |
warning TKO0833: passing different pointer types at a callback |
your callback's parameters differ from the C type | match the header exactly, since a wrong callback reads garbage |
| a library floods the console | it logs to standard output | lower its log level (raylib: SetTraceLogLevel( LOG_WARNING )) |
- #cinclude and the #cinclude Tutorial
- Foreign Library Linking and the #foreignlib Tutorial
-
tiko build, for
tiko.toml - Chapter 29, Interfacing with C: types, strings and callbacks across the boundary
tiko-lang · Home · Feature Index · Keyword Index · License
FreeBASIC manual pages reproduced under the GNU FDL 1.3.
- Generics
- Interfaces
- For Each
- Iterators
- Tuples
- Array Results
- Defer
- Lambdas
- Block Strings
- Partial Types
- String Slices
- Embed
- Typed Variadics
- C Header Import
- Foreign Library Linking
- UString
- Fixed-Width Types
- Nullptr
- String Library
- Format
- Array
- Map
- Set
- LinkedList
- OrderedMap
- OrderedSet
- Sorting
- Optional
- Result
- Smart Pointers
- Date and Time (Chrono)
- Filesystem
- File Streams
- Config (INI and TOML)
- Codec
- Hash
- UUID
- OS
- Process
- Net
- HTTP and TLS
- Math Library
- Finance
- Logging
- Terminal TUI Library
- PsControls
- tkControls
- sys/win
- Goroutines and Channels
- LLVM Backend
- tiko build
- TIKO_COMPILER
- Variable Declarations
- Standard Data Types
- User Defined Types
- Procedures
- Operators
- Control Flow Statements
- Converting Data Types
- Array Functions
- String Functions
- Mathematical Functions
- Bit Manipulation
- Date and Time Functions
- File I/O Functions
- Console Functions
- User Input (Runtime Library)
- User Input Functions (Graphics Library)
- Screen Functions
- 2D Drawing Functions
- Graphics Library Reference
- Graphics Keyword List
- Memory Functions
- Threading Support Functions
- Error Handling Functions
- Operating System Functions
- Preprocessor
- Intrinsic Defines
- Miscellaneous Keywords
- Modularizing