1 /*
2  * This file is part of gtkD.
3  *
4  * gtkD is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License
6  * as published by the Free Software Foundation; either version 3
7  * of the License, or (at your option) any later version, with
8  * some exceptions, please read the COPYING file.
9  *
10  * gtkD is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with gtkD; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
18  */
19 
20 module gtkd.Loader;
21 
22 import std.algorithm : canFind;
23 import std.stdio;
24 import std.string;
25 
26 import gtkd.paths;
27 
28 public struct Linker
29 {
30 	private static void*[string]    loadedLibraries;
31 	private static string[][string] loadFailures;
32 
33 	extern(C) static void unsupportedSymbol()
34 	{
35 		throw new Error("The function you are calling is not pressent in your version of GTK+.");
36 	}
37 
38 	/*
39 	 * Links the provided symbol
40 	 * Params:
41 	 *     funct     = The function we are linking
42 	 *     symbol    = The name of the symbol to link
43 	 *     libraries = One or more libraries to search for the symbol
44 	 */
45 	public static void link(T)(ref T funct, string symbol, const string[] libraries ...)
46 	{
47 		funct = cast(T)getSymbol(symbol, libraries);
48 	}
49 
50 	/*
51 	 * Gets a simbol from one of the provided libraries
52 	 * Params:
53 	 *     symbol    = The name of the symbol to link
54 	 *     libraries = One or more libraries to search for the symbol
55 	 */
56 	public static void* getSymbol(string symbol, const string[] libraries ...)
57 	{
58 		void* handle;
59 
60 		foreach ( library; libraries )
61 		{
62 			if( !(library in loadedLibraries) )
63 				loadLibrary(library);
64 
65 			handle = pGetSymbol(loadedLibraries[library], symbol);
66 
67 			if ( handle !is null )
68 				break;
69 		}
70 
71 		if ( handle is null )
72 		{
73 			foreach ( library; libraries )
74 				loadFailures[library] ~= symbol;
75 
76 			handle = &unsupportedSymbol;
77 		}
78 
79 		return handle;
80 	}
81 
82 	/*
83 	 * Loads a library
84 	 */
85 	public static void loadLibrary(string library)
86 	{
87 		void* handle;
88 
89 		if ( library.canFind(';') )
90 		{
91 			foreach ( lib; library.split(';') )
92 			{
93 				handle = pLoadLibrary(lib);
94 				if ( handle )
95 					break;
96 			}
97 		}
98 		else
99 		{
100 			handle = pLoadLibrary(library);
101 		}
102 
103 		if ( handle is null )
104 			throw new Exception("Library load failed ("~ library ~"): "~ getErrorMessage());
105 
106 		loadedLibraries[library] = handle;
107 	}
108 
109 	/*
110 	 * Unload a library
111 	 */
112 	public static void unloadLibrary(string library)
113 	{
114 		pUnloadLibrary(loadedLibraries[library]);
115 
116 		loadedLibraries.remove(library);
117 	}
118 
119 	///Ditto
120 	public static void unloadLibrary(const string[] libraries)
121 	{
122 		foreach ( lib; libraries )
123 		{
124 			unloadLibrary(lib);
125 		}
126 	}
127 
128 	/**
129 	 * Checks if any symbol failed to load
130 	 * Returns: true if ALL symbols are loaded
131 	 */
132 	public static bool isPerfectLoad()
133 	{
134 		return loadFailures.keys.length == 0;
135 	}
136 
137 	/**
138 	 * Gets all libraries loaded.
139 	 * returns: An array with the loaded libraries
140 	 */
141 	public static string[] getLoadLibraries()
142 	{
143 		return loadedLibraries.keys;
144 	}
145 
146 	/**
147 	 * Print all libraries loaded.
148 	 */
149 	public static void dumpLoadLibraries()
150 	{
151 		foreach ( lib; getLoadLibraries() )
152 		{
153 			writefln("Loaded lib = %s", lib);
154 		}
155 	}
156 
157 	/**
158 	 * Checks if a library is loaded.
159 	 * Returns: true is the library was loaded sucsessfully.
160 	 */
161 	public static bool isLoaded(string library)
162 	{
163 		if ( library in loadedLibraries )
164 			return true;
165 		else
166 			return false;
167 	}
168 
169 	///Ditto
170 	public static bool isLoaded(const string[] libraries)
171 	{
172 		return isLoaded(libraries[0]);
173 	}
174 
175 	/**
176 	 * Gets all the failed loads for a specific library.
177 	 * returns: An array of the names hat failed to load for a specific library
178 	 *          or null if none was found
179 	 */
180 	public static string[] getLoadFailures(string library)
181 	{
182 		if ( library in loadFailures )
183 			return loadFailures[library];
184 		else
185 			return null;
186 	}
187 
188 	///Ditto.
189 	public static string[] getLoadFailures(const string[] libraries)
190 	{
191 		string[] failures;
192 
193 		foreach ( lib; libraries )
194 		{
195 			failures ~= getLoadFailures(lib);
196 		}
197 
198 		return failures;
199 	}
200 
201 	/**
202 	 * Print all symbols that failed to load
203 	 */
204 	public static void dumpFailedLoads()
205 	{
206 		foreach ( library; loadedLibraries.keys )
207 		{
208 			foreach ( symbol; getLoadFailures(library) )
209 			{
210 				writefln("failed (%s) %s", library, symbol);
211 			}
212 		}
213 	}
214 
215 	static ~this()
216 	{
217 		foreach ( library; loadedLibraries.keys )
218 			unloadLibrary(library);
219 	}
220 
221 		/*
222 	 * Links the provided symbol
223 	 * Params:
224 	 *     funct     = The function we are linking
225 	 *     symbol    = The name of the symbol to link
226 	 *     libraries = One or more libraries to search for the symbol
227 	 */
228 	deprecated("Use the LIBRARY_* symbols defined for each package, instead of gtkd.paths.LIBRARY")
229 	public static void link(T)(ref T funct, string symbol, LIBRARY[] libraries ...)
230 	{
231 		funct = cast(T)getSymbol(symbol, libraries);
232 	}
233 
234 	/*
235 	 * Gets a simbol from one of the provided libraries
236 	 * Params:
237 	 *     symbol    = The name of the symbol to link
238 	 *     libraries = One or more libraries to search for the symbol
239 	 */
240 	deprecated("Use the LIBRARY_* symbols defined for each package, instead of gtkd.paths.LIBRARY")
241 	public static void* getSymbol(string symbol, LIBRARY[] libraries ...)
242 	{
243 		string[] libStr = new string[libraries.length];
244 
245 		foreach (i, library; libraries )
246 		{
247 			libStr[i] = importLibs[library];
248 		}
249 
250 		return getSymbol(symbol, libStr);
251 	}
252 
253 	/*
254 	 * Unload a library
255 	 */
256 	deprecated("Use the LIBRARY_* symbols defined for each package, instead of gtkd.paths.LIBRARY")
257 	public static void unloadLibrary(LIBRARY library)
258 	{
259 		unloadLibrary( importLibs[library] );
260 	}
261 
262 	/**
263 	 * Checks if a library is loaded.
264 	 * Returns: true is the library was loaded sucsessfully.
265 	 */
266 	deprecated("Use the LIBRARY_* symbols defined for each package, instead of gtkd.paths.LIBRARY")
267 	public static bool isLoaded(LIBRARY library)
268 	{
269 		return isLoaded(importLibs[library]);
270 	}
271 
272 	/**
273 	 * Gets all the failed loads for a specific library.
274 	 * returns: An array of the names hat failed to load for a specific library
275 	 *          or null if none was found
276 	 */
277 	deprecated("Use the LIBRARY_* symbols defined for each package, instead of gtkd.paths.LIBRARY")
278 	public static string[] getLoadFailures(LIBRARY library)
279 	{
280 		return getLoadFailures(importLibs[library]);
281 	}
282 }
283 
284 // Platform specific implementation below.
285 
286 version(Windows)
287 {
288 	import core.sys.windows.winbase : LoadLibraryA, GetProcAddress, FreeLibrary, GetLastError, FormatMessageA,
289 				FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_ARGUMENT_ARRAY;
290 	import core.sys.windows.winnt : LANG_NEUTRAL, IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_I386;
291 
292 	extern(Windows)
293 	{
294 		int SetDllDirectoryA(const(char)* path);
295 	}
296 
297 	private void* pLoadLibrary(string libraryName)
298 	{
299 		setDllPath();
300 
301 		return LoadLibraryA(cast(char*)toStringz(libraryName));
302 	}
303 
304 	private void* pGetSymbol(void* handle, string symbol)
305 	{
306 		return GetProcAddress(handle, cast(char*)toStringz(symbol));
307 	}
308 
309 	private alias FreeLibrary pUnloadLibrary;
310 
311 	private string getErrorMessage()
312 	{
313 		char[] buffer = new char[2048];
314 		buffer[0] = '\0';
315 
316 		FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
317 		               null,
318 		               GetLastError(),
319 		               LANG_NEUTRAL,
320 		               buffer.ptr,
321 					   cast(uint)buffer.length,
322 					   cast(char**)["\0".ptr].ptr);
323 
324 		return buffer.ptr.fromStringz.idup;
325 	}
326 
327 	private void setDllPath()
328 	{
329 		static bool isSet;
330 
331 		if ( isSet )
332 			return;
333 
334 		string gtkPath = getGtkPath();
335 
336 		if ( gtkPath.length > 0 )
337 			SetDllDirectoryA((gtkPath~'\0').ptr);
338 
339 		isSet = true;
340 	}
341 
342 	private string getGtkPath()
343 	{
344 		import std.algorithm;
345 		import std.path;
346 		import std.process;
347 		import std.file;
348 
349 		foreach ( lib; ["libgtk-3-0.dll", "gtk-3-3.0.dll", "gtk-3.dll"] )
350 		{
351 			foreach (path; splitter(environment.get("PATH"), ';'))
352 			{
353 				string dllPath = buildNormalizedPath(path, lib);
354 
355 				if ( !exists(dllPath) )
356 					continue;
357 
358 				if ( checkArchitecture(dllPath) )
359 					return path;
360 			}
361 		}
362 
363 		return null;
364 	}
365 
366 	private bool checkArchitecture(string dllPath)
367 	{
368 		import std.stdio;
369 
370 		File dll = File(dllPath);
371 
372 		dll.seek(0x3c);
373 		int offset = dll.rawRead(new int[1])[0];
374 
375 		dll.seek(offset);
376 		uint peHead = dll.rawRead(new uint[1])[0];
377 
378 		//Not a PE Header.
379 		if( peHead != 0x00004550)
380 			return false;
381 
382 		ushort type = dll.rawRead(new ushort[1])[0];
383 
384 		version(Win32)
385 		{
386 			if ( type == IMAGE_FILE_MACHINE_I386 )
387 				return true;
388 		}
389 		else version(Win64)
390 		{
391 			if ( type == IMAGE_FILE_MACHINE_AMD64 )
392 				return true;
393 		}
394 
395 		return false;
396 	}
397 }
398 else
399 {
400 	import core.sys.posix.dlfcn : dlopen, dlerror, dlsym, dlclose, RTLD_NOW, RTLD_GLOBAL;
401 	import std.path : buildPath;
402 
403 	private string lastError;
404 
405 	version(OSX)
406 	{
407 		string basePath()
408 		{
409 			import std.process;
410 
411 			static string path;
412 
413 			if (path !is null)
414 				return path;
415 
416 			path = environment.get("GTK_BASEPATH");
417 			if(!path){
418 				path=environment.get("HOMEBREW_ROOT");
419 				if(path){
420 					path=path.buildPath("lib");
421 				}
422 			}
423 			return path;
424 		}
425 	}
426 	else
427 	{
428 		enum basePath = "";
429 	}
430 
431 	private void* pLoadLibrary(string libraryName, int flag = RTLD_NOW)
432 	{
433 		void* handle = dlopen(cast(char*)toStringz(basePath.buildPath(libraryName)), flag | RTLD_GLOBAL);
434 
435 		if(!handle){
436 			lastError = dlerror().fromStringz.idup;
437 		}
438 
439 		// clear the error buffer
440 		dlerror();
441 
442 		return handle;
443 	}
444 
445 	private void* pGetSymbol(void* libraryHandle, string symbol)
446 	{
447 		void* symbolHandle = dlsym(libraryHandle, cast(char*)toStringz(symbol));
448 
449 		// clear the error buffer
450 		dlerror();
451 
452 		return symbolHandle;
453 	}
454 
455 	private int pUnloadLibrary(void* libraryHandle)
456 	{
457 		return dlclose(libraryHandle);
458 	}
459 
460 	private string getErrorMessage()
461 	{
462 		scope(exit) lastError = null;
463 		return lastError;
464 	}
465 }