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