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         import core.demangle : mangle;
234         import std.algorithm : canFind;
235 
236         static if (is(T == P*, P))
237             if (canFind(symName, "."))
238                 return bindSymbol(handle, cast(void**) ptr, cast(const char*) mangle!(ptr_of!T)(symName)); // D
239             else return bindSymbol(handle, cast(void**) ptr, cast(const char*) symName);                   // C
240         else
241         {
242             T* tmp;
243             auto ret = canFind(symName, ".") ?
244                 bindSymbol(handle, cast(void**) &tmp, cast(const char*) mangle!T(symName)): // D
245                 bindSymbol(handle, cast(void**) &tmp, cast(const char*) symName);           // C
246             *ptr = *tmp;
247             return ret;
248         }
249     }
250 
251     bool unload()
252     {
253         if (unloadLib(handle))
254         {
255             handle = null;
256             return true;
257         } return false;
258     }
259 
260     bool reload()
261     {
262         if (unloadLib(handle))
263             handle = null;
264 
265         return load();
266     }
267     
268     bool isLoaded()
269     {
270         return handle != null;
271     }
272 }
273 
274 @nogc nothrow:
275 
276     alias HANDLE = void*;
277 
278     HANDLE loadLib(const char* libName)
279     {
280         version (Posix) return dlopen(libName, RTLD_NOW);
281         else version (Windows) return LoadLibraryA(libName);
282         else
283         {
284             version(DL_ERROR)
285                 printf("DynaLib does not support the current operating system.\n");
286             return null;
287         }
288     }
289 
290     bool unloadLib(HANDLE lib)
291     {
292         if (!lib)
293         {
294             version(DL_WARNING)
295                 printf("unloadLib: Library was already null!\n");
296             return false;
297         }
298         
299         version (Posix) dlclose(lib);
300         else version (Windows) FreeLibrary(lib);
301         else
302         {
303             version(DL_ERROR)
304                 printf("DynaLib does not support the current operating system.\n");
305             return false;
306         }
307 
308         return true;
309     }
310 
311     bool bindSymbol(T)(HANDLE lib, T* ptr, const char* symbol)
312     {
313         if (!lib)
314         {
315             version(DL_ERROR)
316                 printf("bindSymbol: Library is null!\n");
317             return false;
318         }
319 
320         version (Posix) *ptr = dlsym(lib, symbol);
321         else version (Windows) *ptr = GetProcAddress(lib, symbol);
322         else
323         {
324             version(DL_ERROR)
325                 printf("DynaLib does not support the current operating system.\n");
326             return false;
327         }
328 
329         if (*ptr == null)
330         {
331             version(DL_WARNING)
332                 printf("bindSymbol: Symbol(%s) was not found in the library!\n", symbol);
333             return false;
334         }
335         
336         return true;
337     }
338 
339     bool dependencyPath(const char* path)
340     {
341         version (Windows)
342         {
343             if(!setDepDir)
344             {
345                 auto lib = loadLib("Kernel32.dll");
346                 if (!lib)
347                 {
348                     version(DL_ERROR)
349                         printf("dependencyPath: Library 'Kernel32.dll' is null!\n");
350                     return false;
351                 }
352 
353                 if (!bindSymbol(lib, cast(void**)&setDepDir, "SetDllDirectoryA"))
354                 {
355                     version(DL_ERROR)
356                         printf("dependencyPath: 'SetDllDirectoryA' bind failed.\n");
357                     return false;
358                 }
359                 if (!setDepDir) 
360                 {
361                     version(DL_ERROR)
362                         printf("dependencyPath: Could not re-route dependency search!\n");
363                     return false;
364                 }
365             }
366 
367             setDepDir(path);
368         }
369         
370         return true;
371     }