1 module dynalib;
2 
3 import core.stdc.stdio : printf;
4 import std.stdio : writeln;
5 
6 version (Posix) import core.sys.posix.dlfcn;
7 version (Windows)
8 {
9     import core.sys.windows.windows;
10     @nogc nothrow bool function(const char*) setDepDir;
11 }
12 
13 private template ptr_of(T) { alias ptr_of = typeof(*T); }
14 
15 /***********************************
16 * Borrowed from BindBC.SFML - Created by mdparker
17 *
18 * Original source: 
19 *     https://github.com/BindBC/bindbc-sfml/blob/40b08ffa045a139585720a113b619d4357cd89c2/source/bindbc/sfml/config.d#L46
20 */
21 enum expandEnum(EnumType, string fqnEnumType = EnumType.stringof) = ()
22 {
23     string expandEnum = "enum {";
24     foreach(m; __traits(allMembers, EnumType))
25         expandEnum ~= m ~ " = " ~ fqnEnumType ~ "." ~ m ~ ",";
26     
27     expandEnum  ~= "}";
28     return expandEnum;
29 } ();
30 
31 bool loadBindConfig(BindDef* def, string section, string altConfig = "")
32 {
33     import std.file : exists, readText, thisExePath;
34     import std.json : parseJSON;
35     import std.path : dirName, stripExtension;
36 
37     if (!def) return false;
38 
39     auto cf = thisExePath();
40     auto cd = dirName(cf);
41     auto file = cd ~ "/" ~ altConfig ~ ".bindconf";
42 
43     if (!exists(file))
44     {
45         file = stripExtension(cf) ~ ".bindconf";
46         if (!exists(file)) return false;
47     }
48 
49     auto json = parseJSON(readText(file));
50     if (!(section in json)) return false;
51     
52     def.from(json[section]);
53     return true;
54 }
55 
56 struct BindDef
57 {
58     import std.json : parseJSON, JSONValue;
59 
60     string nameLinux;
61     string nameOSX;
62     string nameWindows;
63     string loadPath;
64     string depPath;
65 
66     void from(string input) { from(parseJSON(input)); }
67 
68     void from(JSONValue input)
69     {
70         if ("nameLinux" in input)
71             nameLinux = input["nameLinux"].str;
72         else nameLinux = "";
73 
74         if ("nameOSX" in input)
75             nameOSX = input["nameOSX"].str;
76         else nameOSX = "";
77 
78         if ("nameWindows" in input)
79             nameWindows = input["nameWindows"].str;
80         else nameWindows = "";
81 
82         if ("loadPath" in input)
83             loadPath = input["loadPath"].str;
84         else loadPath = "";
85         
86         if ("depPath" in input)
87             depPath = input["depPath"].str;
88         else depPath = "";
89     }
90 
91     string namePlatform()
92     {
93         version (linux) return nameLinux;
94         version (OSX) return nameOSX;
95         version (Windows) return nameWindows;
96     }
97 
98     const(char)* fullName()
99     {
100         return cast(const(char*)) (loadPath ~ "/" ~ namePlatform() ~ "\00");
101     }
102 }
103 
104 struct DynaLib
105 {
106     HANDLE handle;
107     string fileName;
108     string directory;
109     string dependencyDir;
110 
111     private bool load()
112     {
113         if (handle)
114         {
115             writeln("DynaLib: Invalid request, handle is not yet free!");
116             return false;
117         }
118 
119         version (linux)
120         {
121             if (fileName == "")
122             {
123                 writeln("DynaLib: Invalid input, not enough information to load library!");
124                 return false;
125             }
126         }
127         else
128         {
129             if (
130                 fileName == "" ||
131                 directory == "" ||
132                 dependencyDir == ""
133             ) {
134                 writeln("DynaLib: Invalid input, not enough information to load library!");
135                 return false;
136             }
137         }
138         
139         dependencyPath(cast(const char*) dependencyDir);
140         handle = loadLib(cast(const char*)(directory ~ fileName));
141         dependencyPath(null);
142 
143         return handle != null;
144     }
145 
146     bool loadFromConfig(string section, string altConfig = "")
147     {
148         auto bind = BindDef();
149         if (!loadBindConfig(&bind, section, altConfig)) return false;
150         return load(bind);
151     }
152 
153     bool load(BindDef def)
154     {
155         version (linux) auto file = def.nameLinux;
156         version (OSX) auto file = def.nameOSX;
157         version (Windows) auto file = def.nameWindows;
158         return load(file, def.loadPath, def.depPath);
159     }
160 
161     bool load(string libName, string libDir = "", string depDir = "")
162     {
163         import std.file : exists, thisExePath;
164         import std.path : dirName;
165 
166         auto cd = dirName(thisExePath);
167         if (libDir == "") libDir = "./";
168         if (depDir == "") depDir = "./";
169         else if (depDir == "!") depDir = libDir;
170 
171         if (libDir[0] == '.') libDir = cd ~ libDir[1..$];
172         if (depDir[0] == '.') depDir = cd ~ depDir[1..$];
173 
174         auto e = cast(int) libDir.length - 1;
175         if (libDir[e] != '/' && libDir[e] != '\\') libDir ~= "/";
176 
177         e = cast(int) depDir.length - 1;
178         if (depDir[e] != '/' && depDir[e] != '\\') depDir ~= "/";
179 
180         version (linux) auto ext = ".so";
181         version (OSX) auto ext = ".dylib";
182         version (Windows) auto ext = ".dll";
183         
184         auto file = libDir ~ libName;
185         if (!exists(file))
186         {
187             file ~= ext;
188             if (!exists(file))
189             {
190                 file = libName;
191                 if (!exists(file))
192                 {
193                     file ~= ext;
194                     if (!exists(file))
195                     {
196                         version (linux)
197                         {
198                             import std.algorithm.searching : endsWith;
199 
200                             // Pray its on the system paths
201                             if (libName.endsWith(ext))
202                                 fileName = libName;
203                             else fileName = libName ~ ext;
204 
205                             directory = "";
206                             dependencyDir = "";
207                             
208                             return load();
209                         }
210                         else
211                         {
212                             writeln("DynaLib: File not found [" ~ libName ~ "]");
213                             return false;
214                         }
215                     } libName ~= ext;
216                 }
217             } libName ~= ext;
218         }
219 
220         fileName = libName;
221         directory = libDir;
222         dependencyDir = depDir;
223 
224         return load();
225     }
226 
227     bool bind(T)(T* ptr, string symName, bool silent = false)
228     {
229         static if (is(T == P*, P))
230             return bindSymbol(handle, cast(void**) ptr, 
231                     cast(const char*) symName, silent);
232         else
233         {
234             T* tmp;
235             auto ret = bindSymbol(handle, cast(void**) &tmp,
236                           cast(const char*) symName, silent);
237             *ptr = *tmp;
238             return ret;
239         }
240     }
241 
242     bool bindD(T)(T* ptr, string symName, bool silent = false)
243     {
244         import core.demangle : mangle;
245 
246         static if (is(T == P*, P))
247             return bindSymbol(handle, cast(void**) ptr,
248                               cast(const char*) mangle!(ptr_of!T)(symName), silent);
249         else
250         {
251             T* tmp;
252             auto ret = bindSymbol(handle, cast(void**) &tmp,
253                           cast(const char*) mangle!T(symName), silent);
254             *ptr = *tmp;
255             return ret;
256         }
257     }
258 
259     bool unload()
260     {
261         if (unloadLib(handle))
262         {
263             handle = null;
264             return true;
265         } return false;
266     }
267 
268     bool reload()
269     {
270         if (unloadLib(handle))
271             handle = null;
272 
273         return load();
274     }
275     
276     bool isLoaded()
277     {
278         return handle != null;
279     }
280 }
281 
282 @nogc nothrow:
283 
284     alias HANDLE = void*;
285 
286     HANDLE loadLib(const char* libName)
287     {
288         version (Posix) return dlopen(libName, RTLD_NOW);
289         else version (Windows) return LoadLibraryA(libName);
290         else throw new Exception("DynaLib does not support the current operating system.");
291     }
292 
293     bool unloadLib(HANDLE lib)
294     {
295         if (!lib)
296         {
297             printf("unloadLib: Library was already null!\n");
298             return false;
299         }
300         
301         version (Posix) dlclose(lib);
302         else version (Windows) FreeLibrary(lib);
303         else throw new Exception("DynaLib does not support the current operating system.");
304 
305         return true;
306     }
307 
308     bool bindSymbol(T)(HANDLE lib, T* ptr, const char* symbol, bool silent = false)
309     {
310         if (!lib)
311         {
312             printf("bindSymbol: Library is null!\n");
313             return false;
314         }
315 
316         version (Posix) *ptr = dlsym(lib, symbol);
317         else version (Windows) *ptr = GetProcAddress(lib, symbol);
318         else throw new Exception("DynaLib does not support the current operating system.");
319 
320         if (*ptr == null)
321         {
322             if (!silent)
323                 printf("bindSymbol: Symbol(%s) was not found in the library!\n", symbol);
324             return false;
325         }
326         
327         return true;
328     }
329 
330     bool dependencyPath(const char* path)
331     {
332         version (Windows)
333         {
334             if(!setDepDir)
335             {
336                 auto lib = loadLib("Kernel32.dll");
337                 if (!lib)
338                 {
339                     printf("dependencyPath: Library is null!\n");
340                     return false;
341                 }
342 
343                 bindSymbol(lib, cast(void**)&setDepDir, "SetDllDirectoryA");
344                 if (!setDepDir) 
345                 {
346                     printf("dependencyPath: Could not re-route dependency search!\n");
347                     return false;
348                 }
349             }
350 
351             setDepDir(path);
352         }
353         
354         return true;
355     }