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:
parent
2d51379f00
commit
40ec2a292e
19
examples/dynamic_library_loader/modules/library/library.v
Normal file
19
examples/dynamic_library_loader/modules/library/library.v
Normal 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
|
||||||
|
}
|
35
examples/dynamic_library_loader/use.v
Normal file
35
examples/dynamic_library_loader/use.v
Normal 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}')
|
||||||
|
}
|
68
examples/dynamic_library_loader/use_test.v
Normal file
68
examples/dynamic_library_loader/use_test.v
Normal 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
|
||||||
|
}
|
@ -4,3 +4,6 @@
|
|||||||
It is a thin wrapper over `LoadLibrary` on Windows, and `dlopen` on Unix.
|
It is a thin wrapper over `LoadLibrary` on Windows, and `dlopen` on Unix.
|
||||||
|
|
||||||
Using it, you can implement a plugin system for your application.
|
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
36
vlib/dl/loader/README.md
Normal 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
153
vlib/dl/loader/loader.v
Normal 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)
|
||||||
|
}
|
83
vlib/dl/loader/loader_test.v
Normal file
83
vlib/dl/loader/loader_test.v
Normal 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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user