mirror of
https://github.com/odin-lang/Odin.git
synced 2026-02-15 07:43:13 +00:00
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^)
}
}
```
This commit is contained in:
@@ -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
|
||||
36
core/dynlib/example/example.odin
Normal file
36
core/dynlib/example/example.odin
Normal file
@@ -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^)
|
||||
}
|
||||
}
|
||||
14
core/dynlib/example/lib.odin
Normal file
14
core/dynlib/example/lib.odin
Normal file
@@ -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
|
||||
@@ -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:
|
||||
@@ -39,8 +46,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:
|
||||
@@ -67,11 +74,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:
|
||||
@@ -92,3 +99,72 @@ Example:
|
||||
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.
|
||||
|
||||
Returns:
|
||||
* -1 if the library could not be loaded.
|
||||
* The number of symbols assigned on success.
|
||||
|
||||
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) {
|
||||
assert(symbol_table != nil)
|
||||
ok: bool
|
||||
handle: Library
|
||||
|
||||
if handle, ok = load_library(library_name); !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
// `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 {
|
||||
(^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
|
||||
}
|
||||
Reference in New Issue
Block a user