Linking Libraries
An extern declaration says what a foreign function looks like; #Link says where it lives. The Rux linker records the shared library (.dll, .so, .dylib) and the symbol in the executable's import tables, and the system loader finds the library when the program starts.
#Link("Kernel32.dll")
extern func GetCurrentProcessId() -> uint32; // the library alone
#Link("Kernel32.dll", "GetTickCount64")
extern func Milliseconds() -> uint64; // and the exported symbol
#Link("Kernel32.dll")
extern { // every member of a block
func GetCurrentThreadId() -> uint32;
}
| Form | Library | Symbol | On |
|---|---|---|---|
#Link("lib") | lib | the declaration's own name | extern func, extern { } |
#Link("lib", "symbol") | lib | symbol | extern func only |
Either argument may be a string literal or the name of a string constant declared in the same package. An extern func without #Link is an error on every target (extern function 'GetTickCount64' must specify a source DLL via #Link("dll.dll")).
One library per platform
A library's name differs from system to system — the C runtime alone has four. Declare its name once, as a constant chosen by when, and link against the constant:
import Core::{ #target, #Error };
when #target.os {
.FreeBSD => const CRuntime = "libc.so.7";
.Linux => const CRuntime = "libc.so.6";
.macOS => const CRuntime = "libSystem.B.dylib";
.Windows => const CRuntime = "ucrtbase.dll";
else => #Error("unknown C runtime")
}
#Link(CRuntime)
extern {
func abs(n: int32) -> int32;
func strlen(text: *char8) -> uint;
}
| System | C runtime |
|---|---|
| Windows | ucrtbase.dll |
| Linux | libc.so.6 |
| macOS | libSystem.B.dylib |
| FreeBSD | libc.so.7 |
On Windows the formatted-output family (printf, sprintf, …) is not exported by ucrtbase.dll, which implements it as header inlines; bind those from msvcrt.dll.
The constant must be declared in the package that links against it; a constant imported from another package — even the C package's own CRuntime — is refused with '#Link' library name 'CRuntime' is not a compile-time constant.
Functions that exist on one system only
When a function exists on one system only, declare it only there, and give the other targets either another implementation or an explanation. A missing branch is better than a program that builds and then cannot find its library:
import Core::{ #target };
when #target.os {
.Windows => {
#Link("Kernel32.dll")
extern func GetTickCount64() -> uint64;
func Uptime() -> uint64 { return GetTickCount64() / 1000; }
},
else => {
#Error("Uptime is implemented for Windows only")
func Uptime() -> uint64 { return 0; }
}
}
The declarations in an untaken branch are never resolved, so a library that does not exist on the target never reaches the linker. The #Error attribute makes a call to Uptime on any other system a compile error that says why.
Renaming a symbol
The second argument binds a Rux name to an export with a different name — a terse C name to a readable one, or one entry point to two Rux signatures:
#Link(CRuntime, "abs")
extern func AbsoluteValue(n: int32) -> int32;
AbsoluteValue(-7) calls the library's abs. One symbol cannot stand for every member of a block, so this form applies to a single extern func.
What is checked
The compiler cannot open the library. Building for Windows, the linker does check that the named DLL exports each symbol, and a misspelt name stops the build:
error: cannot link PE/COFF executable 'Extern': import function 'GetCurrentProcessID' was not found in DLL 'Kernel32.dll'
For Linux, macOS and FreeBSD the name is resolved by the dynamic loader when the program is loaded. Neither check covers the signature: parameter and result types are taken on trust everywhere.
rux build --target <name> links for any of the eight targets from any host, so every branch of a per-platform when can be linked without the other systems at hand.
See also
- Foreign function interface — extern declarations and C types
#Link— the attribute reference- Conditional compilation — per-platform declarations
- Learn: Extern, ABI