annotate vamp-sdk/hostext/PluginLoader.cpp @ 59:fa79c4ec847d host-factory-stuff

* Put hostext stuff in the HostExt sub-namespace * Tidy up system-specific stuff in PluginLoader * Make PluginLoader return a deletion-notifying wrapper which permits the library to be unloaded when no longer in use * Add PluginChannelAdapter * Make vamp-simple-host use PluginChannelAdapter, and use the PluginLoader for plugin-running task. Also some other enhancements to host
author cannam
date Thu, 24 May 2007 15:17:07 +0000
parents 0284955e31e5
children 97c5ac99d725
rev   line source
cannam@56 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
cannam@56 2
cannam@56 3 /*
cannam@56 4 Vamp
cannam@56 5
cannam@56 6 An API for audio analysis and feature extraction plugins.
cannam@56 7
cannam@56 8 Centre for Digital Music, Queen Mary, University of London.
cannam@56 9 Copyright 2006 Chris Cannam.
cannam@56 10
cannam@56 11 Permission is hereby granted, free of charge, to any person
cannam@56 12 obtaining a copy of this software and associated documentation
cannam@56 13 files (the "Software"), to deal in the Software without
cannam@56 14 restriction, including without limitation the rights to use, copy,
cannam@56 15 modify, merge, publish, distribute, sublicense, and/or sell copies
cannam@56 16 of the Software, and to permit persons to whom the Software is
cannam@56 17 furnished to do so, subject to the following conditions:
cannam@56 18
cannam@56 19 The above copyright notice and this permission notice shall be
cannam@56 20 included in all copies or substantial portions of the Software.
cannam@56 21
cannam@56 22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
cannam@56 23 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
cannam@56 24 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
cannam@56 25 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
cannam@56 26 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
cannam@56 27 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
cannam@56 28 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
cannam@56 29
cannam@56 30 Except as contained in this notice, the names of the Centre for
cannam@56 31 Digital Music; Queen Mary, University of London; and Chris Cannam
cannam@56 32 shall not be used in advertising or otherwise to promote the sale,
cannam@56 33 use or other dealings in this Software without prior written
cannam@56 34 authorization.
cannam@56 35 */
cannam@56 36
cannam@59 37 #include "vamp-sdk/PluginHostAdapter.h"
cannam@56 38 #include "PluginLoader.h"
cannam@56 39
cannam@57 40 #include <fstream>
cannam@57 41
cannam@59 42 #ifdef _WIN32
cannam@59 43
cannam@59 44 #include <windows.h>
cannam@59 45 #include <tchar.h>
cannam@59 46 #define PLUGIN_SUFFIX "dll"
cannam@59 47
cannam@59 48 #else /* ! _WIN32 */
cannam@59 49
cannam@59 50 #include <dirent.h>
cannam@59 51 #include <dlfcn.h>
cannam@59 52
cannam@59 53 #ifdef __APPLE__
cannam@59 54 #define PLUGIN_SUFFIX "dylib"
cannam@59 55 #else /* ! __APPLE__ */
cannam@59 56 #define PLUGIN_SUFFIX "so"
cannam@59 57 #endif /* ! __APPLE__ */
cannam@59 58
cannam@59 59 #endif /* ! _WIN32 */
cannam@56 60
cannam@57 61 using namespace std;
cannam@57 62
cannam@56 63 namespace Vamp {
cannam@56 64
cannam@59 65 namespace HostExt {
cannam@59 66
cannam@59 67 PluginLoader *
cannam@59 68 PluginLoader::m_instance = 0;
cannam@59 69
cannam@56 70 PluginLoader::PluginLoader()
cannam@56 71 {
cannam@56 72 }
cannam@56 73
cannam@56 74 PluginLoader::~PluginLoader()
cannam@56 75 {
cannam@56 76 }
cannam@56 77
cannam@59 78 PluginLoader *
cannam@59 79 PluginLoader::getInstance()
cannam@59 80 {
cannam@59 81 if (!m_instance) m_instance = new PluginLoader();
cannam@59 82 return m_instance;
cannam@59 83 }
cannam@59 84
cannam@57 85 vector<PluginLoader::PluginKey>
cannam@56 86 PluginLoader::listPlugins()
cannam@56 87 {
cannam@59 88 if (m_pluginLibraryNameMap.empty()) generateLibraryMap();
cannam@56 89
cannam@57 90 vector<PluginKey> plugins;
cannam@57 91 for (map<PluginKey, string>::iterator mi =
cannam@59 92 m_pluginLibraryNameMap.begin();
cannam@59 93 mi != m_pluginLibraryNameMap.end(); ++mi) {
cannam@56 94 plugins.push_back(mi->first);
cannam@56 95 }
cannam@56 96
cannam@56 97 return plugins;
cannam@56 98 }
cannam@56 99
cannam@59 100 void
cannam@59 101 PluginLoader::generateLibraryMap()
cannam@59 102 {
cannam@59 103 vector<string> path = PluginHostAdapter::getPluginPath();
cannam@59 104
cannam@59 105 for (size_t i = 0; i < path.size(); ++i) {
cannam@59 106
cannam@59 107 vector<string> files = listFiles(path[i], PLUGIN_SUFFIX);
cannam@59 108
cannam@59 109 for (vector<string>::iterator fi = files.begin();
cannam@59 110 fi != files.end(); ++fi) {
cannam@59 111
cannam@59 112 string fullPath = path[i];
cannam@59 113 fullPath = splicePath(fullPath, *fi);
cannam@59 114 void *handle = loadLibrary(fullPath);
cannam@59 115 if (!handle) continue;
cannam@59 116
cannam@59 117 VampGetPluginDescriptorFunction fn =
cannam@59 118 (VampGetPluginDescriptorFunction)lookupInLibrary
cannam@59 119 (handle, "vampGetPluginDescriptor");
cannam@59 120
cannam@59 121 if (!fn) {
cannam@59 122 unloadLibrary(handle);
cannam@59 123 continue;
cannam@59 124 }
cannam@59 125
cannam@59 126 int index = 0;
cannam@59 127 const VampPluginDescriptor *descriptor = 0;
cannam@59 128
cannam@59 129 while ((descriptor = fn(VAMP_API_VERSION, index))) {
cannam@59 130 PluginKey key = composePluginKey(*fi, descriptor->identifier);
cannam@59 131 if (m_pluginLibraryNameMap.find(key) ==
cannam@59 132 m_pluginLibraryNameMap.end()) {
cannam@59 133 m_pluginLibraryNameMap[key] = fullPath;
cannam@59 134 }
cannam@59 135 ++index;
cannam@59 136 }
cannam@59 137
cannam@59 138 unloadLibrary(handle);
cannam@59 139 }
cannam@59 140 }
cannam@59 141 }
cannam@59 142
cannam@59 143 PluginLoader::PluginKey
cannam@59 144 PluginLoader::composePluginKey(string libraryName, string identifier)
cannam@59 145 {
cannam@59 146 string basename = libraryName;
cannam@59 147
cannam@59 148 string::size_type li = basename.rfind('/');
cannam@59 149 if (li != string::npos) basename = basename.substr(li + 1);
cannam@59 150
cannam@59 151 li = basename.find('.');
cannam@59 152 if (li != string::npos) basename = basename.substr(0, li);
cannam@59 153
cannam@59 154 return basename + ":" + identifier;
cannam@59 155 }
cannam@59 156
cannam@57 157 PluginLoader::PluginCategoryHierarchy
cannam@57 158 PluginLoader::getPluginCategory(PluginKey plugin)
cannam@57 159 {
cannam@57 160 if (m_taxonomy.empty()) generateTaxonomy();
cannam@57 161 if (m_taxonomy.find(plugin) == m_taxonomy.end()) return PluginCategoryHierarchy();
cannam@57 162 return m_taxonomy[plugin];
cannam@57 163 }
cannam@57 164
cannam@57 165 string
cannam@57 166 PluginLoader::getLibraryPathForPlugin(PluginKey plugin)
cannam@56 167 {
cannam@59 168 if (m_pluginLibraryNameMap.empty()) generateLibraryMap();
cannam@59 169 if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) return "";
cannam@59 170 return m_pluginLibraryNameMap[plugin];
cannam@56 171 }
cannam@56 172
cannam@56 173 Plugin *
cannam@59 174 PluginLoader::loadPlugin(PluginKey key, float inputSampleRate)
cannam@56 175 {
cannam@57 176 string fullPath = getLibraryPathForPlugin(key);
cannam@56 177 if (fullPath == "") return 0;
cannam@56 178
cannam@57 179 string::size_type ki = key.find(':');
cannam@57 180 if (ki == string::npos) {
cannam@56 181 //!!! flag error
cannam@56 182 return 0;
cannam@56 183 }
cannam@56 184
cannam@57 185 string identifier = key.substr(ki + 1);
cannam@56 186
cannam@59 187 void *handle = loadLibrary(fullPath);
cannam@59 188 if (!handle) return 0;
cannam@56 189
cannam@56 190 VampGetPluginDescriptorFunction fn =
cannam@59 191 (VampGetPluginDescriptorFunction)lookupInLibrary
cannam@56 192 (handle, "vampGetPluginDescriptor");
cannam@56 193
cannam@56 194 if (!fn) {
cannam@59 195 unloadLibrary(handle);
cannam@56 196 return 0;
cannam@56 197 }
cannam@56 198
cannam@56 199 int index = 0;
cannam@56 200 const VampPluginDescriptor *descriptor = 0;
cannam@56 201
cannam@56 202 while ((descriptor = fn(VAMP_API_VERSION, index))) {
cannam@59 203
cannam@57 204 if (string(descriptor->identifier) == identifier) {
cannam@59 205
cannam@59 206 Vamp::PluginHostAdapter *plugin =
cannam@59 207 new Vamp::PluginHostAdapter(descriptor, inputSampleRate);
cannam@59 208
cannam@59 209 PluginDeletionNotifyAdapter *adapter =
cannam@59 210 new PluginDeletionNotifyAdapter(plugin, this);
cannam@59 211
cannam@59 212 m_pluginLibraryHandleMap[adapter] = handle;
cannam@59 213 return adapter;
cannam@56 214 }
cannam@59 215
cannam@56 216 ++index;
cannam@56 217 }
cannam@59 218
cannam@59 219 cerr << "Vamp::HostExt::PluginLoader: Plugin \""
cannam@59 220 << identifier << "\" not found in library \""
cannam@59 221 << fullPath << "\"" << endl;
cannam@59 222
cannam@56 223 return 0;
cannam@56 224 }
cannam@56 225
cannam@57 226 void
cannam@57 227 PluginLoader::generateTaxonomy()
cannam@57 228 {
cannam@57 229 // cerr << "PluginLoader::generateTaxonomy" << endl;
cannam@57 230
cannam@57 231 vector<string> path = PluginHostAdapter::getPluginPath();
cannam@57 232 string libfragment = "/lib/";
cannam@57 233 vector<string> catpath;
cannam@57 234
cannam@57 235 string suffix = "cat";
cannam@57 236
cannam@57 237 for (vector<string>::iterator i = path.begin();
cannam@57 238 i != path.end(); ++i) {
cannam@59 239
cannam@59 240 // It doesn't matter that we're using literal forward-slash in
cannam@59 241 // this bit, as it's only relevant if the path contains
cannam@59 242 // "/lib/", which is only meaningful and only plausible on
cannam@59 243 // systems with forward-slash delimiters
cannam@57 244
cannam@57 245 string dir = *i;
cannam@57 246 string::size_type li = dir.find(libfragment);
cannam@57 247
cannam@57 248 if (li != string::npos) {
cannam@57 249 catpath.push_back
cannam@57 250 (dir.substr(0, li)
cannam@57 251 + "/share/"
cannam@57 252 + dir.substr(li + libfragment.length()));
cannam@57 253 }
cannam@57 254
cannam@57 255 catpath.push_back(dir);
cannam@57 256 }
cannam@57 257
cannam@57 258 char buffer[1024];
cannam@57 259
cannam@57 260 for (vector<string>::iterator i = catpath.begin();
cannam@57 261 i != catpath.end(); ++i) {
cannam@57 262
cannam@59 263 vector<string> files = listFiles(*i, suffix);
cannam@57 264
cannam@57 265 for (vector<string>::iterator fi = files.begin();
cannam@57 266 fi != files.end(); ++fi) {
cannam@57 267
cannam@59 268 string filepath = splicePath(*i, *fi);
cannam@57 269 ifstream is(filepath.c_str(), ifstream::in | ifstream::binary);
cannam@57 270
cannam@57 271 if (is.fail()) {
cannam@57 272 // cerr << "failed to open: " << filepath << endl;
cannam@57 273 continue;
cannam@57 274 }
cannam@57 275
cannam@57 276 // cerr << "opened: " << filepath << endl;
cannam@57 277
cannam@57 278 while (!!is.getline(buffer, 1024)) {
cannam@57 279
cannam@57 280 string line(buffer);
cannam@57 281
cannam@57 282 // cerr << "line = " << line << endl;
cannam@57 283
cannam@57 284 string::size_type di = line.find("::");
cannam@57 285 if (di == string::npos) continue;
cannam@57 286
cannam@57 287 string id = line.substr(0, di);
cannam@57 288 string encodedCat = line.substr(di + 2);
cannam@57 289
cannam@57 290 if (id.substr(0, 5) != "vamp:") continue;
cannam@57 291 id = id.substr(5);
cannam@57 292
cannam@57 293 while (encodedCat.length() >= 1 &&
cannam@57 294 encodedCat[encodedCat.length()-1] == '\r') {
cannam@57 295 encodedCat = encodedCat.substr(0, encodedCat.length()-1);
cannam@57 296 }
cannam@57 297
cannam@57 298 // cerr << "id = " << id << ", cat = " << encodedCat << endl;
cannam@57 299
cannam@57 300 PluginCategoryHierarchy category;
cannam@57 301 string::size_type ai;
cannam@57 302 while ((ai = encodedCat.find(" > ")) != string::npos) {
cannam@57 303 category.push_back(encodedCat.substr(0, ai));
cannam@57 304 encodedCat = encodedCat.substr(ai + 3);
cannam@57 305 }
cannam@57 306 if (encodedCat != "") category.push_back(encodedCat);
cannam@57 307
cannam@57 308 m_taxonomy[id] = category;
cannam@57 309 }
cannam@57 310 }
cannam@57 311 }
cannam@57 312 }
cannam@57 313
cannam@59 314 void *
cannam@59 315 PluginLoader::loadLibrary(string path)
cannam@59 316 {
cannam@59 317 void *handle = 0;
cannam@59 318 #ifdef _WIN32
cannam@59 319 handle = LoadLibrary(path.c_str());
cannam@59 320 if (!handle) {
cannam@59 321 cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
cannam@59 322 << path << "\"" << endl;
cannam@59 323 }
cannam@59 324 #else
cannam@59 325 handle = dlopen(path.c_str(), RTLD_LAZY);
cannam@59 326 if (!handle) {
cannam@59 327 cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
cannam@59 328 << path << "\": " << dlerror() << endl;
cannam@59 329 }
cannam@59 330 #endif
cannam@59 331 return handle;
cannam@59 332 }
cannam@59 333
cannam@59 334 void
cannam@59 335 PluginLoader::unloadLibrary(void *handle)
cannam@59 336 {
cannam@59 337 #ifdef _WIN32
cannam@59 338 FreeLibrary((HINSTANCE)handle);
cannam@59 339 #else
cannam@59 340 dlclose(handle);
cannam@59 341 #endif
cannam@59 342 }
cannam@59 343
cannam@59 344 void *
cannam@59 345 PluginLoader::lookupInLibrary(void *handle, const char *symbol)
cannam@59 346 {
cannam@59 347 #ifdef _WIN32
cannam@59 348 return (void *)GetProcAddress((HINSTANCE)handle, symbol);
cannam@59 349 #else
cannam@59 350 return (void *)dlsym(handle, symbol);
cannam@59 351 #endif
cannam@59 352 }
cannam@59 353
cannam@59 354 string
cannam@59 355 PluginLoader::splicePath(string a, string b)
cannam@59 356 {
cannam@59 357 #ifdef _WIN32
cannam@59 358 return a + "\\" + b;
cannam@59 359 #else
cannam@59 360 return a + "/" + b;
cannam@59 361 #endif
cannam@59 362 }
cannam@59 363
cannam@59 364 vector<string>
cannam@59 365 PluginLoader::listFiles(string dir, string extension)
cannam@59 366 {
cannam@59 367 vector<string> files;
cannam@59 368 size_t extlen = extension.length();
cannam@59 369
cannam@59 370 #ifdef _WIN32
cannam@59 371
cannam@59 372 string expression = dir + "\\*." + extension;
cannam@59 373 WIN32_FIND_DATA data;
cannam@59 374 HANDLE fh = FindFirstFile(expression.c_str(), &data);
cannam@59 375 if (fh == INVALID_HANDLE_VALUE) return files;
cannam@59 376
cannam@59 377 bool ok = true;
cannam@59 378 while (ok) {
cannam@59 379 files.push_back(data.cFileName);
cannam@59 380 ok = FindNextFile(fh, &data);
cannam@59 381 }
cannam@59 382
cannam@59 383 FindClose(fh);
cannam@59 384
cannam@59 385 #else
cannam@59 386 DIR *d = opendir(dir.c_str());
cannam@59 387 if (!d) return files;
cannam@59 388
cannam@59 389 struct dirent *e = 0;
cannam@59 390 while ((e = readdir(d))) {
cannam@59 391
cannam@59 392 if (!(e->d_type & DT_REG) || !e->d_name) continue;
cannam@59 393
cannam@59 394 size_t len = strlen(e->d_name);
cannam@59 395 if (len < extlen + 2 ||
cannam@59 396 e->d_name + len - extlen - 1 != "." + extension) {
cannam@59 397 continue;
cannam@59 398 }
cannam@59 399
cannam@59 400 files.push_back(e->d_name);
cannam@59 401 }
cannam@59 402
cannam@59 403 closedir(d);
cannam@59 404 #endif
cannam@59 405
cannam@59 406 return files;
cannam@59 407 }
cannam@59 408
cannam@59 409 void
cannam@59 410 PluginLoader::pluginDeleted(PluginDeletionNotifyAdapter *adapter)
cannam@59 411 {
cannam@59 412 void *handle = m_pluginLibraryHandleMap[adapter];
cannam@59 413 if (handle) unloadLibrary(handle);
cannam@59 414 m_pluginLibraryHandleMap.erase(adapter);
cannam@59 415 }
cannam@59 416
cannam@59 417 PluginLoader::PluginDeletionNotifyAdapter::PluginDeletionNotifyAdapter(Plugin *plugin,
cannam@59 418 PluginLoader *loader) :
cannam@59 419 PluginWrapper(plugin),
cannam@59 420 m_loader(loader)
cannam@59 421 {
cannam@59 422 }
cannam@59 423
cannam@59 424 PluginLoader::PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter()
cannam@59 425 {
cannam@59 426 // We need to delete the plugin before calling pluginDeleted, as
cannam@59 427 // the delete call may require calling through to the descriptor
cannam@59 428 // (for e.g. cleanup) but pluginDeleted may unload the required
cannam@59 429 // library for the call. To prevent a double deletion when our
cannam@59 430 // parent's destructor runs (after this one), be sure to set
cannam@59 431 // m_plugin to 0 after deletion.
cannam@59 432 delete m_plugin;
cannam@59 433 m_plugin = 0;
cannam@59 434
cannam@59 435 if (m_loader) m_loader->pluginDeleted(this);
cannam@59 436 }
cannam@57 437
cannam@57 438 }
cannam@59 439
cannam@59 440 }