Jamoma API  0.6.0.a19
TTExtensionLoader.cpp
1 #include "TTExtensionLoader.h"
2 #include "TTFoundation.h"
3 #include "TTEnvironment.h"
4 #include <vector>
5 #include <string>
6 
7 // The organization here is as follows :
8 // 1. TTLoadExtensions is called and selects the paths
9 // where the extensions should be searched, in order.
10 // As soon as extensions are found in a folder, the search stops.
11 // 2. Platforms are defined as classes :
12 // - TTUnixCommon contains generic data for Unix-like systems
13 // - OS X, Linux, Android, Windows are the available platforms.
14 // 3. Each platform has a specific way to list what's in a folder :
15 // The relevant method is TTPlatform::TTLoadExtensionsFromFolder
16 // 4. The algorithm to load a file is generic : TTLoadExtension. The
17 // platform provides the algorithm with functions to get a handle.
18 
19 
20 using namespace std;
21 using TTStringVector = vector<string>;
22 
23 // Utility compile-time functions to work with string constants.
24 // Except on windows, where constexpr will only be in VS2015 (hopefully...)
25 // This would allow to shrink this file size by a small half.
26 #if !defined(TT_PLATFORM_WIN)
27 template <typename T, size_t n>
28 constexpr size_t array_length(const T (&)[n])
29 { return n; }
30 
31 template <typename T>
32 constexpr size_t string_length(T&& t)
33 { return array_length(forward<T&&>(t)) - 1; }
34 
35 template<typename T>
36 constexpr bool string_empty(T&& t)
37 { return string_length(forward<T&&>(t)) == 0; }
38 #endif
39 
40 template<typename OS>
41 // Returns true if the filename (ex. : AudioEngine.ttdylib)
42 // is a correct extension filename for the platform we're in.
43 bool TTIsExtensionFilename(const string& filename)
44 {
45  auto res = mismatch(begin(OS::extensionPrefix),
46  end(OS::extensionPrefix),
47  begin(filename));
48 
49  if (string_empty(OS::extensionPrefix) || res.first == (end(OS::extensionPrefix)))
50  {
51  if(filename.length() >= string_length(OS::extensionSuffix))
52  {
53  return (0 == filename.compare(
54  filename.length() - string_length(OS::extensionSuffix),
55  string_length(OS::extensionSuffix),
56  OS::extensionSuffix));
57  }
58  }
59 
60  return false;
61 }
62 
63 template<typename OS>
64 // Gets the extension name from the file name.
65 // ex. : libWebSocket.so on Android -> WebSocket
66 string TTFilenameToExtensionName(string name)
67 {
68  // Remove the prefix
69  if(!string_empty(OS::extensionPrefix))
70  name.erase(begin(name),
71  begin(name) + string_length(OS::extensionPrefix));
72 
73  // Remove the suffix
74  if(!string_empty(OS::extensionSuffix))
75  name.erase(end(name) - string_length(OS::extensionSuffix),
76  end(name));
77 
78  return name;
79 }
80 
81 template<typename OS, typename Loader, typename GetProc>
82 // A generic way to load classes.
83 // Loader : a callable object that takes a filename of a shared object,
84 // and returns a handle.
85 // GetProc : a callable object that takes a handle and a function name,
86 // and returns a pointer to the function.
87 bool TTLoadExtension(const string& filename,
88  const string& folder,
89  Loader&& handle_fun,
90  GetProc&& getproc_fun)
91 {
92  // Check if the file is a Jamoma extension
93  if(!TTIsExtensionFilename<OS>(filename))
94  return false;
95 
96  // Get a handle
97  void *handle = handle_fun((folder + "/" + filename).c_str());
98  if (!handle)
99  {
100  TTLogMessage("Error when trying to get an handle on %s.\n", filename.c_str());
101  return false;
102  }
103 
104  // Load the Jamoma extension
105  string initFun = "TTLoadJamomaExtension_" + TTFilenameToExtensionName<OS>(filename);
106  auto initializer = reinterpret_cast<TTExtensionInitializationMethod>(getproc_fun(handle, initFun.c_str()));
107  if (initializer)
108  {
109  auto err = initializer();
110  if(err != kTTErrNone)
111  {
112  TTLogMessage("Error when initializing extension %s.\n", filename.c_str());
113  return false;
114  }
115  return true;
116  }
117  return false;
118 }
119 
120 // Here is the platform-specific code.
121 // It is organized in the following fashion :
122 //
123 // * Each platform has an extension prefix (e.g. "lib", required on Android) and suffix (e.g. ".ttdylib").
124 // * Platforms have these paths defined (they will be evaluated in this order) :
125 // * A computed relative path (the path of the JamomaFoundation shared object)
126 // * Built-in relative paths (e.g. in an app bundle on OS X)
127 // * Built-in absolute paths (standard paths, e.g. "/usr/local/jamoma"...
128 // and a compiled-in absolute path (to allow package maintainers to add their own paths)
129 // * Common code for Unix-like platforms is abstracted in TTUnixCommon.
130 #if defined(TT_PLATFORM_MAC) || defined(TT_PLATFORM_LINUX)
131 #include <dlfcn.h>
132 #include <dirent.h>
133 
134 // This class contains informations that are applicable to
135 // UNIX-like systems (i.e. they respect the Filesystem Hierarchy Standard
136 // and load shared objects using dlopen / dlsym).
137 class TTUnixCommon
138 {
139  public:
140  static TTStringVector builtinAbsolutePaths()
141  {
142  return {
143 #if defined(JAMOMA_EXTENSIONS_INSTALL_PREFIX)
144  JAMOMA_EXTENSIONS_INSTALL_PREFIX,
145 #endif
146  "/usr/lib/jamoma",
147  "/usr/local/lib/jamoma",
148  "/usr/jamoma/lib",
149  "/usr/jamoma/extensions",
150  "/usr/local/jamoma/lib",
151  "/usr/local/jamoma/extensions"
152  };
153  }
154 
155  template<typename OS>
156  // Try to load extensions. Returns "true" only if at least one extension was loaded.
157  static bool TTLoadExtensionsFromFolder(const string& folder)
158  {
159  DIR* dirp = opendir(folder.c_str());
160  if(!dirp)
161  return false;
162 
163  dirent* dp{};
164  int count = 0; // Number of extensions loaded.
165  while ((dp = readdir(dirp)))
166  {
167  if(TTLoadExtension<OS>(dp->d_name,
168  folder,
169  [] (const char * file)
170  { return dlopen(file, RTLD_LAZY); },
171  [] (void* handle, const char * fun)
172  { return dlsym(handle, fun); }))
173  {
174  ++count;
175  }
176  }
177 
178  closedir(dirp);
179 
180  if(count > 0)
181  {
182  TTFoundationBinaryPath = folder.c_str();
183  return true;
184  }
185 
186  return false;
187  }
188 
189  // Returns the path of the extensions
190  // relative to the folder of the JamomaFoundation library
191  // the application uses.
192  static string computedRelativePath()
193  {
194  Dl_info info;
195  char mainBundleStr[4096];
196 
197  // Use the path of JamomaFoundation
198  if (dladdr((const void*)TTLoadExtensions, &info))
199  {
200  char *c = 0;
201 
202  TTLogMessage("computedRelativePath(): %s\n", info.dli_fname);
203 
204  strncpy(mainBundleStr, info.dli_fname, 4096);
205  c = strrchr(mainBundleStr, '/');
206  if (c)
207  *c = 0; // chop the "/JamomaFoundation.dylib/so off of the path
208  }
209 
210  return mainBundleStr;
211  }
212 };
213 #endif
214 
215 #if defined(TT_PLATFORM_MAC)
216 class TTOSXSpecific
217 {
218  public:
219  static constexpr const char extensionPrefix[]{""};
220  static constexpr const char extensionSuffix[]{".ttdylib"};
221 
222  static string computedRelativePath()
223  { return TTUnixCommon::computedRelativePath(); }
224 
225  static TTStringVector builtinRelativePaths()
226  { return {"../Frameworks/jamoma/extensions"}; }
227 
228  static TTStringVector builtinAbsolutePaths()
229  { return TTUnixCommon::builtinAbsolutePaths(); }
230 
231  static bool TTLoadExtensionsFromFolder(const string& folderName)
232  { return TTUnixCommon::TTLoadExtensionsFromFolder<TTOSXSpecific>(folderName); }
233 };
234 using TTOperatingSystem = TTOSXSpecific;
235 #endif
236 
237 #if defined(TT_PLATFORM_IOS)
238 class TTiOSSpecific
239 {
240 public:
241  static constexpr const char extensionPrefix[]{""};
242  static constexpr const char extensionSuffix[]{".ttdylib"};
243 
244  static string computedRelativePath()
245  { return ""; }
246 
247  static TTStringVector builtinRelativePaths()
248  { return {}; }
249 
250  static TTStringVector builtinAbsolutePaths()
251  { return {}; }
252 
253  static bool TTLoadExtensionsFromFolder(const string& folderName)
254  { return false; }
255 };
256 using TTOperatingSystem = TTiOSSpecific;
257 #endif
258 
259 
260 #if defined(TT_PLATFORM_LINUX) && defined(__ANDROID_API__)
261 #include <unistd.h>
262 #include <iostream>
263 
264 class TTAndroidSpecific
265 {
266  public:
267  static constexpr const char extensionPrefix[]{"lib"};
268  static constexpr const char extensionSuffix[]{".so"};
269 
270  static string computedRelativePath()
271  {
272  string s{"/proc/" + string{getpid()} + "/cmdline"};
273  ifstream input{s.c_str()};
274  string line;
275  getline(input, line);
276  return string{"/data/data/"} + line + string{"/lib"};
277  }
278 
279  static TTStringVector builtinRelativePaths()
280  { return {}; }
281 
282  static TTStringVector builtinAbsolutePaths()
283  { return {}; }
284 
285  static bool TTLoadExtensionsFromFolder(const string& folderName)
286  { return TTUnixCommon::TTLoadExtensionsFromFolder<TTAndroidSpecific>(folderName); }
287 };
288 using TTOperatingSystem = TTAndroidSpecific;
289 #endif
290 
291 
292 #if defined(TT_PLATFORM_LINUX)
293 class TTLinuxSpecific
294 {
295  public:
296  static constexpr const char extensionPrefix[]="";
297  static constexpr const char extensionSuffix[]=".ttso";
298 
299  static string computedRelativePath()
300  { return TTUnixCommon::computedRelativePath(); }
301 
302  static TTStringVector builtinRelativePaths()
303  { return {"./extensions"}; }
304 
305  static TTStringVector builtinAbsolutePaths()
306  { return TTUnixCommon::builtinAbsolutePaths(); }
307 
308  static bool TTLoadExtensionsFromFolder(const string& folderName)
309  { return TTUnixCommon::TTLoadExtensionsFromFolder<TTLinuxSpecific>(folderName); }
310 };
311 using TTOperatingSystem = TTLinuxSpecific;
312 #endif
313 
314 
315 #if defined(TT_PLATFORM_WIN)
316 #include <ShlObj.h>
317 
318 class TTWinSpecific
319 {
320  public:
321  static const char* extensionPrefix;
322  static const char* extensionSuffix;
323 
324  static string computedRelativePath();
325  static TTStringVector builtinRelativePaths();
326  static TTStringVector builtinAbsolutePaths();
327  static bool TTLoadExtensionsFromFolder(const string& folder);
328 };
329 
330 // Specializations since windows does not support constexpr yet.
331 template<>
332 string TTFilenameToExtensionName<TTWinSpecific>(string name)
333 {
334  name.erase(end(name) - strlen(TTWinSpecific::extensionSuffix), end(name));
335  return name;
336 }
337 
338 template<>
339 bool TTIsExtensionFilename<TTWinSpecific>(const string& filename)
340 {
341  auto suffix_len = strlen(TTWinSpecific::extensionSuffix);
342  if(filename.length() >= suffix_len)
343  {
344  return (0 == filename.compare(
345  filename.length() - suffix_len,
346  suffix_len,
347  TTWinSpecific::extensionSuffix));
348  }
349 
350  return false;
351 }
352 
353 string TTWinSpecific::computedRelativePath()
354 {
355  TTString fullpath{};
356  char temppath[4096];
357  LONG lRes;
358 
359  LPCSTR moduleName = "JamomaFoundation.dll";
360  HMODULE hmodule = GetModuleHandle(moduleName);
361  // get the path
362  GetModuleFileName(hmodule, (LPSTR)temppath, 4096);
363 
364  if (!FAILED(hmodule) && temppath[0])
365  {
366  fullpath = temppath;
367 
368  // get support folder path
369  fullpath = fullpath.substr(0, fullpath.length() - (strlen(moduleName) + 1));
370  lRes = SHCreateDirectory(NULL, (LPCWSTR)fullpath.c_str());
371  }
372 
373  return fullpath.c_str();
374 }
375 
376 TTStringVector TTWinSpecific::builtinRelativePaths()
377 {
378  return {"./extensions"};
379 }
380 
381 TTStringVector TTWinSpecific::builtinAbsolutePaths()
382 {
383  return {
384 #if defined(JAMOMA_EXTENSIONS_INSTALL_PREFIX)
385  JAMOMA_EXTENSIONS_INSTALL_PREFIX,
386 #endif
387  "c:\\Program Files (x86)\\Jamoma\\extensions"
388  };
389 }
390 
391 bool TTWinSpecific::TTLoadExtensionsFromFolder(const string& folder)
392 {
393  auto windowsPathSpec = folder
394  + "/*"
395  + string{TTWinSpecific::extensionSuffix};
396  WIN32_FIND_DATA FindFileData;
397  HANDLE hFind = FindFirstFile(windowsPathSpec.c_str(), &FindFileData);
398 
399  int count = 0; // Number of extensions loaded.
400  if (hFind == INVALID_HANDLE_VALUE)
401  return false;
402 
403  do {
404  if(TTLoadExtension<TTWinSpecific>(
405  FindFileData.cFileName,
406  folder,
407  [] (const char * file)
408  { return LoadLibrary(file); },
409  [] (void* handle, const char * fun)
410  { return GetProcAddress((HMODULE) handle, fun); }))
411  {
412  ++count;
413  }
414 
415  } while (FindNextFile(hFind, &FindFileData));
416  FindClose(hFind);
417 
418  if(count > 0)
419  {
420  TTFoundationBinaryPath = folder.c_str();
421  return true;
422  }
423 
424  return false;
425 }
426 
427 using TTOperatingSystem = TTWinSpecific;
428 #endif
429 
430 // Because these members are static they have to be allocated in this compilation unit.
431 #if defined(TT_PLATFORM_WIN)
432 const char* TTOperatingSystem::extensionPrefix{""};
433 const char* TTOperatingSystem::extensionSuffix{".ttdll"};
434 #else
435 constexpr const char TTOperatingSystem::extensionPrefix[array_length(TTOperatingSystem::extensionPrefix)];
436 constexpr const char TTOperatingSystem::extensionSuffix[array_length(TTOperatingSystem::extensionSuffix)];
437 #endif
438 
439 template<typename OS>
440 // Try to load Jamoma classes from a vector of paths.
441 // This will return on the first successful folder.
442 bool TTLoadExtensionsFromPaths(TTStringVector&& v)
443 {
444  return find_if(begin(v), end(v), [] (const string& path)
445  { return OS::TTLoadExtensionsFromFolder(path); }) != end(v);
446 }
447 
448 template<typename OS>
449 // Try to load Jamoma classes from a path computed at runtime.
450 bool TTLoadExtensionsFromComputedPaths()
451 {
452  auto computedPath = TTOperatingSystem::computedRelativePath();
453  return (!computedPath.empty()
454  && TTOperatingSystem::TTLoadExtensionsFromFolder(computedPath));
455 }
456 
457 template<typename OS>
458 // Try to load Jamoma classes from the paths built-in for the
459 // platform.
460 bool TTLoadExtensionsFromBuiltinPaths()
461 {
462  return TTLoadExtensionsFromPaths<OS>(OS::builtinRelativePaths()) ||
463  TTLoadExtensionsFromPaths<OS>(OS::builtinAbsolutePaths());
464 }
465 
466 void TTLoadExtensions(const char* pathToBinaries, bool loadFromOtherPaths)
467 {
468  if(!pathToBinaries)
469  {
470  if(!TTLoadExtensionsFromComputedPaths<TTOperatingSystem>() && loadFromOtherPaths)
471  {
472  TTLoadExtensionsFromBuiltinPaths<TTOperatingSystem>();
473  }
474  }
475  else
476  {
477  if(!TTOperatingSystem::TTLoadExtensionsFromFolder(pathToBinaries) && loadFromOtherPaths)
478  {
479  if(!TTLoadExtensionsFromComputedPaths<TTOperatingSystem>())
480  {
481  TTLoadExtensionsFromBuiltinPaths<TTOperatingSystem>();
482  }
483  }
484  }
485 
486  if(TTFoundationBinaryPath == "")
487  {
488  TTLogMessage("Warning: no classes were loaded.");
489  }
490 }
const char * c_str() const
Return a pointer to the internal C-string.
Definition: TTString.h:83
STL namespace.
TTEnvironment is a global object providing information on the environemt.
void TTFOUNDATION_EXPORT TTLogMessage(TTImmutableCString message,...)
Platform and host independent method for posting log messages.
Definition: TTBase.cpp:534
No Error.
Definition: TTBase.h:343
The TTString class is used to represent a string.
Definition: TTString.h:34
TTErr(* TTExtensionInitializationMethod)()
A function pointer for an instance creation function required to be provided by all classes...
Definition: TTEnvironment.h:34