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             version(DL_ERROR)
116                 writeln("DynaLib: Invalid request, handle is not yet free!");
117             return false;
118         }
119 
120         version (linux)
121         {
122             if (fileName == "")
123             {
124                 version(DL_ERROR)
125                     writeln("DynaLib: Invalid input, not enough information to load library!");
126                 return false;
127             }
128         }
129         else
130         {
131             if (
132                 fileName == "" ||
133                 directory == "" ||
134                 dependencyDir == ""
135             ) {
136                 version(DL_ERROR)
137                     writeln("DynaLib: Invalid input, not enough information to load library!");
138                 return false;
139             }
140         }
141         
142         dependencyPath(cast(const char*) dependencyDir);
143         handle = loadLib(cast(const char*)(directory ~ fileName));
144         dependencyPath(null);
145 
146         return handle != null;
147     }
148 
149     bool loadFromConfig(string section, string altConfig = "")
150     {
151         auto bind = BindDef();
152         if (!loadBindConfig(&bind, section, altConfig)) return false;
153         return load(bind);
154     }
155 
156     bool load(BindDef def)
157     {
158         version (linux) auto file = def.nameLinux;
159         version (OSX) auto file = def.nameOSX;
160         version (Windows) auto file = def.nameWindows;
161         return load(file, def.loadPath, def.depPath);
162     }
163 
164     bool load(string libName, string libDir = "", string depDir = "")
165     {
166         import std.file : exists, thisExePath;
167         import std.path : dirName;
168 
169         auto cd = dirName(thisExePath);
170         if (libDir == "") libDir = "./";
171         if (depDir == "") depDir = "./";
172         else if (depDir == "!") depDir = libDir;
173 
174         if (libDir[0] == '.') libDir = cd ~ libDir[1..$];
175         if (depDir[0] == '.') depDir = cd ~ depDir[1..$];
176 
177         auto e = cast(int) libDir.length - 1;
178         if (libDir[e] != '/' && libDir[e] != '\\') libDir ~= "/";
179 
180         e = cast(int) depDir.length - 1;
181         if (depDir[e] != '/' && depDir[e] != '\\') depDir ~= "/";
182 
183         version (linux) auto ext = ".so";
184         version (OSX) auto ext = ".dylib";
185         version (Windows) auto ext = ".dll";
186         
187         auto file = libDir ~ libName;
188         if (!exists(file))
189         {
190             file ~= ext;
191             if (!exists(file))
192             {
193                 file = libName;
194                 if (!exists(file))
195                 {
196                     file ~= ext;
197                     if (!exists(file))
198                     {
199                         version (linux)
200                         {
201                             import std.algorithm.searching : endsWith;
202 
203                             // Pray its on the system paths
204                             if (libName.endsWith(ext))
205                                 fileName = libName;
206                             else fileName = libName ~ ext;
207 
208                             directory = "";
209                             dependencyDir = "";
210                             
211                             return load();
212                         }
213                         else
214                         {
215                             version(DL_ERROR)
216                                 writeln("DynaLib: File not found [" ~ libName ~ "]");
217                             return false;
218                         }
219                     } libName ~= ext;
220                 }
221             } libName ~= ext;
222         }
223 
224         fileName = libName;
225         directory = libDir;
226         dependencyDir = depDir;
227 
228         return load();
229     }
230 
231     bool bind(T)(T* ptr, string symName)
232     {
233         static if (is(T == P*, P))
234             return bindSymbol(handle, cast(void**) ptr, 
235                     cast(const char*) symName);
236         else
237         {
238             T* tmp;
239             auto ret = bindSymbol(handle, cast(void**) &tmp,
240                           cast(const char*) symName);
241             *ptr = *tmp;
242             return ret;
243         }
244     }
245 
246     bool bindD(T)(T* ptr, string symName)
247     {
248         import core.demangle : mangle;
249 
250         static if (is(T == P*, P))
251             return bindSymbol(handle, cast(void**) ptr,
252                                 cast(const char*) mangle!(ptr_of!T)(symName));
253         else
254         {
255             T* tmp;
256             auto ret = bindSymbol(handle, cast(void**) &tmp,
257                                     cast(const char*) mangle!T(symName));
258             *ptr = *tmp;
259             return ret;
260         }
261     }
262 
263     bool unload()
264     {
265         if (unloadLib(handle))
266         {
267             handle = null;
268             return true;
269         } return false;
270     }
271 
272     bool reload()
273     {
274         if (unloadLib(handle))
275             handle = null;
276 
277         return load();
278     }
279     
280     bool isLoaded()
281     {
282         return handle != null;
283     }
284 }
285 
286 @nogc nothrow:
287 
288     alias HANDLE = void*;
289 
290     HANDLE loadLib(const char* libName)
291     {
292         version (Posix) return dlopen(libName, RTLD_NOW);
293         else version (Windows) return LoadLibraryA(libName);
294         else
295         {
296             version(DL_ERROR)
297                 printf("DynaLib does not support the current operating system.\n");
298             return null;
299         }
300     }
301 
302     bool unloadLib(HANDLE lib)
303     {
304         if (!lib)
305         {
306             version(DL_WARNING)
307                 printf("unloadLib: Library was already null!\n");
308             return false;
309         }
310         
311         version (Posix) dlclose(lib);
312         else version (Windows) FreeLibrary(lib);
313         else
314         {
315             version(DL_ERROR)
316                 printf("DynaLib does not support the current operating system.\n");
317             return false;
318         }
319 
320         return true;
321     }
322 
323     bool bindSymbol(T)(HANDLE lib, T* ptr, const char* symbol)
324     {
325         if (!lib)
326         {
327             version(DL_ERROR)
328                 printf("bindSymbol: Library is null!\n");
329             return false;
330         }
331 
332         version (Posix) *ptr = dlsym(lib, symbol);
333         else version (Windows) *ptr = GetProcAddress(lib, symbol);
334         else
335         {
336             version(DL_ERROR)
337                 printf("DynaLib does not support the current operating system.\n");
338             return false;
339         }
340 
341         if (*ptr == null)
342         {
343             version(DL_WARNING)
344                 printf("bindSymbol: Symbol(%s) was not found in the library!\n", symbol);
345             return false;
346         }
347         
348         return true;
349     }
350 
351     bool dependencyPath(const char* path)
352     {
353         version (Windows)
354         {
355             if(!setDepDir)
356             {
357                 auto lib = loadLib("Kernel32.dll");
358                 if (!lib)
359                 {
360                     version(DL_ERROR)
361                         printf("dependencyPath: Library 'Kernel32.dll' is null!\n");
362                     return false;
363                 }
364 
365                 if (!bindSymbol(lib, cast(void**)&setDepDir, "SetDllDirectoryA"))
366                 {
367                     version(DL_ERROR)
368                         printf("dependencyPath: 'SetDllDirectoryA' bind failed.\n");
369                     return false;
370                 }
371                 if (!setDepDir) 
372                 {
373                     version(DL_ERROR)
374                         printf("dependencyPath: Could not re-route dependency search!\n");
375                     return false;
376                 }
377             }
378 
379             setDepDir(path);
380         }
381         
382         return true;
383     }