From 1cece52359ec32d3b82b2f419faceb2e323ee6a1 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Thu, 28 Nov 2024 21:05:17 +0100 Subject: [PATCH] dynlib: unload library before loading again & add LIBRARY_FILE_EXTENSION constant --- core/dynlib/example/example.odin | 12 ++++++----- core/dynlib/lib.odin | 36 ++++++++++++++++++++------------ core/dynlib/lib_js.odin | 2 ++ core/dynlib/lib_unix.odin | 2 ++ core/dynlib/lib_windows.odin | 2 ++ 5 files changed, 36 insertions(+), 18 deletions(-) diff --git a/core/dynlib/example/example.odin b/core/dynlib/example/example.odin index f12233b0acf..78fb5a98c35 100644 --- a/core/dynlib/example/example.odin +++ b/core/dynlib/example/example.odin @@ -21,12 +21,14 @@ Symbols :: struct { main :: proc() { sym: Symbols + LIB_PATH :: "lib." + dynlib.LIBRARY_FILE_EXTENSION + // 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") + count, ok := dynlib.initialize_symbols(&sym, LIB_PATH, "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) + fmt.printf("(Initial DLL Load) ok: %v. %v symbols loaded from " + LIB_PATH + " (%p).\n", ok, count, sym._my_lib_handle) if count > 0 { fmt.println("42 + 42 =", sym.add(42, 42)) @@ -34,12 +36,12 @@ main :: proc() { 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) + count, ok = dynlib.initialize_symbols(&sym, LIB_PATH, "foo_", "_my_lib_handle") + fmt.printf("(DLL Reload) ok: %v. %v symbols loaded from " + LIB_PATH + " (%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/lib.odin b/core/dynlib/lib.odin index 84675a56026..cd9eed5f881 100644 --- a/core/dynlib/lib.odin +++ b/core/dynlib/lib.odin @@ -12,6 +12,11 @@ A handle to a dynamically loaded library. */ Library :: distinct rawptr +/* +The file extension for dynamic libraries on the target OS. +*/ +LIBRARY_FILE_EXTENSION :: _LIBRARY_FILE_EXTENSION + /* Loads a dynamic library from the filesystem. The paramater `global_symbols` makes the symbols in the loaded library available to resolve references in subsequently loaded libraries. @@ -123,31 +128,36 @@ initialize_symbols :: proc( ) -> (count: int = -1, ok: bool = false) where intrinsics.type_is_struct(T) { assert(symbol_table != nil) - handle := load_library(library_path) or_return - - // Buffer to concatenate the prefix + symbol name. - prefixed_symbol_buf: [2048]u8 = --- - - count = 0 + // First, (re)load the library. + handle: Library for field in reflect.struct_fields_zipped(T) { - // Calculate address of struct member - field_ptr := rawptr(uintptr(symbol_table) + field.offset) - - // 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 { + field_ptr := rawptr(uintptr(symbol_table) + field.offset) + // We appear to be hot reloading. Unload previous incarnation of the library. if old_handle := (^Library)(field_ptr)^; old_handle != nil { unload_library(old_handle) or_return } + + handle = load_library(library_path) or_return (^Library)(field_ptr)^ = handle - continue + break } + } + + // Buffer to concatenate the prefix + symbol name. + prefixed_symbol_buf: [2048]u8 = --- - // 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(field.type) || reflect.is_pointer(field.type)) { + count = 0 + for field in reflect.struct_fields_zipped(T) { + // If we're not the library handle, the field needs to be a pointer type, be it a procedure pointer or an exported global. + if field.name == handle_field_name || !(reflect.is_procedure(field.type) || reflect.is_pointer(field.type)) { continue } + // Calculate address of struct member + field_ptr := rawptr(uintptr(symbol_table) + field.offset) + // Let's look up or construct the symbol name to find in the library prefixed_name: string diff --git a/core/dynlib/lib_js.odin b/core/dynlib/lib_js.odin index b99143ba0a5..99f486dd0c3 100644 --- a/core/dynlib/lib_js.odin +++ b/core/dynlib/lib_js.odin @@ -4,6 +4,8 @@ package dynlib import "base:runtime" +_LIBRARY_FILE_EXTENSION :: "" + _load_library :: proc(path: string, global_symbols: bool, allocator: runtime.Allocator) -> (Library, bool) { return nil, false } diff --git a/core/dynlib/lib_unix.odin b/core/dynlib/lib_unix.odin index 337bf496d7d..50ab1acc840 100644 --- a/core/dynlib/lib_unix.odin +++ b/core/dynlib/lib_unix.odin @@ -7,6 +7,8 @@ import "base:runtime" import "core:strings" import "core:sys/posix" +_LIBRARY_FILE_EXTENSION :: "dylib" when ODIN_OS == .Darwin else "so" + _load_library :: proc(path: string, global_symbols: bool, allocator: runtime.Allocator) -> (Library, bool) { flags := posix.RTLD_Flags{.NOW} if global_symbols { diff --git a/core/dynlib/lib_windows.odin b/core/dynlib/lib_windows.odin index 928a1510d76..05cd2cb3cd7 100644 --- a/core/dynlib/lib_windows.odin +++ b/core/dynlib/lib_windows.odin @@ -8,6 +8,8 @@ import win32 "core:sys/windows" import "core:strings" import "core:reflect" +_LIBRARY_FILE_EXTENSION :: "dll" + _load_library :: proc(path: string, global_symbols: bool, allocator: runtime.Allocator) -> (Library, bool) { // NOTE(bill): 'global_symbols' is here only for consistency with POSIX which has RTLD_GLOBAL wide_path := win32.utf8_to_wstring(path, allocator)