lib.odin 5.4 KB

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