Repository navigation
modern Foreignlib tutorial
Home › Foreign Library Linking › #foreignlib Tutorial
📘 Reference: #foreignlib · Before this: #cinclude Tutorial
This tutorial shows you how to link a C library into a tiko program with #foreignlib. You will build a tiny C library, link it as a shared library and then statically, keep it inside your project so the project builds from anywhere, and learn what to do when a link goes wrong. No earlier experience with linking is needed.
Building a program that uses a C library takes two steps.
-
Compiling. tiko reads your source. It needs to know what the library's functions look like: their names, parameters and results.
#cincludegets that from the library's header. -
Linking. The linker joins your compiled code with the library's code into one program. It needs to know which library file to use. That is the job of
#foreignlib.
A library can be linked in two ways:
| Static | Dynamic (shared) | |
|---|---|---|
| the library's code is | copied into your program | kept in a separate file, loaded when the program starts |
| the file the linker reads | Windows: .lib · Linux and macOS: .a
|
Windows: an import library .lib, or the .dll · Linux: .so · macOS: .dylib
|
| the file the program needs when it runs | nothing extra | Windows: the .dll · Linux: the .so · macOS: the .dylib
|
| your program is | one file | the program plus the library file |
On Windows, the linker does not read a DLL directly. It reads a small import library that lists what the DLL contains. Libraries you download usually come with one, and tiko can make one when they do not (section 11).
tiko has two simple directives for linking: #libpath names a folder to search, and #inclib names a library to look for there.
'' Linking with #libpath and #inclib: a library name and a search directory.
#cinclude once "vendor/greet/greet.h"
#libpath "vendor/greet"
#inclib "greet"This works when you build from the project folder. But build the same file from any other folder and the link fails:
lld-link: error: could not open 'greet.lib': no such file or directory
#libpath "vendor/greet" means a folder called vendor/greet under the folder you are building in, not under the source file. The library is right where the source says, but the linker looks in the wrong place.
There are two more problems:
-
It does not say static or dynamic.
#inclib "greet"names a library, and the linker takes the first file it finds with that name. Nothing tells you which kind you got. -
It does not handle platforms. A Windows
.liband a Linux.soare different files, and#inclibhas no way of picking the right one for the platform you are building for.
#foreignlib solves all three. You can use #inclib and #libpath next to #foreignlib in the same program.
#foreignlib GREET dynamic "vendor/greet/greet"| Part | Meaning |
|---|---|
GREET |
a name for this library. You choose it. |
dynamic or static
|
required. Which kind of library to link. tiko never guesses. |
"vendor/greet/greet" |
where the library file is, relative to this source file, without its ending |
Three rules carry the whole directive:
- The path is relative to the source file, so the project builds from any folder.
- You state the kind. If no file of that kind is found, you get an error that lists every file tiko tried. tiko never falls back to the other kind.
-
You never write the platform. For
"vendor/greet/greet", tiko looks forgreet_windows_x86_64.libon Windows ,greet_linux_x86_64.soon Linux andgreet_macos_arm64.dylibon macOS, among others (section 9). One line works on all three.
Create this layout:
greet-app/
hello.tiko
vendor/greet/greet.h
vendor/greet/greet.c
vendor/greet/greet.h:
/* greet.h -- a tiny C library, for trying out #foreignlib */
#ifndef GREET_H
#define GREET_H
/* On Windows, a DLL must say which functions it exports. */
#if defined(_WIN32) && defined(GREET_DLL)
# define GREET_API __declspec(dllexport)
#else
# define GREET_API
#endif
GREET_API const char *greet_how(void);
GREET_API int greet_add(int a, int b);
#endifvendor/greet/greet.c:
/* greet.c -- the code behind greet.h */
#include "greet.h"
const char *greet_how(void)
{
#ifdef GREET_DLL
return "from a shared library";
#else
return "from a static library";
#endif
}
int greet_add(int a, int b)
{
return a + b;
}greet_how() reports which way the library was built, so you can see which kind your program linked.
Now build the library both ways, from the greet-app folder. clang comes with LLVM, which tiko already needs.
On Windows:
clang -shared -fuse-ld=lld -O2 -DGREET_DLL vendor/greet/greet.c -o greet.dll -Wl,/implib:vendor/greet/greet_windows_x86_64.lib
clang -c -O2 vendor/greet/greet.c -o greet.obj
llvm-lib /out:vendor/greet/greet-static_windows_x86_64.lib greet.objThe first command makes greet.dll in the project folder, next to where your program will be, and its import library in vendor/greet. The other two make the static library.
On Linux:
clang -shared -fPIC -O2 -DGREET_DLL vendor/greet/greet.c -Wl,-soname,libgreet.so -o vendor/greet/greet_linux_x86_64.so
cp vendor/greet/greet_linux_x86_64.so libgreet.so
clang -c -fPIC -O2 vendor/greet/greet.c -o greet.o
ar rcs vendor/greet/greet-static_linux_x86_64.a greet.o-Wl,-soname,libgreet.so records the name the program will ask for when it starts, and the cp puts a file with that name next to the program (section 12).
On macOS:
clang -dynamiclib -O2 -DGREET_DLL vendor/greet/greet.c -install_name @rpath/libgreet.dylib -o vendor/greet/greet_macos_arm64.dylib
cp vendor/greet/greet_macos_arm64.dylib libgreet.dylib
clang -c -O2 vendor/greet/greet.c -o greet.o
ar rcs vendor/greet/greet-static_macos_arm64.a greet.o-install_name @rpath/libgreet.dylib plays the part of the soname: it is the name the program asks for, found through the program's run path (section 12).
You now have:
greet-app/
greet.dll Windows: loaded when the program runs
libgreet.so Linux: loaded when the program runs
vendor/greet/greet.h
vendor/greet/greet_windows_x86_64.lib Windows: import library
vendor/greet/greet-static_windows_x86_64.lib Windows: static library
vendor/greet/greet_linux_x86_64.so Linux: shared library
vendor/greet/greet-static_linux_x86_64.a Linux: static library
libgreet.dylib macOS: loaded when the program runs
vendor/greet/greet_macos_arm64.dylib macOS: shared library
vendor/greet/greet-static_macos_arm64.a macOS: static library
The static library is called greet-static because on Windows a static library and an import library are both .lib files, so they cannot share a name.
hello.tiko:
'' hello.tiko -- link the greet library as a shared library.
#cinclude once "vendor/greet/greet.h"
#foreignlib GREET dynamic "vendor/greet/greet"
print "2 + 3 ="; greet_add( 2, 3 )
print "greetings "; *greet_how( )Build and run it. On Windows:
tiko hello.tiko
./helloOn Linux, tell the system to look for libgreet.so in the current folder (section 12 shows a better way):
tiko hello.tiko
LD_LIBRARY_PATH=. ./hello2 + 3 = 5
greetings from a shared library
There is no -p, no -l and no #ifdef anywhere. The program names the library once, and tiko picks the right file for the platform.
Go to the folder above greet-app and build again:
cd ..
tiko greet-app/hello.tikoIt links, because the path in #foreignlib is relative to hello.tiko, not to where you are. The program is written next to its source, greet-app/hello, as always. You can move or copy the whole greet-app folder anywhere, and it still builds.
hello-static.tiko is the same program with one changed line:
'' hello-static.tiko -- link the greet library statically.
#cinclude once "vendor/greet/greet.h"
#foreignlib GREET static "vendor/greet/greet-static"
print "2 + 3 ="; greet_add( 2, 3 )
print "greetings "; *greet_how( )tiko hello-static.tiko
./hello-static2 + 3 = 5
greetings from a static library
The library's code is now inside hello-static itself. Delete greet.dll or libgreet.so and it still runs.
A program can offer both, and let whoever builds it pick:
'' hello-choose.tiko -- the build picks: -d GREET_STATIC for static.
#cinclude once "vendor/greet/greet.h"
#ifdef GREET_STATIC
#foreignlib GREET static "vendor/greet/greet-static"
#else
#foreignlib GREET dynamic "vendor/greet/greet"
#endif
print "greetings "; *greet_how( )tiko hello-choose.tiko
./hello-choose
tiko hello-choose.tiko -d GREET_STATIC
./hello-choosegreetings from a shared library
greetings from a static library
On Linux, run the shared build with LD_LIBRARY_PATH=. in front, as in section 5.
Make a typing mistake in the path, "vendor/greet/gret", and build:
hello.tiko(3,1): error TKO0329: #foreignlib found no matching library file: GREET dynamic vendor/greet/gret; tried: vendor/greet/gret_windows_x86_64.dll.a
vendor/greet/gret_windows_x86_64.lib
vendor/greet/gret_windows_x86_64.dll
vendor/greet/gret_windows.dll.a
vendor/greet/gret_windows.lib
vendor/greet/gret_windows.dll
vendor/greet/gret_x86_64.dll.a
vendor/greet/gret_x86_64.lib
vendor/greet/gret_x86_64.dll
vendor/greet/gret.dll.a
vendor/greet/gret.lib
vendor/greet/gret.dll
|
3 | #foreignlib GREET dynamic "vendor/greet/gret"
| ^
The error names the directive, the line it is on, and every file tiko tried, in order. Compare the file names with the files you have, and the mistake is usually plain. That list is also the easiest way to find out what to call a library file.
Asking for the wrong kind is the same error. On Linux, #foreignlib GREET static "vendor/greet/greet" finds only the .so there, which is not a static library:
error TKO0329: #foreignlib found no matching library file: GREET static vendor/greet/greet; tried: vendor/greet/greet_linux_x86_64.a
vendor/greet/greet_linux.a
vendor/greet/greet_x86_64.a
vendor/greet/greet.a
You do not quietly get a shared library instead.
| Error | Cause |
|---|---|
TKO0327: expected a name, STATIC or DYNAMIC, then a quoted spec after #foreignlib |
the directive is incomplete |
TKO0328: expected STATIC or DYNAMIC after the #foreignlib name, X |
the kind is missing |
TKO0329: #foreignlib found no matching library file |
no file has any of the names listed (section 7) |
TKO0330: #foreignlib name already bound to a different library set, X |
the same name is used for two different libraries |
Repeating a #foreignlib line exactly is allowed. That happens when two files include the same binding header.
For #foreignlib NAME kind "dir/base", tiko tries these names, most specific first:
dir/base_<os>_<arch><ext>
dir/base_<os><ext>
dir/base_<arch><ext>
dir/base<ext>
| Windows | Linux | macOS | |
|---|---|---|---|
<os> |
windows |
linux |
macos |
<arch> |
x86_64 |
x86_64 |
arm64 |
<ext> for static
|
.a, then .lib
|
.a |
.a |
<ext> for dynamic
|
.dll.a, then .lib, then .dll
|
.so |
.dylib |
Every ending for the kind is tried at each step, and the first file that exists is used.
-
Name files with the platform tag (
greet_windows_x86_64.lib,greet_linux_x86_64.so) whenever files for more than one platform share a folder. Then each platform finds only its own files. -
No
libis added or removed. The base name is used exactly as written. A file calledlibgreet.ais"vendor/greet/libgreet". -
A path can also be absolute, such as
"C:/libs/greet/greet".
⚠️ On Windows,.libcounts as both kinds. A static library and an import library are both.libfiles, and nothing in the name tells them apart. So#foreignlib GREET static "vendor/greet/greet"would acceptgreet_windows_x86_64.lib, the import library, and the program would quietly needgreet.dllafter all. Give the two different base names, as section 4 does withgreetandgreet-static.
Libraries that are part of the operating system, or installed for every program on the machine, are named with system::
#foreignlib WINMM dynamic "system:winmm""system:winmm" hands the name to the linker, which searches its usual folders, exactly as #inclib "winmm" does. On Linux and macOS, "system:m" links the maths library, and a library installed by your package manager, such as "system:sqlite3", is found the same way.
⚠️ The kind is not checked forsystem:. tiko does not look for the file itself, so it cannot check that it is the kind you asked for. The linker takes whatever it finds.
One #foreignlib can name several libraries. They are linked in the order given. This is how a static library brings along the system libraries it needs:
#foreignlib GREET static "vendor/greet/greet-static", "system:winmm"A static library is only its own code. If it calls other libraries, your program has to link them too. A library's documentation usually lists them. If it does not, link without them, and each undefined symbol error names a function you still need. The Using C Libraries tutorial shows how to track them down.
Some Windows libraries come as a .dll alone. That is fine. When a dynamic search finds only a .dll, tiko reads the list of functions inside it and makes the import library itself while linking.
To try it, keep only the DLL in vendor/greet, renamed with the platform tag:
greet-app/
greet.dll beside the program, unchanged
vendor/greet/greet_windows_x86_64.dll a copy, renamed
hello.tiko builds and runs exactly as before. The import library records the DLL by the name the DLL gives itself, greet.dll, not by the renamed file, so the program loads the copy beside it.
An import library or .dll.a with a matching name is found before the .dll, and then it is used as it is. A 32-bit DLL is refused with TKO0172, because tiko builds 64-bit programs only.
#foreignlib takes care of linking. Making sure the program can find the shared library when it starts is up to you.
Windows looks in the program's own folder first, then in the system folders and on PATH. Put the DLL beside the .exe. If it is missing, the program stops at once with exit code -1073741515 (DLL not found). Keep the DLL's own name: it is recorded in the import library.
Linux does not look beside the program. It looks in LD_LIBRARY_PATH, then in the system's library folders, for the name recorded with -soname (libgreet.so here). Without it, you see:
./hello: error while loading shared libraries: libgreet.so: cannot open shared object file: No such file or directory
LD_LIBRARY_PATH=. ./hello is fine for a quick test. To make the program look beside itself every time, record an rpath when you link. Add this at the top of hello.tiko:
#ifdef __FB_LINUX__
#cmdline "-Wl -rpath,$ORIGIN"
#endif$ORIGIN means the folder this program is in, and -Wl passes the option to the linker. Now ./hello runs with no LD_LIBRARY_PATH, from any folder, as long as libgreet.so is next to it. The #ifdef matters: Windows' linker has no -rpath.
macOS loads a library by its install name, here @rpath/libgreet.dylib. A program that links a .dylib file from #foreignlib gets the run path @loader_path, the folder this program is in, with nothing written: ./hello runs as long as libgreet.dylib is next to it. Without it, the program stops before it starts:
dyld[<pid>]: Library not loaded: @rpath/libgreet.dylib
If you write a declare by hand, you can say which library it belongs to with lib and the name from #foreignlib:
'' A system library, and a declaration written by hand that says
'' which library it belongs to.
#foreignlib WINMM dynamic "system:winmm"
declare function timeGetTime lib WINMM alias "timeGetTime" ( ) as uint32
print "Windows has been running for"; timeGetTime( ) \ 60000; " minutes"This is optional. A #foreignlib links its libraries whether or not any declaration names them, so the declarations that #cinclude makes need no lib at all. Treat lib WINMM as a note to the reader: tiko does not check the name, so a misspelt one is not an error.
Put the #cinclude and #foreignlib lines for a library in one .ti file, next to the library. Both paths in it are relative to that .ti file, so a program includes it with one line, and the folder works wherever it is:
'' greet.ti -- the greet library: its header, and the file to link.
#pragma once
#cinclude once "greet/greet.h"
#foreignlib GREET dynamic "greet/greet"The #cinclude Tutorial shows this in full, and also how to make a binding a module with public #cinclude.
Compiling in pieces. When you compile one file at a time with -c and link the objects later, each object file remembers the libraries its #foreignlib lines found, with their full paths. The final link includes them, even when it runs in another folder.
- It does not download anything. It links a library you already have.
-
It does not copy the DLL or
.sonext to your program. That is still your job (section 12). -
It does not load libraries while the program runs. For that, use
DyLibLoadandDyLibSymbol.
- #foreignlib, the reference page
- #cinclude Tutorial, declaring a library's functions from its header
- Using C Libraries, with step-by-step tutorials for SQLite, raylib, GLFW and SDL3
-
#inclib·#libpath·#cmdline
Category: Foreign Library Linking
Kind: tutorial
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