From d6a89d667dc2dacdc760838cd489c3832b00b8f7 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 6 Jan 2024 01:31:27 +0100 Subject: Add `dynlib.initialize_symbols` (#3071) ``` 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 := dynlib.initialize_symbols(&sym, "lib.dll", "foo_", "_my_lib_handle") defer dynlib.unload_library(sym._my_lib_handle) fmt.printf("%v symbols loaded from lib.dll (%p).\n", 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^) } } ``` --- core/dynlib/example/example.odin | 36 ++++++++++++++++++++++++++++++++++++ core/dynlib/example/lib.odin | 14 ++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 core/dynlib/example/example.odin create mode 100644 core/dynlib/example/lib.odin (limited to 'core/dynlib/example') diff --git a/core/dynlib/example/example.odin b/core/dynlib/example/example.odin new file mode 100644 index 000000000..7a6368d59 --- /dev/null +++ b/core/dynlib/example/example.odin @@ -0,0 +1,36 @@ +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 := dynlib.initialize_symbols(&sym, "lib.dll", "foo_", "_my_lib_handle") + defer dynlib.unload_library(sym._my_lib_handle) + fmt.printf("%v symbols loaded from lib.dll (%p).\n", 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 -- cgit v1.2.3 From 649b5fa528834a973c19a0624025970946bc6fd0 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 6 Jan 2024 02:04:09 +0100 Subject: Add bool return to `dynlib.initialize_symbols`. --- core/dynlib/example/example.odin | 4 ++-- core/dynlib/lib.odin | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) (limited to 'core/dynlib/example') diff --git a/core/dynlib/example/example.odin b/core/dynlib/example/example.odin index 7a6368d59..78b14f5c0 100644 --- a/core/dynlib/example/example.odin +++ b/core/dynlib/example/example.odin @@ -24,9 +24,9 @@ main :: proc() { // 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 := dynlib.initialize_symbols(&sym, "lib.dll", "foo_", "_my_lib_handle") + count, ok := dynlib.initialize_symbols(&sym, "lib.dll", "foo_", "_my_lib_handle") defer dynlib.unload_library(sym._my_lib_handle) - fmt.printf("%v symbols loaded from lib.dll (%p).\n", count, sym._my_lib_handle) + fmt.printf("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)) diff --git a/core/dynlib/lib.odin b/core/dynlib/lib.odin index edeffe242..183ddd724 100644 --- a/core/dynlib/lib.odin +++ b/core/dynlib/lib.odin @@ -108,18 +108,17 @@ Optionally also takes the struct member to assign the library handle to, `__hand This allows using one struct to hold library handles and symbol pointers for more than 1 dynamic library. Returns: -* -1 if the library could not be loaded. -* The number of symbols assigned on success. +* `-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_name: string, symbol_prefix := "", handle_field_name := "__handle") -> (count: int) where intrinsics.type_is_struct(T) { +initialize_symbols :: proc(symbol_table: ^$T, library_name: string, symbol_prefix := "", handle_field_name := "__handle") -> (count: int, ok: bool) where intrinsics.type_is_struct(T) { assert(symbol_table != nil) - ok: bool handle: Library if handle, ok = load_library(library_name); !ok { - return -1 + return -1, false } // `symbol_table` must be a struct because of the where clause, so this can't fail. @@ -166,5 +165,5 @@ initialize_symbols :: proc(symbol_table: ^$T, library_name: string, symbol_prefi count += 1 } } - return + return count, count > 0 } \ No newline at end of file -- cgit v1.2.3 From e8102a40d03b06fbb48318577cbcbec8325ae1a9 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 19 Jan 2024 18:43:02 +0100 Subject: Add hot reload support to `dynlib.initialize_symbols` --- core/dynlib/example/example.odin | 11 ++++++++++- core/dynlib/lib.odin | 12 ++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) (limited to 'core/dynlib/example') diff --git a/core/dynlib/example/example.odin b/core/dynlib/example/example.odin index 78b14f5c0..f12233b0a 100644 --- a/core/dynlib/example/example.odin +++ b/core/dynlib/example/example.odin @@ -26,7 +26,16 @@ main :: proc() { // 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("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.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)) diff --git a/core/dynlib/lib.odin b/core/dynlib/lib.odin index 6a5c919a3..e9ee77d2c 100644 --- a/core/dynlib/lib.odin +++ b/core/dynlib/lib.odin @@ -115,17 +115,19 @@ Optionally also takes the struct member to assign the library handle to, `__hand 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_name: string, symbol_prefix := "", handle_field_name := "__handle") -> (count: int, ok: bool) where intrinsics.type_is_struct(T) { +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_name); !ok { + if handle, ok = load_library(library_path); !ok { return -1, false } @@ -143,6 +145,12 @@ initialize_symbols :: proc(symbol_table: ^$T, library_name: string, symbol_prefi // 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 } -- cgit v1.2.3