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 	deprecated("Use the LIBRARY_* symbols defined for each package, instead of gtkd.paths.LIBRARY")
46 	public static void link(T)(ref T funct, string symbol, LIBRARY[] libraries ...)
47 	{
48 		funct = cast(T)getSymbol(symbol, libraries);
49 	}
50 
51 	/*
52 	 * Links the provided symbol
53 	 * Params:
54 	 *     funct     = The function we are linking
55 	 *     symbol    = The name of the symbol to link
56 	 *     libraries = One or more libraries to search for the symbol
57 	 */
58 	public static void link(T)(ref T funct, string symbol, const string[] libraries ...)
59 	{
60 		funct = cast(T)getSymbol(symbol, libraries);
61 	}
62 
63 	/*
64 	 * Gets a simbol from one of the provided libraries
65 	 * Params:
66 	 *     symbol    = The name of the symbol to link
67 	 *     libraries = One or more libraries to search for the symbol
68 	 */
69 	deprecated("Use the LIBRARY_* symbols defined for each package, instead of gtkd.paths.LIBRARY")
70 	public static void* getSymbol(string symbol, LIBRARY[] libraries ...)
71 	{
72 		string[] libStr = new string[libraries.length];
73 
74 		foreach (i, library; libraries )
75 		{
76 			libStr[i] = importLibs[library];
77 		}
78 
79 		return getSymbol(symbol, libStr);
80 	}
81 
82 	/*
83 	 * Gets a simbol from one of the provided libraries
84 	 * Params:
85 	 *     symbol    = The name of the symbol to link
86 	 *     libraries = One or more libraries to search for the symbol
87 	 */
88 	public static void* getSymbol(string symbol, const string[] libraries ...)
89 	{
90 		void* handle;
91 
92 		foreach ( library; libraries )
93 		{
94 			if( !(library in loadedLibraries) )
95 				loadLibrary(library);
96 
97 			handle = pGetSymbol(loadedLibraries[library], symbol);
98 
99 			if ( handle !is null )
100 				break;
101 		}
102 
103 		if ( handle is null )
104 		{
105 			foreach ( library; libraries )
106 				loadFailures[library] ~= symbol;
107 
108 			handle = &unsupportedSymbol;
109 		}
110 
111 		return handle;
112 	}
113 
114 	/*
115 	 * Loads a library
116 	 */
117 	public static void loadLibrary(string library)
118 	{
119 		void* handle;
120 
121 		if ( library.canFind(';') )
122 		{
123 			foreach ( lib; library.split(';') )
124 			{
125 				handle = pLoadLibrary(lib);
126 				if ( handle )
127 					break;
128 			}
129 		}
130 		else
131 		{
132 			handle = pLoadLibrary(library);
133 		}
134 
135 		if ( handle is null )
136 			throw new Exception("Library load failed ("~ library ~"): "~ getErrorMessage());
137 
138 		loadedLibraries[library] = handle;
139 	}
140 
141 	/*
142 	 * Unload a library
143 	 */
144 	deprecated("Use the LIBRARY_* symbols defined for each package, instead of gtkd.paths.LIBRARY")
145 	public static void unloadLibrary(LIBRARY library)
146 	{
147 		unloadLibrary( importLibs[library] );
148 	}
149 
150 	/*
151 	 * Unload a library
152 	 */
153 	public static void unloadLibrary(string library)
154 	{
155 		pUnloadLibrary(loadedLibraries[library]);
156 
157 		loadedLibraries.remove(library);
158 	}
159 
160 	///Ditto
161 	public static void unloadLibrary(const string[] libraries)
162 	{
163 		foreach ( lib; libraries )
164 		{
165 			unloadLibrary(lib);
166 		}
167 	}
168 
169 	/**
170 	 * Checks if any symbol failed to load
171 	 * Returns: true if ALL symbols are loaded
172 	 */
173 	public static bool isPerfectLoad()
174 	{
175 		return loadFailures.keys.length == 0;
176 	}
177 
178 	/**
179 	 * Gets all libraries loaded.
180 	 * returns: An array with the loaded libraries
181 	 */
182 	public static string[] getLoadLibraries()
183 	{
184 		return loadedLibraries.keys;
185 	}
186 
187 	/**
188 	 * Print all libraries loaded.
189 	 */
190 	public static void dumpLoadLibraries()
191 	{
192 		foreach ( lib; getLoadLibraries() )
193 		{
194 			writefln("Loaded lib = %s", lib);
195 		}
196 	}
197 
198 	/**
199 	 * Checks if a library is loaded.
200 	 * Returns: true is the library was loaded sucsessfully.
201 	 */
202 	deprecated("Use the LIBRARY_* symbols defined for each package, instead of gtkd.paths.LIBRARY")
203 	public static bool isLoaded(LIBRARY library)
204 	{
205 		return isLoaded(importLibs[library]);
206 	}
207 
208 	/**
209 	 * Checks if a library is loaded.
210 	 * Returns: true is the library was loaded sucsessfully.
211 	 */
212 	public static bool isLoaded(string library)
213 	{
214 		if ( library in loadedLibraries )
215 			return true;
216 		else
217 			return false;
218 	}
219 
220 	///Ditto
221 	public static bool isLoaded(const string[] libraries)
222 	{
223 		return isLoaded(libraries[0]);
224 	}
225 
226 	/**
227 	 * Gets all the failed loads for a specific library.
228 	 * returns: An array of the names hat failed to load for a specific library
229 	 *          or null if none was found
230 	 */
231 	deprecated("Use the LIBRARY_* symbols defined for each package, instead of gtkd.paths.LIBRARY")
232 	public static string[] getLoadFailures(LIBRARY library)
233 	{
234 		return getLoadFailures(importLibs[library]);
235 	}
236 
237 	/**
238 	 * Gets all the failed loads for a specific library.
239 	 * returns: An array of the names hat failed to load for a specific library
240 	 *          or null if none was found
241 	 */
242 	public static string[] getLoadFailures(string library)
243 	{
244 		if ( library in loadFailures )
245 			return loadFailures[library];
246 		else
247 			return null;
248 	}
249 
250 	///Ditto.
251 	public static string[] getLoadFailures(const string[] libraries)
252 	{
253 		string[] failures;
254 
255 		foreach ( lib; libraries )
256 		{
257 			failures ~= getLoadFailures(lib);
258 		}
259 
260 		return failures;
261 	}
262 
263 	/**
264 	 * Print all symbols that failed to load
265 	 */
266 	public static void dumpFailedLoads()
267 	{
268 		foreach ( library; loadedLibraries.keys )
269 		{
270 			foreach ( symbol; getLoadFailures(library) )
271 			{
272 				writefln("failed (%s) %s", library, symbol);
273 			}
274 		}
275 	}
276 
277 	static ~this()
278 	{
279 		foreach ( library; loadedLibraries.keys )
280 			unloadLibrary(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 (path; splitter(environment.get("PATH"), ';'))
350 		{
351 			string dllPath = buildNormalizedPath(path, "libgtk-3-0.dll");
352 
353 			if ( !exists(dllPath) )
354 				continue;
355 
356 			if ( checkArchitecture(dllPath) )
357 				return path;
358 		}
359 
360 		return null;
361 	}
362 
363 	private bool checkArchitecture(string dllPath)
364 	{
365 		import std.stdio;
366 
367 		File dll = File(dllPath);
368 
369 		dll.seek(0x3c);
370 		int offset = dll.rawRead(new int[1])[0];
371 
372 		dll.seek(offset);
373 		uint peHead = dll.rawRead(new uint[1])[0];
374 
375 		//Not a PE Header.
376 		if( peHead != 0x00004550)
377 			return false;
378 
379 		ushort type = dll.rawRead(new ushort[1])[0];
380 
381 		version(Win32)
382 		{
383 			if ( type == IMAGE_FILE_MACHINE_I386 )
384 				return true;
385 		}
386 		else version(Win64)
387 		{
388 			if ( type == IMAGE_FILE_MACHINE_AMD64 )
389 				return true;
390 		}
391 
392 		return false;
393 	}
394 }
395 else
396 {
397 	import core.sys.posix.dlfcn : dlopen, dlerror, dlsym, dlclose, RTLD_NOW, RTLD_GLOBAL;
398 	import std.path : buildPath;
399 
400 	private string lastError;
401 
402 	version(OSX)
403 	{
404 		string basePath()
405 		{
406 			import std.process;
407 
408 			static string path;
409 
410 			if (path !is null)
411 				return path;
412 
413 			path = environment.get("GTK_BASEPATH");
414 			if(!path){
415 				path=environment.get("HOMEBREW_ROOT");
416 				if(path){
417 					path=path.buildPath("lib");
418 				}
419 			}
420 			return path;
421 		}
422 	}
423 	else
424 	{
425 		enum basePath = "";
426 	}
427 
428 	private void* pLoadLibrary(string libraryName, int flag = RTLD_NOW)
429 	{
430 		void* handle = dlopen(cast(char*)toStringz(basePath.buildPath(libraryName)), flag | RTLD_GLOBAL);
431 
432 		if(!handle){
433 			lastError = dlerror().fromStringz.idup;
434 		}
435 
436 		// clear the error buffer
437 		dlerror();
438 
439 		return handle;
440 	}
441 
442 	private void* pGetSymbol(void* libraryHandle, string symbol)
443 	{
444 		void* symbolHandle = dlsym(libraryHandle, cast(char*)toStringz(symbol));
445 
446 		// clear the error buffer
447 		dlerror();
448 
449 		return symbolHandle;
450 	}
451 
452 	private int pUnloadLibrary(void* libraryHandle)
453 	{
454 		return dlclose(libraryHandle);
455 	}
456 
457 	private string getErrorMessage()
458 	{
459 		scope(exit) lastError = null;
460 		return lastError;
461 	}
462 }