Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,10 @@
|
||||
## Description
|
||||
|
||||
`dl` can be used to Dynamically Load a library during runtime.
|
||||
It is a thin wrapper over `LoadLibrary` on Windows, and `dlopen` on Unix.
|
||||
|
||||
Using it, you can implement a plugin system for your application.
|
||||
|
||||
> **Note**
|
||||
> We highly recommend using `dl.loader` instead of `dl` directly.
|
||||
> It provides a more user-friendly API in the V way.
|
||||
@@ -0,0 +1,48 @@
|
||||
module dl
|
||||
|
||||
pub const version = 1
|
||||
|
||||
pub const dl_ext = get_shared_library_extension()
|
||||
|
||||
pub const rtld_next = voidptr(-1)
|
||||
|
||||
// get_shared_library_extension returns the platform dependent shared library extension
|
||||
// i.e. .dll on windows, .so on most unixes, .dylib on macos.
|
||||
@[inline]
|
||||
pub fn get_shared_library_extension() string {
|
||||
return $if windows {
|
||||
'.dll'
|
||||
} $else $if macos {
|
||||
'.dylib'
|
||||
} $else {
|
||||
'.so'
|
||||
}
|
||||
}
|
||||
|
||||
// get_libname returns a library name with the operating system specific extension for
|
||||
// shared libraries.
|
||||
@[inline]
|
||||
pub fn get_libname(libname string) string {
|
||||
return '${libname}${dl_ext}'
|
||||
}
|
||||
|
||||
// open_opt tries to load a given dynamic shared object.
|
||||
pub fn open_opt(filename string, flags int) !voidptr {
|
||||
shared_object_handle := open(filename, flags)
|
||||
if shared_object_handle == 0 {
|
||||
e := dlerror()
|
||||
return error(e)
|
||||
}
|
||||
return shared_object_handle
|
||||
}
|
||||
|
||||
// sym_opt returns the address of a symbol in a given shared object, if found.
|
||||
// Unlike sym, sym_opt returns an option.
|
||||
pub fn sym_opt(shared_object_handle voidptr, symbol string) !voidptr {
|
||||
sym_handle := sym(shared_object_handle, symbol)
|
||||
if sym_handle == 0 {
|
||||
e := dlerror()
|
||||
return error(e)
|
||||
}
|
||||
return sym_handle
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
module dl
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
$if linux {
|
||||
#flag -ldl
|
||||
}
|
||||
|
||||
pub const rtld_now = C.RTLD_NOW
|
||||
pub const rtld_lazy = C.RTLD_LAZY
|
||||
pub const rtld_global = C.RTLD_GLOBAL
|
||||
pub const rtld_local = C.RTLD_LOCAL
|
||||
pub const rtld_nodelete = C.RTLD_NODELETE
|
||||
pub const rtld_noload = C.RTLD_NOLOAD
|
||||
|
||||
fn C.dlopen(filename &char, flags i32) voidptr
|
||||
|
||||
fn C.dlsym(handle voidptr, symbol &char) voidptr
|
||||
|
||||
fn C.dlclose(handle voidptr) i32
|
||||
|
||||
fn C.dlerror() &char
|
||||
|
||||
// open loads a given dynamic shared object.
|
||||
pub fn open(filename string, flags int) voidptr {
|
||||
return C.dlopen(&char(filename.str), flags)
|
||||
}
|
||||
|
||||
// close frees a given shared object.
|
||||
pub fn close(handle voidptr) bool {
|
||||
return C.dlclose(handle) == 0
|
||||
}
|
||||
|
||||
// sym returns an address of a symbol in a given shared object.
|
||||
pub fn sym(handle voidptr, symbol string) voidptr {
|
||||
return C.dlsym(handle, &char(symbol.str))
|
||||
}
|
||||
|
||||
// dlerror provides a text error diagnostic message for functions in `dl`.
|
||||
// It returns a human-readable string, describing the most recent error
|
||||
// that occurred from a call to one of the `dl` functions, since the last
|
||||
// call to dlerror().
|
||||
pub fn dlerror() string {
|
||||
sptr := C.dlerror()
|
||||
if sptr == unsafe { nil } {
|
||||
return ''
|
||||
}
|
||||
return unsafe { cstring_to_vstring(sptr) }
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import dl
|
||||
|
||||
fn test_dl() {
|
||||
$if linux {
|
||||
run_test_invalid_lib_linux()
|
||||
return
|
||||
}
|
||||
$if windows {
|
||||
run_test_invalid_lib_windows()
|
||||
run_test_valid_lib_windows()
|
||||
run_test_invalid_sym_windows()
|
||||
run_test_valid_sym_windows()
|
||||
return
|
||||
} $else {
|
||||
eprint('currently not implemented on this platform')
|
||||
}
|
||||
}
|
||||
|
||||
fn run_test_invalid_lib_linux() {
|
||||
// ensure a not-existing dl won't be loaded
|
||||
h := dl.open('not-existing-dynamic-link-library', dl.rtld_now)
|
||||
assert h == 0
|
||||
}
|
||||
|
||||
fn run_test_invalid_lib_windows() {
|
||||
// ensure a not-existing dl won't be loaded
|
||||
h := dl.open('not-existing-dynamic-link-library', dl.rtld_now)
|
||||
assert h == 0
|
||||
}
|
||||
|
||||
fn run_test_valid_lib_windows() {
|
||||
h := dl.open('shell32', dl.rtld_now)
|
||||
assert h != 0
|
||||
}
|
||||
|
||||
fn run_test_invalid_sym_windows() {
|
||||
h := dl.open('shell32', dl.rtld_now)
|
||||
proc := dl.sym(h, 'CommandLineToArgvW2')
|
||||
assert proc == 0
|
||||
}
|
||||
|
||||
fn run_test_valid_sym_windows() {
|
||||
h := dl.open('shell32', dl.rtld_now)
|
||||
proc := dl.sym(h, 'CommandLineToArgvW')
|
||||
assert proc != 0
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
module dl
|
||||
|
||||
pub const rtld_now = 0
|
||||
pub const rtld_lazy = 0
|
||||
pub const rtld_global = 0
|
||||
pub const rtld_local = 0
|
||||
pub const rtld_nodelete = 0
|
||||
pub const rtld_noload = 0
|
||||
|
||||
fn C.LoadLibrary(libfilename &u16) voidptr
|
||||
|
||||
fn C.GetProcAddress(handle voidptr, procname &u8) voidptr
|
||||
|
||||
fn C.FreeLibrary(handle voidptr) bool
|
||||
|
||||
type FN_vinit_caller = fn ()
|
||||
|
||||
type FN_vcleanup_caller = fn ()
|
||||
|
||||
// open loads a given module into the address space of the calling process.
|
||||
pub fn open(filename string, flags int) voidptr {
|
||||
res := C.LoadLibrary(filename.to_wide())
|
||||
return res
|
||||
}
|
||||
|
||||
// close frees the loaded a given module.
|
||||
pub fn close(handle voidptr) bool {
|
||||
return C.FreeLibrary(handle)
|
||||
}
|
||||
|
||||
// sym returns an address of an exported function or variable from a given module.
|
||||
pub fn sym(handle voidptr, symbol string) voidptr {
|
||||
return C.GetProcAddress(handle, voidptr(symbol.str))
|
||||
}
|
||||
|
||||
// dlerror provides a text error diagnostic message for functions in `dl`.
|
||||
// It returns a human-readable string, describing the most recent error
|
||||
// that occurred from a call to one of the `dl` functions, since the last
|
||||
// call to dlerror().
|
||||
pub fn dlerror() string {
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror
|
||||
// Unlike dlerror(), GetLastError returns just an error code, that is function specific.
|
||||
cerr := int(C.GetLastError())
|
||||
return 'error code ${cerr}'
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
## Description
|
||||
|
||||
`dl.loader` is an abstraction layer over `dl` that provides a more user-friendly API in the V way.
|
||||
It can be used to Dynamically Load a library during runtime in scenarios where the library to load
|
||||
does not have a determined path and can be located in different places.
|
||||
|
||||
It also provides a way to load a library from a specific path, or from a list of paths, or from
|
||||
a custom environment variable that contains a list of paths.
|
||||
|
||||
## Usage
|
||||
|
||||
```v
|
||||
import dl.loader
|
||||
|
||||
// Load a library from a list of paths
|
||||
const default_paths = [
|
||||
'not-existing-dynamic-link-library'
|
||||
// 'C:\\Windows\\System32\\shell32.dll',
|
||||
'shell32',
|
||||
]
|
||||
|
||||
fn main() {
|
||||
mut dl_loader := loader.get_or_create_dynamic_lib_loader(
|
||||
key: 'LibExample'
|
||||
env_path: 'LIB_PATH'
|
||||
paths: default_paths
|
||||
)!
|
||||
|
||||
defer {
|
||||
dl_loader.unregister()
|
||||
}
|
||||
|
||||
sym := dl_loader.get_sym('CommandLineToArgvW')!
|
||||
assert !isnil(sym)
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,150 @@
|
||||
@[has_globals]
|
||||
module loader
|
||||
|
||||
import dl
|
||||
import os
|
||||
|
||||
const dl_no_path_issue_msg = 'no paths to dynamic library'
|
||||
const dl_open_issue_msg = 'could not open dynamic library'
|
||||
const dl_sym_issue_msg = 'could not get optional symbol from dynamic library'
|
||||
const dl_close_issue_msg = 'could not close dynamic library'
|
||||
const dl_register_issue_msg = 'could not register dynamic library loader'
|
||||
|
||||
pub const dl_no_path_issue_code = 1
|
||||
pub const dl_open_issue_code = 1
|
||||
pub const dl_sym_issue_code = 2
|
||||
pub const dl_close_issue_code = 3
|
||||
pub const dl_register_issue_code = 4
|
||||
|
||||
pub const dl_no_path_issue_err = error_with_code(dl_no_path_issue_msg, dl_no_path_issue_code)
|
||||
pub const dl_open_issue_err = error_with_code(dl_open_issue_msg, dl_open_issue_code)
|
||||
pub const dl_sym_issue_err = error_with_code(dl_sym_issue_msg, dl_sym_issue_code)
|
||||
pub const dl_close_issue_err = error_with_code(dl_close_issue_msg, dl_close_issue_code)
|
||||
pub const dl_register_issue_err = error_with_code(dl_register_issue_msg, dl_register_issue_code)
|
||||
|
||||
__global (
|
||||
registered_dl_loaders map[string]&DynamicLibLoader
|
||||
)
|
||||
|
||||
fn register_dl_loader(dl_loader &DynamicLibLoader) ! {
|
||||
if dl_loader.key in registered_dl_loaders {
|
||||
return dl_register_issue_err
|
||||
}
|
||||
registered_dl_loaders[dl_loader.key] = dl_loader
|
||||
}
|
||||
|
||||
// registered_dl_loader_keys returns the keys of registered DynamicLibLoader.
|
||||
pub fn registered_dl_loader_keys() []string {
|
||||
return registered_dl_loaders.keys()
|
||||
}
|
||||
|
||||
// DynamicLibLoader is a wrapper around dlopen, dlsym and dlclose.
|
||||
@[heap]
|
||||
pub struct DynamicLibLoader {
|
||||
pub:
|
||||
key string
|
||||
flags int = dl.rtld_lazy
|
||||
paths []string
|
||||
mut:
|
||||
handle voidptr
|
||||
sym_map map[string]voidptr
|
||||
}
|
||||
|
||||
// DynamicLibLoaderConfig is a configuration for DynamicLibLoader.
|
||||
@[params]
|
||||
pub struct DynamicLibLoaderConfig {
|
||||
pub:
|
||||
// flags is the flags for dlopen.
|
||||
flags int = dl.rtld_lazy
|
||||
// key is the key to register the DynamicLibLoader.
|
||||
key string
|
||||
// env_path is the environment variable name that contains the path to the dynamic library.
|
||||
env_path string
|
||||
// paths is the list of paths to the dynamic library.
|
||||
paths []string
|
||||
}
|
||||
|
||||
// new_dynamic_lib_loader returns a new DynamicLibLoader.
|
||||
fn new_dynamic_lib_loader(conf DynamicLibLoaderConfig) !&DynamicLibLoader {
|
||||
mut paths := []string{}
|
||||
|
||||
if conf.env_path != '' {
|
||||
if env_path := os.getenv_opt(conf.env_path) {
|
||||
paths << env_path.split(os.path_delimiter)
|
||||
}
|
||||
}
|
||||
|
||||
paths << conf.paths
|
||||
|
||||
if paths.len == 0 {
|
||||
return dl_no_path_issue_err
|
||||
}
|
||||
|
||||
mut dl_loader := &DynamicLibLoader{
|
||||
key: conf.key
|
||||
flags: conf.flags
|
||||
paths: paths
|
||||
}
|
||||
|
||||
register_dl_loader(dl_loader)!
|
||||
return dl_loader
|
||||
}
|
||||
|
||||
// get_or_create_dynamic_lib_loader returns a DynamicLibLoader.
|
||||
// If the DynamicLibLoader is not registered, it creates a new DynamicLibLoader.
|
||||
pub fn get_or_create_dynamic_lib_loader(conf DynamicLibLoaderConfig) !&DynamicLibLoader {
|
||||
if dl_loader := registered_dl_loaders[conf.key] {
|
||||
return dl_loader
|
||||
}
|
||||
return new_dynamic_lib_loader(conf)
|
||||
}
|
||||
|
||||
// load loads the dynamic library.
|
||||
pub fn (mut dl_loader DynamicLibLoader) open() !voidptr {
|
||||
if !isnil(dl_loader.handle) {
|
||||
return dl_loader.handle
|
||||
}
|
||||
|
||||
for path in dl_loader.paths {
|
||||
if handle := dl.open_opt(path, dl_loader.flags) {
|
||||
dl_loader.handle = handle
|
||||
return handle
|
||||
}
|
||||
}
|
||||
|
||||
return dl_open_issue_err
|
||||
}
|
||||
|
||||
// close closes the dynamic library.
|
||||
pub fn (mut dl_loader DynamicLibLoader) close() ! {
|
||||
if !isnil(dl_loader.handle) {
|
||||
if dl.close(dl_loader.handle) {
|
||||
dl_loader.handle = unsafe { nil }
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return dl_close_issue_err
|
||||
}
|
||||
|
||||
// get_sym gets a symbol from the dynamic library.
|
||||
pub fn (mut dl_loader DynamicLibLoader) get_sym(name string) !voidptr {
|
||||
if sym := dl_loader.sym_map[name] {
|
||||
return sym
|
||||
}
|
||||
|
||||
handle := dl_loader.open()!
|
||||
if sym := dl.sym_opt(handle, name) {
|
||||
dl_loader.sym_map[name] = sym
|
||||
return sym
|
||||
}
|
||||
|
||||
dl_loader.close()!
|
||||
return dl_sym_issue_err
|
||||
}
|
||||
|
||||
// unregister unregisters the DynamicLibLoader.
|
||||
pub fn (mut dl_loader DynamicLibLoader) unregister() {
|
||||
dl_loader.close() or {}
|
||||
registered_dl_loaders.delete(dl_loader.key)
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import dl.loader
|
||||
import dl
|
||||
|
||||
fn test_dl_loader() ! {
|
||||
$if linux {
|
||||
run_test_invalid_lib_linux()!
|
||||
return
|
||||
}
|
||||
$if windows {
|
||||
run_test_invalid_lib_windows()!
|
||||
run_test_valid_lib_windows()!
|
||||
run_test_invalid_sym_windows()!
|
||||
run_test_valid_sym_windows()!
|
||||
return
|
||||
} $else {
|
||||
eprint('currently not implemented on this platform')
|
||||
}
|
||||
}
|
||||
|
||||
fn get_or_create_loader(name string, paths []string) !&loader.DynamicLibLoader {
|
||||
return loader.get_or_create_dynamic_lib_loader(
|
||||
key: name
|
||||
paths: paths
|
||||
flags: dl.rtld_now
|
||||
)
|
||||
}
|
||||
|
||||
fn run_test_invalid_lib_linux() ! {
|
||||
// ensure a not-existing dl won't be loaded
|
||||
mut dl_loader := get_or_create_loader(@MOD + '.' + @FN + '.' + 'lib', [
|
||||
'not-existing-dynamic-link-library',
|
||||
])!
|
||||
defer {
|
||||
dl_loader.unregister()
|
||||
}
|
||||
h := dl_loader.open() or { unsafe { nil } }
|
||||
assert isnil(h)
|
||||
}
|
||||
|
||||
fn run_test_invalid_lib_windows() ! {
|
||||
// ensure a not-existing dl won't be loaded
|
||||
mut dl_loader := get_or_create_loader(@MOD + '.' + @FN + '.' + 'lib', [
|
||||
'not-existing-dynamic-link-library',
|
||||
])!
|
||||
defer {
|
||||
dl_loader.unregister()
|
||||
}
|
||||
h := dl_loader.open() or { unsafe { nil } }
|
||||
assert isnil(h)
|
||||
}
|
||||
|
||||
fn run_test_valid_lib_windows() ! {
|
||||
mut dl_loader := get_or_create_loader(@MOD + '.' + @FN + '.' + 'lib', [
|
||||
'not-existing-dynamic-link-library',
|
||||
'shell32',
|
||||
])!
|
||||
defer {
|
||||
dl_loader.unregister()
|
||||
}
|
||||
h := dl_loader.open() or { unsafe { nil } }
|
||||
assert !isnil(h)
|
||||
}
|
||||
|
||||
fn run_test_invalid_sym_windows() ! {
|
||||
mut dl_loader := get_or_create_loader(@MOD + '.' + @FN + '.' + 'lib', ['shell32'])!
|
||||
defer {
|
||||
dl_loader.unregister()
|
||||
}
|
||||
proc := dl_loader.get_sym('CommandLineToArgvW2') or { unsafe { nil } }
|
||||
assert isnil(proc)
|
||||
}
|
||||
|
||||
fn run_test_valid_sym_windows() ! {
|
||||
mut dl_loader := get_or_create_loader(@MOD + '.' + @FN + '.' + 'lib', [
|
||||
'not-existing-dynamic-link-library',
|
||||
'shell32',
|
||||
])!
|
||||
defer {
|
||||
dl_loader.unregister()
|
||||
}
|
||||
proc := dl_loader.get_sym('CommandLineToArgvW') or { unsafe { nil } }
|
||||
assert !isnil(proc)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import dl
|
||||
|
||||
type RealOpen = fn (charptr, int, int) int
|
||||
|
||||
fn test_rtld_next() {
|
||||
$if windows {
|
||||
println('skipping test_rtld_next on windows')
|
||||
return
|
||||
}
|
||||
real_open := RealOpen((dl.sym(dl.rtld_next, 'open')))
|
||||
println(ptr_str(real_open))
|
||||
assert real_open != unsafe { nil }
|
||||
assert dl.dlerror() == ''
|
||||
}
|
||||
Reference in new issue
Block a user