lib.odin 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. package dynlib
  2. import "base:intrinsics"
  3. import "core:reflect"
  4. import "base:runtime"
  5. _ :: intrinsics
  6. _ :: reflect
  7. _ :: runtime
  8. /*
  9. A handle to a dynamically loaded library.
  10. */
  11. Library :: distinct rawptr
  12. /*
  13. The file extension for dynamic libraries on the target OS.
  14. */
  15. LIBRARY_FILE_EXTENSION :: _LIBRARY_FILE_EXTENSION
  16. /*
  17. Loads a dynamic library from the filesystem. The paramater `global_symbols` makes the symbols in the loaded
  18. library available to resolve references in subsequently loaded libraries.
  19. The parameter `global_symbols` is only used for the platforms `linux`, `darwin`, `freebsd` and `openbsd`.
  20. On `windows` this paramater is ignored.
  21. The underlying behaviour is platform specific.
  22. On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlopen`.
  23. On `windows` refer to `LoadLibraryW`. Also temporarily needs an allocator to convert a string.
  24. Example:
  25. import "core:dynlib"
  26. import "core:fmt"
  27. load_my_library :: proc() {
  28. LIBRARY_PATH :: "my_library.dll"
  29. library, ok := dynlib.load_library(LIBRARY_PATH)
  30. if ! ok {
  31. fmt.eprintln(dynlib.last_error())
  32. return
  33. }
  34. fmt.println("The library %q was successfully loaded", LIBRARY_PATH)
  35. }
  36. */
  37. load_library :: proc(path: string, global_symbols := false, allocator := context.temp_allocator) -> (library: Library, did_load: bool) {
  38. return _load_library(path, global_symbols, allocator)
  39. }
  40. /*
  41. Unloads a dynamic library.
  42. The underlying behaviour is platform specific.
  43. On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlclose`.
  44. On `windows` refer to `FreeLibrary`.
  45. Example:
  46. import "core:dynlib"
  47. import "core:fmt"
  48. load_then_unload_my_library :: proc() {
  49. LIBRARY_PATH :: "my_library.dll"
  50. library, ok := dynlib.load_library(LIBRARY_PATH)
  51. if ! ok {
  52. fmt.eprintln(dynlib.last_error())
  53. return
  54. }
  55. did_unload := dynlib.unload_library(library)
  56. if ! did_unload {
  57. fmt.eprintln(dynlib.last_error())
  58. return
  59. }
  60. fmt.println("The library %q was successfully unloaded", LIBRARY_PATH)
  61. }
  62. */
  63. unload_library :: proc(library: Library) -> (did_unload: bool) {
  64. return _unload_library(library)
  65. }
  66. /*
  67. Loads the address of a procedure/variable from a dynamic library.
  68. The underlying behaviour is platform specific.
  69. On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlsym`.
  70. On `windows` refer to `GetProcAddress`. Also temporarily needs an allocator to convert a string.
  71. Example:
  72. import "core:dynlib"
  73. import "core:fmt"
  74. find_a_in_my_library :: proc() {
  75. LIBRARY_PATH :: "my_library.dll"
  76. library, ok := dynlib.load_library(LIBRARY_PATH)
  77. if ! ok {
  78. fmt.eprintln(dynlib.last_error())
  79. return
  80. }
  81. a, found_a := dynlib.symbol_address(library, "a")
  82. if found_a {
  83. fmt.printf("The symbol %q was found at the address %v", "a", a)
  84. } else {
  85. fmt.eprintln(dynlib.last_error())
  86. }
  87. }
  88. */
  89. symbol_address :: proc(library: Library, symbol: string, allocator := context.temp_allocator) -> (ptr: rawptr, found: bool) #optional_ok {
  90. return _symbol_address(library, symbol, allocator)
  91. }
  92. /*
  93. Scans a dynamic library for symbols matching a struct's members, assigning found procedure pointers to the corresponding entry.
  94. Optionally takes a symbol prefix added to the struct's member name to construct the symbol looked up in the library.
  95. Optionally also takes the struct member to assign the library handle to, `__handle` by default.
  96. This allows using one struct to hold library handles and symbol pointers for more than 1 dynamic library.
  97. Loading the same library twice unloads the previous incarnation, allowing for straightforward hot reload support.
  98. Returns:
  99. * `-1, false` if the library could not be loaded.
  100. * The number of symbols assigned on success. `ok` = true if `count` > 0
  101. See doc.odin for an example.
  102. */
  103. initialize_symbols :: proc(
  104. symbol_table: ^$T, library_path: string,
  105. symbol_prefix := "", handle_field_name := "__handle",
  106. ) -> (count: int = -1, ok: bool = false) where intrinsics.type_is_struct(T) {
  107. assert(symbol_table != nil)
  108. // First, (re)load the library.
  109. handle: Library
  110. for field in reflect.struct_fields_zipped(T) {
  111. if field.name == handle_field_name {
  112. field_ptr := rawptr(uintptr(symbol_table) + field.offset)
  113. // We appear to be hot reloading. Unload previous incarnation of the library.
  114. if old_handle := (^Library)(field_ptr)^; old_handle != nil {
  115. unload_library(old_handle) or_return
  116. }
  117. handle = load_library(library_path) or_return
  118. (^Library)(field_ptr)^ = handle
  119. break
  120. }
  121. }
  122. // Buffer to concatenate the prefix + symbol name.
  123. prefixed_symbol_buf: [2048]u8 = ---
  124. count = 0
  125. for field in reflect.struct_fields_zipped(T) {
  126. // If we're not the library handle, the field needs to be a pointer type, be it a procedure pointer or an exported global.
  127. if field.name == handle_field_name || !(reflect.is_procedure(field.type) || reflect.is_pointer(field.type)) {
  128. continue
  129. }
  130. // Calculate address of struct member
  131. field_ptr := rawptr(uintptr(symbol_table) + field.offset)
  132. // Let's look up or construct the symbol name to find in the library
  133. prefixed_name: string
  134. // Do we have a symbol override tag?
  135. if override, tag_ok := reflect.struct_tag_lookup(field.tag, "dynlib"); tag_ok {
  136. prefixed_name = override
  137. }
  138. // No valid symbol override tag found, fall back to `<symbol_prefix>name`.
  139. if len(prefixed_name) == 0 {
  140. offset := copy(prefixed_symbol_buf[:], symbol_prefix)
  141. copy(prefixed_symbol_buf[offset:], field.name)
  142. prefixed_name = string(prefixed_symbol_buf[:len(symbol_prefix) + len(field.name)])
  143. }
  144. // Assign procedure (or global) pointer if found.
  145. sym_ptr := symbol_address(handle, prefixed_name) or_continue
  146. (^rawptr)(field_ptr)^ = sym_ptr
  147. count += 1
  148. }
  149. return count, count > 0
  150. }
  151. // Returns an error message for the last failed procedure call.
  152. last_error :: proc() -> string {
  153. return _last_error()
  154. }