alex

/

v Public
0 Issues 1 Contributor 0 Releases 4 Branches
Additions: 397 Deletions: 0 View patch
1new file mode 100644
2+module library
3+
4+// add_1 is exported with the C name `add_1`.
5+// It can be called by external programs, when the module is compiled
6+// as a shared library.
7+// It is exported, because the function is declared as public with `pub`.
8+// The exported C name is `add_1`, because of the export: tag.
9+// (Normally, the exported name is a V mangled version based on the module
10+// name followed by __, followed by the fn name, i.e. it would have been
11+// `library__add_1`, if not for the export: tag).
12+[export: 'add_1']
13+pub fn add_1(x int, y int) int {
14+ return my_private_function(x + y)
15+}
16+
17+// this function is not exported and will not be visible to external programs.
18+fn my_private_function(x int) int {
19+ return 1 + x
20+}
21
1new file mode 100644
2+module main
3+
4+// Note: This program, requires that the shared library was already compiled.
5+// To do so, run `v -d no_backtrace -o library -shared modules/library/library.v`
6+// before running this program.
7+import os
8+import dl
9+import dl.loader
10+
11+type FNAdder = fn (int, int) int
12+
13+const (
14+ cfolder = os.dir(@FILE)
15+ default_paths = [
16+ os.join_path(cfolder, 'library${dl.dl_ext}'),
17+ os.join_path(cfolder, 'location1/library${dl.dl_ext}'),
18+ os.join_path(cfolder, 'location2/library${dl.dl_ext}'),
19+ os.join_path(cfolder, 'modules/library/library${dl.dl_ext}'),
20+ ]
21+)
22+
23+fn main() {
24+ mut dl_loader := loader.get_or_create_dynamic_lib_loader(
25+ key: cfolder + '/library'
26+ paths: default_paths
27+ )!
28+ defer {
29+ dl_loader.unregister()
30+ }
31+ sym := dl_loader.get_sym('add_1')!
32+ f := FNAdder(sym)
33+ eprintln('f: ${ptr_str(f)}')
34+ res := f(1, 2)
35+ eprintln('res: ${res}')
36+}
37
1new file mode 100644
2+module main
3+
4+import os
5+import dl
6+
7+const (
8+ vexe = os.real_path(os.getenv('VEXE'))
9+ so_ext = dl.dl_ext
10+)
11+
12+fn test_vexe() {
13+ // dump(vexe)
14+ assert vexe != ''
15+ // dump(os.executable())
16+ // dump(@FILE)
17+ // dump(cfolder)
18+ // dump(so_ext)
19+}
20+
21+fn test_can_compile_library() {
22+ os.chdir(cfolder) or {}
23+ library_file_path := os.join_path(cfolder, dl.get_libname('library'))
24+ os.rm(library_file_path) or {}
25+ v_compile('-d no_backtrace -o library -shared modules/library/library.v')
26+ assert os.is_file(library_file_path)
27+}
28+
29+fn test_can_compile_main_program() {
30+ os.chdir(cfolder) or {}
31+ library_file_path := os.join_path(cfolder, dl.get_libname('library'))
32+ assert os.is_file(library_file_path)
33+ result := v_compile('run use.v')
34+ // dump(result)
35+ assert result.output.contains('res: 4')
36+ os.rm(library_file_path) or {}
37+}
38+
39+fn test_can_compile_and_use_library_with_skip_unused_home_dir() {
40+ os.chdir(cfolder) or {}
41+ library_file_path := os.join_path(cfolder, dl.get_libname('library'))
42+ os.rm(library_file_path) or {}
43+ v_compile('-skip-unused -d no_backtrace -o library -shared modules/library/library.v')
44+ assert os.is_file(library_file_path)
45+ result := v_compile('run use.v')
46+ assert result.output.contains('res: 4')
47+ os.rm(library_file_path) or {}
48+}
49+
50+fn test_can_compile_and_use_library_with_skip_unused_location1_dir() {
51+ os.chdir(cfolder) or {}
52+ library_file_path := os.join_path(cfolder, 'location1', dl.get_libname('library'))
53+ os.rm(library_file_path) or {}
54+ os.mkdir('location1') or {}
55+ v_compile('-skip-unused -d no_backtrace -o location1/library -shared modules/library/library.v')
56+ assert os.is_file(library_file_path)
57+ result := v_compile('run use.v')
58+ assert result.output.contains('res: 4')
59+ os.rm(library_file_path) or {}
60+}
61+
62+fn v_compile(vopts string) os.Result {
63+ cmd := '${os.quoted_path(vexe)} -showcc ${vopts}'
64+ // dump(cmd)
65+ res := os.execute_or_exit(cmd)
66+ // dump(res)
67+ assert res.exit_code == 0
68+ return res
69+}
70
1 It is a thin wrapper over `LoadLibrary` on Windows, and `dlopen` on Unix.
2
3 Using it, you can implement a plugin system for your application.
4+
5+> NOTE: We highly recommend using `dl.loader` instead of `dl` directly.
6+> It provides a more user-friendly API in the V way.
7
1new file mode 100644
2+## Description:
3+
4+`dl.loader` is an abstraction layer over `dl` that provides a more user-friendly API in the V way.
5+It can be used to Dynamically Load a library during runtime in scenarios where the library to load
6+does not have a determined path an can be located in different places.
7+
8+It also provides a way to load a library from a specific path, or from a list of paths, or from
9+a custom environment variable that contains a list of paths.
10+
11+## Usage:
12+
13+```v
14+import dl.loader
15+
16+// Load a library from a list of paths
17+const default_paths = [
18+ 'not-existing-dynamic-link-library'
19+ // 'C:\\Windows\\System32\\shell32.dll',
20+ 'shell32',
21+]
22+
23+fn main() {
24+ mut dl_loader := loader.get_or_create_dynamic_lib_loader(
25+ key: 'LibExample'
26+ env_path: 'LIB_PATH'
27+ paths: default_paths
28+ )!
29+
30+ defer {
31+ dl_loader.unregister()
32+ }
33+
34+ sym := dl_loader.get_sym('CommandLineToArgvW')!
35+ assert !isnil(sym)
36+}
37+```
38
1new file mode 100644
2+[has_globals]
3+module loader
4+
5+import dl
6+import os
7+
8+const (
9+ dl_no_path_issue_msg = 'no paths to dynamic library'
10+ dl_open_issue_msg = 'could not open dynamic library'
11+ dl_sym_issue_msg = 'could not get optional symbol from dynamic library'
12+ dl_close_issue_msg = 'could not close dynamic library'
13+ dl_register_issue_msg = 'could not register dynamic library loader'
14+)
15+
16+pub const (
17+ dl_no_path_issue_code = 1
18+ dl_open_issue_code = 1
19+ dl_sym_issue_code = 2
20+ dl_close_issue_code = 3
21+ dl_register_issue_code = 4
22+
23+ dl_no_path_issue_err = error_with_code(dl_no_path_issue_msg, dl_no_path_issue_code)
24+ dl_open_issue_err = error_with_code(dl_open_issue_msg, dl_open_issue_code)
25+ dl_sym_issue_err = error_with_code(dl_sym_issue_msg, dl_sym_issue_code)
26+ dl_close_issue_err = error_with_code(dl_close_issue_msg, dl_close_issue_code)
27+ dl_register_issue_err = error_with_code(dl_register_issue_msg, dl_register_issue_code)
28+)
29+
30+__global (
31+ registered_dl_loaders map[string]&DynamicLibLoader
32+)
33+
34+fn register_dl_loader(dl_loader &DynamicLibLoader) ! {
35+ if dl_loader.key in registered_dl_loaders {
36+ return loader.dl_register_issue_err
37+ }
38+ registered_dl_loaders[dl_loader.key] = dl_loader
39+}
40+
41+// registered_dl_loader_keys returns the keys of registered DynamicLibLoader.
42+pub fn registered_dl_loader_keys() []string {
43+ return registered_dl_loaders.keys()
44+}
45+
46+// DynamicLibLoader is a wrapper around dlopen, dlsym and dlclose.
47+[heap]
48+pub struct DynamicLibLoader {
49+pub:
50+ key string
51+ flags int = dl.rtld_lazy
52+ paths []string
53+mut:
54+ handle voidptr
55+ sym_map map[string]voidptr
56+}
57+
58+// DynamicLibLoaderConfig is a configuration for DynamicLibLoader.
59+[params]
60+pub struct DynamicLibLoaderConfig {
61+ // flags is the flags for dlopen.
62+ flags int = dl.rtld_lazy
63+ // key is the key to register the DynamicLibLoader.
64+ key string
65+ // env_path is the environment variable name that contains the path to the dynamic library.
66+ env_path string
67+ // paths is the list of paths to the dynamic library.
68+ paths []string
69+}
70+
71+// new_dynamic_lib_loader returns a new DynamicLibLoader.
72+fn new_dynamic_lib_loader(conf DynamicLibLoaderConfig) !&DynamicLibLoader {
73+ mut paths := []string{}
74+
75+ if conf.env_path.len > 0 {
76+ if env_path := os.getenv_opt(conf.env_path) {
77+ paths << env_path.split(os.path_delimiter)
78+ }
79+ }
80+
81+ paths << conf.paths
82+
83+ if paths.len == 0 {
84+ return loader.dl_no_path_issue_err
85+ }
86+
87+ mut dl_loader := &DynamicLibLoader{
88+ key: conf.key
89+ flags: conf.flags
90+ paths: paths
91+ }
92+
93+ register_dl_loader(dl_loader)!
94+ return dl_loader
95+}
96+
97+// get_or_create_dynamic_lib_loader returns a DynamicLibLoader.
98+// If the DynamicLibLoader is not registered, it creates a new DynamicLibLoader.
99+pub fn get_or_create_dynamic_lib_loader(conf DynamicLibLoaderConfig) !&DynamicLibLoader {
100+ if dl_loader := registered_dl_loaders[conf.key] {
101+ return dl_loader
102+ }
103+ return new_dynamic_lib_loader(conf)
104+}
105+
106+// load loads the dynamic library.
107+pub fn (mut dl_loader DynamicLibLoader) open() !voidptr {
108+ if !isnil(dl_loader.handle) {
109+ return dl_loader.handle
110+ }
111+
112+ for path in dl_loader.paths {
113+ if handle := dl.open_opt(path, dl_loader.flags) {
114+ dl_loader.handle = handle
115+ return handle
116+ }
117+ }
118+
119+ return loader.dl_open_issue_err
120+}
121+
122+// close closes the dynamic library.
123+pub fn (mut dl_loader DynamicLibLoader) close() ! {
124+ if !isnil(dl_loader.handle) {
125+ if dl.close(dl_loader.handle) {
126+ dl_loader.handle = unsafe { nil }
127+ return
128+ }
129+ }
130+
131+ return loader.dl_close_issue_err
132+}
133+
134+// get_sym gets a symbol from the dynamic library.
135+pub fn (mut dl_loader DynamicLibLoader) get_sym(name string) !voidptr {
136+ if sym := dl_loader.sym_map[name] {
137+ return sym
138+ }
139+
140+ handle := dl_loader.open()!
141+ if sym := dl.sym_opt(handle, name) {
142+ dl_loader.sym_map[name] = sym
143+ return sym
144+ }
145+
146+ dl_loader.close()!
147+ return loader.dl_sym_issue_err
148+}
149+
150+// unregister unregisters the DynamicLibLoader.
151+pub fn (mut dl_loader DynamicLibLoader) unregister() {
152+ dl_loader.close() or {}
153+ registered_dl_loaders.delete(dl_loader.key)
154+}
155
1new file mode 100644
2+import dl.loader
3+import dl
4+
5+fn test_dl_loader() ! {
6+ $if linux {
7+ run_test_invalid_lib_linux()!
8+ return
9+ }
10+ $if windows {
11+ run_test_invalid_lib_windows()!
12+ run_test_valid_lib_windows()!
13+ run_test_invalid_sym_windows()!
14+ run_test_valid_sym_windows()!
15+ return
16+ } $else {
17+ eprint('currently not implemented on this platform')
18+ }
19+}
20+
21+fn get_or_create_loader(name string, paths []string) !&loader.DynamicLibLoader {
22+ return loader.get_or_create_dynamic_lib_loader(
23+ key: name
24+ paths: paths
25+ flags: dl.rtld_now
26+ )
27+}
28+
29+fn run_test_invalid_lib_linux() ! {
30+ // ensure a not-existing dl won't be loaded
31+ mut dl_loader := get_or_create_loader(@MOD + '.' + @FN + '.' + 'lib', [
32+ 'not-existing-dynamic-link-library',
33+ ])!
34+ defer {
35+ dl_loader.unregister()
36+ }
37+ h := dl_loader.open() or { unsafe { nil } }
38+ assert isnil(h)
39+}
40+
41+fn run_test_invalid_lib_windows() ! {
42+ // ensure a not-existing dl won't be loaded
43+ mut dl_loader := get_or_create_loader(@MOD + '.' + @FN + '.' + 'lib', [
44+ 'not-existing-dynamic-link-library',
45+ ])!
46+ defer {
47+ dl_loader.unregister()
48+ }
49+ h := dl_loader.open() or { unsafe { nil } }
50+ assert isnil(h)
51+}
52+
53+fn run_test_valid_lib_windows() ! {
54+ mut dl_loader := get_or_create_loader(@MOD + '.' + @FN + '.' + 'lib', [
55+ 'not-existing-dynamic-link-library',
56+ 'shell32',
57+ ])!
58+ defer {
59+ dl_loader.unregister()
60+ }
61+ h := dl_loader.open() or { unsafe { nil } }
62+ assert !isnil(h)
63+}
64+
65+fn run_test_invalid_sym_windows() ! {
66+ mut dl_loader := get_or_create_loader(@MOD + '.' + @FN + '.' + 'lib', ['shell32'])!
67+ defer {
68+ dl_loader.unregister()
69+ }
70+ proc := dl_loader.get_sym('CommandLineToArgvW2') or { unsafe { nil } }
71+ assert isnil(proc)
72+}
73+
74+fn run_test_valid_sym_windows() ! {
75+ mut dl_loader := get_or_create_loader(@MOD + '.' + @FN + '.' + 'lib', [
76+ 'not-existing-dynamic-link-library',
77+ 'shell32',
78+ ])!
79+ defer {
80+ dl_loader.unregister()
81+ }
82+ proc := dl_loader.get_sym('CommandLineToArgvW') or { unsafe { nil } }
83+ assert !isnil(proc)
84+}
85