1 /*
2                                     __
3                                    / _|
4   __ _ _   _ _ __ ___  _ __ __ _  | |_ ___  ___ ___
5  / _` | | | | '__/ _ \| '__/ _` | |  _/ _ \/ __/ __|
6 | (_| | |_| | | | (_) | | | (_| | | || (_) \__ \__ \
7  \__,_|\__,_|_|  \___/|_|  \__,_| |_| \___/|___/___/
8 
9 Copyright © 2013-2016, Mike Parker.
10 Copyright © 2016, 渡世白玉.
11 Copyright © 2018, Michael D. Parker
12 Copyright © 2018-2019, Aurora Free Open Source Software.
13 
14 This file is part of the Aurora Free Open Source Software. This
15 organization promote free and open source software that you can
16 redistribute and/or modify under the terms of the GNU Lesser General
17 Public License Version 3 as published by the Free Software Foundation or
18 (at your option) any later version approved by the Aurora Free Open Source
19 Software Organization. The license is available in the package root path
20 as 'LICENSE' file. Please review the following information to ensure the
21 GNU Lesser General Public License version 3 requirements will be met:
22 https://www.gnu.org/licenses/lgpl.html .
23 
24 Alternatively, this file may be used under the terms of the GNU General
25 Public License version 3 or later as published by the Free Software
26 Foundation. Please review the following information to ensure the GNU
27 General Public License requirements will be met:
28 https://www.gnu.org/licenses/gpl-3.0.html.
29 
30 NOTE: All products, services or anything associated to trademarks and
31 service marks used or referenced on this file are the property of their
32 respective companies/owners or its subsidiaries. Other names and brands
33 may be claimed as the property of others.
34 
35 For more info about intellectual property visit: aurorafoss.org or
36 directly send an email to: contact (at) aurorafoss.org .
37 
38 This file is an improvement of an existing code, part of DerelictUtil
39 from DerelictOrg. Check it out at derelictorg.github.io .
40 
41 This file is an improvement of an existing code, developed by 渡世白玉
42 and available on github at https://github.com/huntlabs/DerelictUtil .
43 
44 This file is an improvement of an existing code, part of bindbc-loader
45 from BindBC. Check it out at github.com/BindBC/bindbc-loader .
46 */
47 
48 module aurorafw.core.dylib;
49 
50 ///TODO: Need documentation
51 
52 import std.array;
53 import std.string;
54 import std.traits;
55 
56 version(Posix) import core.sys.posix.dlfcn;
57 else version(Windows) import core.sys.windows.windows;
58 
59 version(D_BetterC)
60 {
61 	import std.conv : to;
62 
63 	@nogc nothrow:
64 	void* dylib_load(const(char)* name)
65 	{
66 		version(Posix) void* handle = dlopen(name, RTLD_NOW);
67 		else version(Windows) void* handle = LoadLibraryA(name);
68 		if(handle) return handle;
69 		else return null;
70 	}
71 
72 	void dylib_unload(ref void* handle) {
73 		if(handle) {
74 			version(Posix) dlclose(handle);
75 			else version(Windows) FreeLibrary(handle);
76 			handle = null;
77 		}
78 	}
79 
80 	void dylib_bindSymbol(void* handle, void** ptr, const(char)* name)
81 	{
82 		assert(handle);
83 		version(Posix) void* sym = dlsym(handle, name);
84 		else version(Windows) void* sym = GetProcAddress(handle, name);
85 
86 		*ptr = sym;
87 	}
88 
89 	void dylib_sysError(char* buf, size_t len)
90 	{
91 		import core.stdc.string;
92 		version(Windows) {
93 			char* msgBuf;
94 			enum uint langID = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);
95 
96 			FormatMessageA(
97 				FORMAT_MESSAGE_ALLOCATE_BUFFER |
98 				FORMAT_MESSAGE_FROM_SYSTEM |
99 				FORMAT_MESSAGE_IGNORE_INSERTS,
100 				null,
101 				GetLastError(),
102 				langID,
103 				cast(char*)&msgBuf,
104 				0,
105 				null
106 			);
107 
108 			if(msgBuf) {
109 				strncpy(buf, msgBuf, len);
110 				buf[len - 1] = 0;
111 				LocalFree(msgBuf);
112 			}
113 			else strncpy(buf, "Unknown Error\0", len);
114 		}
115 		else version(Posix) {
116 			char* msg = dlerror();
117 			strncpy(buf, msg != null ? msg : "Unknown Error", len);
118 			buf[len - 1] = 0;
119 		}
120 	}
121 }
122 else {
123 	struct DylibVersion
124 	{
125 		uint major;
126 		uint minor;
127 		uint patch;
128 	}
129 
130 	class DylibLoadException : Exception {
131 		this(string msg, size_t line = __LINE__, string file = __FILE__)
132 		{
133 			super(msg, file, line, null);
134 		}
135 
136 		this(string[] names, string[] reasons, size_t line = __LINE__, string file = __FILE__)
137 		{
138 			string msg = "Failed to load one or more shared libraries:";
139 			foreach(i, name; names) {
140 				msg ~= "\n\t" ~ name ~ " - ";
141 				if(i < reasons.length)
142 					msg ~= reasons[i];
143 				else
144 					msg ~= "Unknown";
145 			}
146 			this(msg, line, file);
147 		}
148 
149 		this(string msg, string name = "", size_t line = __LINE__, string file = __FILE__)
150 		{
151 			super(msg, file, line, null);
152 			_name = name;
153 		}
154 
155 		pure nothrow @nogc
156 		@property string name()
157 		{
158 			return _name;
159 		}
160 		private string _name;
161 	}
162 
163 	class DylibSymbolLoadException : Exception {
164 		this(string msg, size_t line = __LINE__, string file = __FILE__) {
165 			super(msg, file, line, null);
166 		}
167 
168 		this(string lib, string symbol, size_t line = __LINE__, string file = __FILE__)
169 		{
170 			_lib = lib;
171 			_symbol = symbol;
172 			this("Failed to load symbol " ~ symbol ~ " from shared library " ~ lib, line, file);
173 		}
174 
175 		@property string lib()
176 		{
177 			return _lib;
178 		}
179 
180 		@property string symbol()
181 		{
182 			return _symbol;
183 		}
184 
185 	private:
186 		string _lib;
187 		string _symbol;
188 	}
189 
190 	pure struct Dylib
191 	{
192 		void load(string[] names)
193 		{
194 			if(isLoaded)
195 				return;
196 
197 			string[] fnames;
198 			string[] freasons;
199 
200 			foreach(name; names)
201 			{
202 				import std.stdio;
203 				import std.conv;
204 
205 				version(Posix) _handle = dlopen(name.toStringz(), RTLD_NOW);
206 				else version(Windows) _handle = LoadLibraryA(name.toStringz());
207 				if(isLoaded) {
208 					_name = name;
209 					break;
210 				}
211 
212 				fnames ~= name;
213 
214 				import std.conv : to;
215 
216 				version(Posix) {
217 					string err = to!string(dlerror());
218 					if(err is null)
219 						err = "Unknown error";
220 				}
221 				else version(Windows)
222 				{
223 					import std.windows.syserror;
224 					string err = sysErrorString(GetLastError());
225 				}
226 
227 				freasons ~= err;
228 			}
229 			if(!isLoaded)
230 				throw new DylibLoadException(fnames, freasons);
231 		}
232 
233 		void* loadSymbol(string name, bool required = true)
234 		{
235 			version(Posix) void* sym = dlsym(_handle, name.toStringz());
236 			else version(Windows) void* sym = GetProcAddress(_handle, name.toStringz());
237 
238 			if(required && !sym)
239 			{
240 				if(_callback !is null)
241 					required = _callback(name);
242 				if(required)
243 					throw new DylibSymbolLoadException(_name, name);
244 			}
245 
246 			return sym;
247 		}
248 
249 		void unload()
250 		{
251 			if(isLoaded)
252 			{
253 				version(Posix) dlclose(_handle);
254 				else version(Windows) FreeLibrary(_handle);
255 				_handle = null;
256 			}
257 		}
258 
259 		@property bool isLoaded()
260 		{
261 			return _handle !is null;
262 		}
263 
264 		@property bool delegate(string) missingSymbolCallback()
265 		{
266 			return _callback;
267 		}
268 
269 		@property void missingSymbolCallback(bool delegate(string) callback)
270 		{
271 			_callback = callback;
272 		}
273 
274 		@property void missingSymbolCallback(bool function(string) callback)
275 		{
276 			import std.functional : toDelegate;
277 			_callback = toDelegate(callback);
278 		}
279 
280 	private:
281 		string _name;
282 		void* _handle;
283 		bool delegate(string) _callback;
284 	}
285 
286 	abstract class DylibLoader
287 	{
288 		this(string libs)
289 		{
290 			string[] libs_ = libs.split(",");
291 			foreach(ref string l; libs_)
292 				l = l.strip();
293 			this(libs_);
294 		}
295 
296 		this(string[] libs)
297 		{
298 			_libs = libs;
299 			dylib.load(_libs);
300 			loadSymbols();
301 		}
302 
303 		final this(string[] libs, DylibVersion ver)
304 		{
305 			configureMinimumVersion(ver);
306 			this(libs);
307 		}
308 
309 		final this(string libs, DylibVersion ver)
310 		{
311 			configureMinimumVersion(ver);
312 			this(libs);
313 		}
314 
315 		protected void loadSymbols() {}
316 
317 		protected void configureMinimumVersion(DylibVersion minVersion)
318 		{
319 			assert(0, "DylibVersion is not supported by this loader.");
320 		}
321 
322 		protected final void bindFunc(void** ptr, string name, bool required = true)
323 		{
324 			void* func = dylib.loadSymbol(name, required);
325 			*ptr = func;
326 		}
327 
328 		protected final void bindFunc(TFUN)(ref TFUN fun, string name, bool required = true)
329 			if(isFunctionPointer!(TFUN))
330 		{
331 			void* func = dylib.loadSymbol(name, required);
332 			fun = cast(TFUN)func;
333 		}
334 
335 		protected final void bindFunc_stdcall(Func)(ref Func f, string unmangledName)
336 		{
337 			version(Win32) {
338 				import std.format : format;
339 				import std.traits : ParameterTypeTuple;
340 
341 				// get type-tuple of parameters
342 				ParameterTypeTuple!f params;
343 
344 				size_t sizeOfParametersOnStack(A...)(A args)
345 				{
346 					size_t sum = 0;
347 					foreach (arg; args) {
348 						sum += arg.sizeof;
349 
350 						// align on 32-bit stack
351 						if (sum % 4 != 0)
352 							sum += 4 - (sum % 4);
353 					}
354 					return sum;
355 				}
356 				unmangledName = format("_%s@%s", unmangledName, sizeOfParametersOnStack(params));
357 			}
358 			bindFunc(cast(void**)&f, unmangledName);
359 		}
360 
361 		@property final string[] libs()
362 		{
363 			return _libs;
364 		}
365 
366 		Dylib dylib;
367 		private string[] _libs;
368 	}
369 
370 	template DylibBuildLoadSymbols(alias T, bool required = false)
371 	{
372 		string _buildLoadSymbols(alias T, bool required = false)()
373 		{
374 			static if(required)
375 				enum dthrow = "true";
376 			else
377 				enum dthrow = "false";
378 
379 			string bindlist = "\n{";
380 			foreach(mem; __traits(derivedMembers, T))
381 			{
382 				static if( isFunctionPointer!(__traits(getMember, T, mem)) /*&& !is(typeof(__traits(getMember, T, mem)) == immutable)*/)
383 				{
384 					bindlist ~= "\tbindFunc(" ~ mem ~ ", \"" ~ mem ~ "\", " ~ dthrow ~ ");\n";
385 				}
386 			}
387 			bindlist ~= "}";
388 			return bindlist;
389 		}
390 
391 		enum DylibBuildLoadSymbols = _buildLoadSymbols!(T, required)();
392 	}
393 }