1 | new 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 |
1 | new 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 |
1 | new 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 |
1 | new 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 |
1 | new 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 |
1 | new 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 |