aboutsummaryrefslogtreecommitdiff
path: root/core/dynlib
diff options
context:
space:
mode:
authorFourteenBrush <naessensarthur2@protonmail.com>2024-01-25 10:15:25 +0100
committerFourteenBrush <naessensarthur2@protonmail.com>2024-01-25 10:15:25 +0100
commit967ccfc7ccc0f76c653ff4bb625bac60e89a7904 (patch)
treef989a0f99337c7a0c93ee84249d73ec0c33e306d /core/dynlib
parent712ae1c5ac73493498aa8e5076d91a6558337117 (diff)
parent9cfd4a953e2a7d19237891993b643b2d1477f8b3 (diff)
Merge branch 'master' of https://github.com/FourteenBrush/Odin
Diffstat (limited to 'core/dynlib')
-rw-r--r--core/dynlib/doc.odin6
-rw-r--r--core/dynlib/example/example.odin45
-rw-r--r--core/dynlib/example/lib.odin14
-rw-r--r--core/dynlib/lib.odin116
-rw-r--r--core/dynlib/lib_js.odin4
-rw-r--r--core/dynlib/lib_unix.odin5
-rw-r--r--core/dynlib/lib_windows.odin7
7 files changed, 187 insertions, 10 deletions
diff --git a/core/dynlib/doc.odin b/core/dynlib/doc.odin
index 812fb02d5..849e03a71 100644
--- a/core/dynlib/doc.odin
+++ b/core/dynlib/doc.odin
@@ -1,7 +1,11 @@
+//+build ignore
/*
Package core:dynlib implements loading of shared libraries/DLLs and their symbols.
The behaviour of dynamically loaded libraries is specific to the target platform of the program.
For in depth detail on the underlying behaviour please refer to your target platform's documentation.
+
+See `example` directory for an example library exporting 3 symbols and a host program loading them automatically
+by defining a symbol table struct.
*/
-package dynlib
+package dynlib \ No newline at end of file
diff --git a/core/dynlib/example/example.odin b/core/dynlib/example/example.odin
new file mode 100644
index 000000000..f12233b0a
--- /dev/null
+++ b/core/dynlib/example/example.odin
@@ -0,0 +1,45 @@
+package example
+
+import "core:dynlib"
+import "core:fmt"
+
+Symbols :: struct {
+ // `foo_` is prefixed, so we look for the symbol `foo_add`.
+ add: proc "c" (int, int) -> int,
+ // We use the tag here to override the symbol to look for, namely `bar_sub`.
+ sub: proc "c" (int, int) -> int `dynlib:"bar_sub"`,
+
+ // Exported global (if exporting an i32, the type must be ^i32 because the symbol is a pointer to the export.)
+ // If it's not a pointer or procedure type, we'll skip the struct field.
+ hellope: ^i32,
+
+ // Handle to free library.
+ // We can have more than one of these so we can match symbols for more than one DLL with one struct.
+ _my_lib_handle: dynlib.Library,
+}
+
+main :: proc() {
+ sym: Symbols
+
+ // Load symbols from `lib.dll` into Symbols struct.
+ // Each struct field is prefixed with `foo_` before lookup in the DLL's symbol table.
+ // The library's Handle (to unload) will be stored in `sym._my_lib_handle`. This way you can load multiple DLLs in one struct.
+ count, ok := dynlib.initialize_symbols(&sym, "lib.dll", "foo_", "_my_lib_handle")
+ defer dynlib.unload_library(sym._my_lib_handle)
+ fmt.printf("(Initial DLL Load) ok: %v. %v symbols loaded from lib.dll (%p).\n", ok, count, sym._my_lib_handle)
+
+ if count > 0 {
+ fmt.println("42 + 42 =", sym.add(42, 42))
+ fmt.println("84 - 13 =", sym.sub(84, 13))
+ fmt.println("hellope =", sym.hellope^)
+ }
+
+ count, ok = dynlib.initialize_symbols(&sym, "lib.dll", "foo_", "_my_lib_handle")
+ fmt.printf("(DLL Reload) ok: %v. %v symbols loaded from lib.dll (%p).\n", ok, count, sym._my_lib_handle)
+
+ if count > 0 {
+ fmt.println("42 + 42 =", sym.add(42, 42))
+ fmt.println("84 - 13 =", sym.sub(84, 13))
+ fmt.println("hellope =", sym.hellope^)
+ }
+} \ No newline at end of file
diff --git a/core/dynlib/example/lib.odin b/core/dynlib/example/lib.odin
new file mode 100644
index 000000000..25687a653
--- /dev/null
+++ b/core/dynlib/example/lib.odin
@@ -0,0 +1,14 @@
+package library
+
+@(export)
+foo_add :: proc "c" (a, b: int) -> (res: int) {
+ return a + b
+}
+
+@(export)
+bar_sub :: proc "c" (a, b: int) -> (res: int) {
+ return a - b
+}
+
+@(export)
+foo_hellope: i32 = 42 \ No newline at end of file
diff --git a/core/dynlib/lib.odin b/core/dynlib/lib.odin
index b5cb16e3c..e9ee77d2c 100644
--- a/core/dynlib/lib.odin
+++ b/core/dynlib/lib.odin
@@ -1,5 +1,12 @@
package dynlib
+import "core:intrinsics"
+import "core:reflect"
+import "core:runtime"
+_ :: intrinsics
+_ :: reflect
+_ :: runtime
+
/*
A handle to a dynamically loaded library.
*/
@@ -12,11 +19,11 @@ library available to resolve references in subsequently loaded libraries.
The paramater `global_symbols` is only used for the platforms `linux`, `darwin`, `freebsd` and `openbsd`.
On `windows` this paramater is ignored.
-The underlying behaviour is platform specific.
-On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlopen`.
+The underlying behaviour is platform specific.
+On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlopen`.
On `windows` refer to `LoadLibraryW`.
-**Implicit Allocators**
+**Implicit Allocators**
`context.temp_allocator`
Example:
@@ -27,6 +34,7 @@ Example:
LIBRARY_PATH :: "my_library.dll"
library, ok := dynlib.load_library(LIBRARY_PATH)
if ! ok {
+ fmt.eprintln(dynlib.last_error())
return
}
fmt.println("The library %q was successfully loaded", LIBRARY_PATH)
@@ -39,8 +47,8 @@ load_library :: proc(path: string, global_symbols := false) -> (library: Library
/*
Unloads a dynamic library.
-The underlying behaviour is platform specific.
-On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlclose`.
+The underlying behaviour is platform specific.
+On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlclose`.
On `windows` refer to `FreeLibrary`.
Example:
@@ -51,10 +59,12 @@ Example:
LIBRARY_PATH :: "my_library.dll"
library, ok := dynlib.load_library(LIBRARY_PATH)
if ! ok {
+ fmt.eprintln(dynlib.last_error())
return
}
did_unload := dynlib.unload_library(library)
if ! did_unload {
+ fmt.eprintln(dynlib.last_error())
return
}
fmt.println("The library %q was successfully unloaded", LIBRARY_PATH)
@@ -67,11 +77,11 @@ unload_library :: proc(library: Library) -> (did_unload: bool) {
/*
Loads the address of a procedure/variable from a dynamic library.
-The underlying behaviour is platform specific.
-On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlsym`.
+The underlying behaviour is platform specific.
+On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlsym`.
On `windows` refer to `GetProcAddress`.
-**Implicit Allocators**
+**Implicit Allocators**
`context.temp_allocator`
Example:
@@ -82,13 +92,101 @@ Example:
LIBRARY_PATH :: "my_library.dll"
library, ok := dynlib.load_library(LIBRARY_PATH)
if ! ok {
+ fmt.eprintln(dynlib.last_error())
return
}
a, found_a := dynlib.symbol_address(library, "a")
- if found_a do fmt.printf("The symbol %q was found at the address %v", "a", a)
+ if found_a {
+ fmt.printf("The symbol %q was found at the address %v", "a", a)
+ } else {
+ fmt.eprintln(dynlib.last_error())
+ }
}
*/
symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) #optional_ok {
return _symbol_address(library, symbol)
}
+
+/*
+Scans a dynamic library for symbols matching a struct's members, assigning found procedure pointers to the corresponding entry.
+Optionally takes a symbol prefix added to the struct's member name to construct the symbol looked up in the library.
+Optionally also takes the struct member to assign the library handle to, `__handle` by default.
+
+This allows using one struct to hold library handles and symbol pointers for more than 1 dynamic library.
+
+Loading the same library twice unloads the previous incarnation, allowing for straightforward hot reload support.
+
+Returns:
+* `-1, false` if the library could not be loaded.
+* The number of symbols assigned on success. `ok` = true if `count` > 0
+
+See doc.odin for an example.
+*/
+initialize_symbols :: proc(symbol_table: ^$T, library_path: string, symbol_prefix := "", handle_field_name := "__handle") -> (count: int, ok: bool) where intrinsics.type_is_struct(T) {
+ assert(symbol_table != nil)
+ handle: Library
+
+ if handle, ok = load_library(library_path); !ok {
+ return -1, false
+ }
+
+ // `symbol_table` must be a struct because of the where clause, so this can't fail.
+ ti := runtime.type_info_base(type_info_of(T))
+ s, _ := ti.variant.(runtime.Type_Info_Struct)
+
+ // Buffer to concatenate the prefix + symbol name.
+ prefixed_symbol_buf: [2048]u8 = ---
+
+ sym_ptr: rawptr
+ for field_name, i in s.names {
+ // Calculate address of struct member
+ field_ptr := rawptr(uintptr(rawptr(symbol_table)) + uintptr(s.offsets[i]))
+
+ // If we've come across the struct member for the handle, store it and continue scanning for other symbols.
+ if field_name == handle_field_name {
+ // We appear to be hot reloading. Unload previous incarnation of the library.
+ if old_handle := (^Library)(field_ptr)^; old_handle != nil {
+ if ok = unload_library(old_handle); !ok {
+ return count, ok
+ }
+ }
+ (^Library)(field_ptr)^ = handle
+ continue
+ }
+
+ // We're not the library handle, so the field needs to be a pointer type, be it a procedure pointer or an exported global.
+ if !(reflect.is_procedure(s.types[i]) || reflect.is_pointer(s.types[i])) {
+ continue
+ }
+
+ // Let's look up or construct the symbol name to find in the library
+ prefixed_name: string
+
+ // Do we have a symbol override tag?
+ if override, tag_ok := reflect.struct_tag_lookup(reflect.Struct_Tag(s.tags[i]), "dynlib"); tag_ok {
+ prefixed_name = string(override)
+ }
+
+ // No valid symbol override tag found, fall back to `<symbol_prefix>name`.
+ if len(prefixed_name) == 0 {
+ offset := copy(prefixed_symbol_buf[:], symbol_prefix)
+ copy(prefixed_symbol_buf[offset:], field_name)
+ prefixed_name = string(prefixed_symbol_buf[:len(symbol_prefix) + len(field_name)])
+ }
+
+ // Assign procedure (or global) pointer if found.
+ if sym_ptr, ok = symbol_address(handle, prefixed_name); ok {
+ (^rawptr)(field_ptr)^ = sym_ptr
+ count += 1
+ }
+ }
+ return count, count > 0
+}
+
+/*
+Returns an error message for the last failed procedure call.
+*/
+last_error :: proc() -> string {
+ return _last_error()
+}
diff --git a/core/dynlib/lib_js.odin b/core/dynlib/lib_js.odin
index ace1b0939..866874ee8 100644
--- a/core/dynlib/lib_js.odin
+++ b/core/dynlib/lib_js.odin
@@ -13,3 +13,7 @@ _unload_library :: proc(library: Library) -> bool {
_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) {
return nil, false
}
+
+_last_error :: proc() -> string {
+ return ""
+}
diff --git a/core/dynlib/lib_unix.odin b/core/dynlib/lib_unix.odin
index b0cc37e99..97f70b576 100644
--- a/core/dynlib/lib_unix.odin
+++ b/core/dynlib/lib_unix.odin
@@ -22,3 +22,8 @@ _symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found
found = ptr != nil
return
}
+
+_last_error :: proc() -> string {
+ err := os.dlerror()
+ return "unknown" if err == "" else err
+}
diff --git a/core/dynlib/lib_windows.odin b/core/dynlib/lib_windows.odin
index 67880df4b..9a1b5f998 100644
--- a/core/dynlib/lib_windows.odin
+++ b/core/dynlib/lib_windows.odin
@@ -5,6 +5,7 @@ package dynlib
import win32 "core:sys/windows"
import "core:strings"
import "core:runtime"
+import "core:reflect"
_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
// NOTE(bill): 'global_symbols' is here only for consistency with POSIX which has RTLD_GLOBAL
@@ -27,3 +28,9 @@ _symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found
found = ptr != nil
return
}
+
+_last_error :: proc() -> string {
+ err := win32.System_Error(win32.GetLastError())
+ err_msg := reflect.enum_string(err)
+ return "unknown" if err_msg == "" else err_msg
+}