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 	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 	import core.sys.windows.winbase : LoadLibraryA, GetProcAddress, FreeLibrary, GetLastError, FormatMessageA,
245 				FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_ARGUMENT_ARRAY;
246 	import core.sys.windows.winnt : LANG_NEUTRAL, IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_I386;
247 
248 	extern(Windows)
249 	{
250 		int SetDllDirectoryA(const(char)* path);
251 	}
252 
253 	private void* pLoadLibrary(string libraryName)
254 	{
255 		setDllPath();
256 
257 		return LoadLibraryA(cast(char*)toStringz(libraryName));
258 	}
259 
260 	private void* pGetSymbol(void* handle, string symbol)
261 	{
262 		return GetProcAddress(handle, cast(char*)toStringz(symbol));
263 	}
264 
265 	private alias FreeLibrary pUnloadLibrary;
266 
267 	private string getErrorMessage()
268 	{
269 		char[] buffer = new char[2048];
270 		buffer[0] = '\0';
271 
272 		FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
273 		               null,
274 		               GetLastError(),
275 		               LANG_NEUTRAL,
276 		               buffer.ptr,
277 					   cast(uint)buffer.length,
278 					   cast(char**)["\0".ptr].ptr);
279 
280 		return buffer.ptr.fromStringz.idup;
281 	}
282 
283 	private void setDllPath()
284 	{
285 		static bool isSet;
286 
287 		if ( isSet )
288 			return;
289 
290 		string gtkPath = getGtkPath();
291 
292 		if ( gtkPath.length > 0 )
293 			SetDllDirectoryA((gtkPath~'\0').ptr);
294 
295 		isSet = true;
296 	}
297 
298 	private string getGtkPath()
299 	{
300 		import std.algorithm;
301 		import std.path;
302 		import std.process;
303 		import std.file;
304 
305 		foreach (path; splitter(environment.get("PATH"), ';'))
306 		{
307 			string dllPath = buildNormalizedPath(path, "libgtk-3-0.dll");
308 
309 			if ( !exists(dllPath) )
310 				continue;
311 
312 			if ( checkArchitecture(dllPath) )
313 				return path;
314 		}
315 
316 		return null;
317 	}
318 
319 	private bool checkArchitecture(string dllPath)
320 	{
321 		import std.stdio;
322 
323 		File dll = File(dllPath);
324 
325 		dll.seek(0x3c);
326 		int offset = dll.rawRead(new int[1])[0];
327 
328 		dll.seek(offset);
329 		uint peHead = dll.rawRead(new uint[1])[0];
330 
331 		//Not a PE Header.
332 		if( peHead != 0x00004550)
333 			return false;
334 
335 		ushort type = dll.rawRead(new ushort[1])[0];
336 
337 		version(Win32)
338 		{
339 			if ( type == IMAGE_FILE_MACHINE_I386 )
340 				return true;
341 		}
342 		else version(Win64)
343 		{
344 			if ( type == IMAGE_FILE_MACHINE_AMD64 )
345 				return true;
346 		}
347 
348 		return false;
349 	}
350 }
351 else
352 {
353 	import core.sys.posix.dlfcn : dlopen, dlerror, dlsym, dlclose, RTLD_NOW, RTLD_GLOBAL;
354 	import std.path : buildPath;
355 
356 	private string lastError;
357 
358 	version(OSX)
359 	{
360 		string basePath()
361 		{
362 			import std.process;
363 
364 			static string path;
365 
366 			if (path !is null)
367 				return path;
368 
369 			path = environment.get("GTK_BASEPATH");
370 			if(!path){
371 				path=environment.get("HOMEBREW_ROOT");
372 				if(path){
373 					path=path.buildPath("lib");
374 				}
375 			}
376 			return path;
377 		}
378 	}
379 	else
380 	{
381 		enum basePath = "";
382 	}
383 
384 	private void* pLoadLibrary(string libraryName, int flag = RTLD_NOW)
385 	{
386 		void* handle = dlopen(cast(char*)toStringz(basePath.buildPath(libraryName)), flag | RTLD_GLOBAL);
387 
388 		if(!handle){
389 			lastError = dlerror().fromStringz.idup;
390 		}
391 
392 		// clear the error buffer
393 		dlerror();
394 
395 		return handle;
396 	}
397 
398 	private void* pGetSymbol(void* libraryHandle, string symbol)
399 	{
400 		void* symbolHandle = dlsym(libraryHandle, cast(char*)toStringz(symbol));
401 
402 		// clear the error buffer
403 		dlerror();
404 
405 		return symbolHandle;
406 	}
407 
408 	private int pUnloadLibrary(void* libraryHandle)
409 	{
410 		return dlclose(libraryHandle);
411 	}
412 
413 	private string getErrorMessage()
414 	{
415 		scope(exit) lastError = null;
416 		return lastError;
417 	}
418 }