os.dl
Loading shared libraries at run time, looking up their functions and calling a function pointer with up to eight integer or pointer arguments.
import os.dl
Most programs link their libraries when they are built. This package is for the other case: the library is chosen while the program runs. Open loads a shared library (dlopen), Sym finds a function or a variable in it by name (dlsym), and Call calls the function pointer you get, with up to eight integer or pointer arguments, and returns the integer or pointer it returns. OpenSelf opens the running program itself, which gives you the C library and everything else already loaded. It is the basis for plug-ins, for probing optional libraries and for tools that load a function they have just written into executable memory.
Arguments and the result are size, an integer as wide as a pointer (64 bits on a 64 bit system). Pass a pointer by converting it, (p as void*) as size, and turn a returned pointer back with r as void*. A small integer is passed as it is. The package works on Linux, macOS and the BSDs; on Windows Supported is false and the functions return empty results.
What people get wrong:
- Calling something that is not a function, or a function with the wrong number of arguments.
Callcannot check that; a wrong pointer or a wrong count crashes the program or returns garbage. Give exactly the arguments the C function takes. - Calling a function that takes a struct by value or a variable argument list such as
printfthroughCallorCallMixed. Those use a different calling convention. Floating point arguments needCallMixed, and a floating point result needsCallMixedF64. - Not checking for
null.OpenandSymreturnnullwhen the library or the symbol is missing, andCallonnullreturns 0; askErrorwhy. - Using a symbol after
Close. Once the last user of a library closes it, its code is gone and a stale pointer crashes. - More than 8 arguments:
Callrefuses and returns 0.
Choosing a function by name at run time
The name of the function does not have to be known when the program is written. The example opens the running program itself with OpenSelf, looks each name up with Sym and calls what it finds. A name that does not exist gives null, which the example reports instead of calling it. Pass a library path to Open to do the same with a plug-in file.
import os.dl
import str
func call_by_name(lib: void*, name: str, arg: size): str:
fn := dl.Sym(lib, name)
if fn == null:
ret name + ": not found"
end
ret name + ": " + str.FromInt(dl.Call(fn, [arg]) as i64)
end
func main:
if not dl.Supported():
println "dynamic loading is not available here"
ret
end
lib := dl.OpenSelf()
if lib == null:
println "cannot open the program:", dl.Error()
ret
end
defer _closed := dl.Close(lib)
text := "salam"
println call_by_name(lib, "strlen", (text as void*) as size)
println call_by_name(lib, "labs", (0 - 42) as size)
println call_by_name(lib, "no_such_function", 1 as size)
endOpening a library by path
Library file names differ between systems, so a portable program tries a few and uses the first that opens. Open returns null for one that does not exist and Error says why. Always pair a successful Open with Close, and do not use a symbol after its library is closed.
import os.dl
import str
func open_first(paths: str[]): void*:
each path in paths:
lib := dl.Open(path)
if lib != null:
ret lib
end
end
ret null
end
func main:
if not dl.Supported():
println "dynamic loading is not available here"
ret
end
lib := open_first(["libno-such-library.so", "libc.so.6", "libc.dylib", "/usr/lib/libSystem.B.dylib", "libc.so.7"])
if lib == null:
println "no C library found:", dl.Error()
ret
end
defer _closed := dl.Close(lib)
println "opened a C library"
text := "shared library"
length := dl.Call(dl.Sym(lib, "strlen"), [(text as void*) as size])
println "strlen says", str.FromInt(length as i64)
endFunctions
Supported
func Supported(): boolimport os.dl
func main:
if not dl.Supported():
println "not supported here"
ret
end
lib := dl.OpenSelf()
defer dl.Close(lib)
text := "hello, world"
length := dl.Call(dl.Sym(lib, "strlen"), [(text as void*) as size])
println "strlen:", length
println "labs:", dl.Call(dl.Sym(lib, "labs"), [(0 - 42) as size])
println "missing symbol:", dl.Sym(lib, "no_such_function") == null
missing := dl.Open("libno-such-library.so.9")
println "missing library:", missing == null, dl.Error().len() > 0
endOpen
func Open(path: str): void*path (a name such as libm.so.6 is searched for by the system) and returns its handle, or null.The example for this item is shown with the ones above: os-dl-Call.salam
OpenSelf
func OpenSelf(): void*Sym can find the C library and anything else already loaded. null where it is not supported.The example for this item is shown with the ones above: os-dl-Call.salam
Sym
func Sym(lib: void*, name: str): void*name in the library, or null. Use null checks, not exceptions.The example for this item is shown with the ones above: os-dl-Call.salam
Close
func Close(lib: void*): boolThe example for this item is shown with the ones above: os-dl-Call.salam
Error
func Error(): strOpen or Sym, or an empty string.The example for this item is shown with the ones above: os-dl-Call.salam
Call
func Call(fn: void*, args: size): sizefn with the integer and pointer arguments in args (0 to 8 of them) and returns its integer or pointer result. A null pointer or more than 8 arguments gives 0. The arguments are passed in exactly the registers and stack slots a C compiler would use.The example for this item is shown with the ones above: os-dl-Call.salam
CallMixed
func CallMixed(fn: void*, ints: size, dbls: f64): sizeCall for a function that takes integer or pointer arguments (at most 6 in ints) and floating point arguments (at most 8 in dbls) and returns an integer or pointer. On x86-64 (System V) and 64 bit ARM the two kinds go in separate registers, so the order in which they are mixed in the C prototype does not matter: ldexp(double, int) is ints = [exp] and dbls = [x]. Not for variable argument lists. A null pointer or too many arguments gives 0.import os.dl
func main:
if not dl.Supported():
println "not supported here"
ret
end
lib := dl.OpenSelf()
defer dl.Close(lib)
mut m := lib
if SALAM_OS_LINUX:
m = dl.Open("libm.so.6")
end
println "pow:", dl.CallMixedF64(dl.Sym(m, "pow"), [0 as size], [2.0, 10.0])
println "ldexp:", dl.CallMixedF64(dl.Sym(m, "ldexp"), [3 as size], [1.5])
println "lround:", dl.CallMixed(dl.Sym(m, "lround"), [0 as size], [2.6])
endCallMixedF64
func CallMixedF64(fn: void*, ints: size, dbls: f64): f64double, such as pow or hypot.The example for this item is shown with the ones above: os-dl-CallMixed.salam