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