1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

vlib: add a new dl.loader module, to simplify dynamic library loading, when the DLLs may be in multiple customisable locations (#17161)

This commit is contained in:
Ulises Jeremias Cornejo Fandos 2023-01-31 04:27:48 -03:00 committed by GitHub
parent 2d51379f00
commit 40ec2a292e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 397 additions and 0 deletions

View File

@ -0,0 +1,19 @@
module library
// add_1 is exported with the C name `add_1`.
// It can be called by external programs, when the module is compiled
// as a shared library.
// It is exported, because the function is declared as public with `pub`.
// The exported C name is `add_1`, because of the export: tag.
// (Normally, the exported name is a V mangled version based on the module
// name followed by __, followed by the fn name, i.e. it would have been
// `library__add_1`, if not for the export: tag).
[export: 'add_1']
pub fn add_1(x int, y int) int {
return my_private_function(x + y)
}
// this function is not exported and will not be visible to external programs.
fn my_private_function(x int) int {
return 1 + x
}

View File

@ -0,0 +1,35 @@
module main
// Note: This program, requires that the shared library was already compiled.
// To do so, run `v -d no_backtrace -o library -shared modules/library/library.v`
// before running this program.
import os
import dl
import dl.loader
type FNAdder = fn (int, int) int
const (
cfolder = os.dir(@FILE)
default_paths = [
os.join_path(cfolder, 'library${dl.dl_ext}'),
os.join_path(cfolder, 'location1/library${dl.dl_ext}'),
os.join_path(cfolder, 'location2/library${dl.dl_ext}'),
os.join_path(cfolder, 'modules/library/library${dl.dl_ext}'),
]
)
fn main() {
mut dl_loader := loader.get_or_create_dynamic_lib_loader(
key: cfolder + '/library'
paths: default_paths
)!
defer {
dl_loader.unregister()
}
sym := dl_loader.get_sym('add_1')!
f := FNAdder(sym)
eprintln('f: ${ptr_str(f)}')
res := f(1, 2)
eprintln('res: ${res}')
}

View File

@ -0,0 +1,68 @@
module main
import os
import dl
const (
vexe = os.real_path(os.getenv('VEXE'))
so_ext = dl.dl_ext
)
fn test_vexe() {
// dump(vexe)
assert vexe != ''
// dump(os.executable())
// dump(@FILE)
// dump(cfolder)
// dump(so_ext)
}
fn test_can_compile_library() {
os.chdir(cfolder) or {}
library_file_path := os.join_path(cfolder, dl.get_libname('library'))
os.rm(library_file_path) or {}
v_compile('-d no_backtrace -o library -shared modules/library/library.v')
assert os.is_file(library_file_path)
}
fn test_can_compile_main_program() {
os.chdir(cfolder) or {}
library_file_path := os.join_path(cfolder, dl.get_libname('library'))
assert os.is_file(library_file_path)
result := v_compile('run use.v')
// dump(result)
assert result.output.contains('res: 4')
os.rm(library_file_path) or {}
}
fn test_can_compile_and_use_library_with_skip_unused_home_dir() {
os.chdir(cfolder) or {}
library_file_path := os.join_path(cfolder, dl.get_libname('library'))
os.rm(library_file_path) or {}
v_compile('-skip-unused -d no_backtrace -o library -shared modules/library/library.v')
assert os.is_file(library_file_path)
result := v_compile('run use.v')
assert result.output.contains('res: 4')
os.rm(library_file_path) or {}
}
fn test_can_compile_and_use_library_with_skip_unused_location1_dir() {
os.chdir(cfolder) or {}
library_file_path := os.join_path(cfolder, 'location1', dl.get_libname('library'))
os.rm(library_file_path) or {}
os.mkdir('location1') or {}
v_compile('-skip-unused -d no_backtrace -o location1/library -shared modules/library/library.v')
assert os.is_file(library_file_path)
result := v_compile('run use.v')
assert result.output.contains('res: 4')
os.rm(library_file_path) or {}
}
fn v_compile(vopts string) os.Result {
cmd := '${os.quoted_path(vexe)} -showcc ${vopts}'
// dump(cmd)
res := os.execute_or_exit(cmd)
// dump(res)
assert res.exit_code == 0
return res
}

View File

@ -4,3 +4,6 @@
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.

36
vlib/dl/loader/README.md Normal file
View File

@ -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 an 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)
}
```

153
vlib/dl/loader/loader.v Normal file
View File

@ -0,0 +1,153 @@
[has_globals]
module loader
import dl
import os
const (
dl_no_path_issue_msg = 'no paths to dynamic library'
dl_open_issue_msg = 'could not open dynamic library'
dl_sym_issue_msg = 'could not get optional symbol from dynamic library'
dl_close_issue_msg = 'could not close dynamic library'
dl_register_issue_msg = 'could not register dynamic library loader'
)
pub const (
dl_no_path_issue_code = 1
dl_open_issue_code = 1
dl_sym_issue_code = 2
dl_close_issue_code = 3
dl_register_issue_code = 4
dl_no_path_issue_err = error_with_code(dl_no_path_issue_msg, dl_no_path_issue_code)
dl_open_issue_err = error_with_code(dl_open_issue_msg, dl_open_issue_code)
dl_sym_issue_err = error_with_code(dl_sym_issue_msg, dl_sym_issue_code)
dl_close_issue_err = error_with_code(dl_close_issue_msg, dl_close_issue_code)
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 loader.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 {
// 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.len > 0 {
if env_path := os.getenv_opt(conf.env_path) {
paths << env_path.split(os.path_delimiter)
}
}
paths << conf.paths
if paths.len == 0 {
return loader.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 loader.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 loader.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 loader.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)
}

View File

@ -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)
}