1 /* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2 
3 /*
4  *  Main authors:
5  *     Jason Nguyen <jason.nguyen@monash.edu>
6  */
7 
8 /* This Source Code Form is subject to the terms of the Mozilla Public
9  * License, v. 2.0. If a copy of the MPL was not distributed with this
10  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
11 
12 #pragma once
13 
14 #ifdef _WIN32
15 #define NOMINMAX  // Ensure the words min/max remain available
16 #include <Windows.h>
17 #undef ERROR
18 #else
19 #include <dlfcn.h>
20 #endif
21 
22 #include <minizinc/exception.hh>
23 #include <minizinc/file_utils.hh>
24 
25 #include <cstdlib>
26 #include <sstream>
27 #include <string>
28 #include <vector>
29 
30 /// Convenience macro for loading symbols
31 #define load_symbol(name) *(void**)(&(name)) = symbol(#name)
32 
33 namespace MiniZinc {
34 /// Base class for plugins loaded from DLLs
35 class Plugin {
36 public:
37   class PluginError : public Exception {
38   public:
39     /// Construct with message \a msg
PluginError(const std::string & msg)40     PluginError(const std::string& msg) : Exception(msg) {}
41     /// Destructor
~PluginError()42     ~PluginError() throw() override {}
43     /// Return description
what() const44     const char* what() const throw() override { return "MiniZinc: plugin loading error"; }
45   };
46 
47   /// Load a plugin with given DLL path
Plugin(const std::string & file)48   Plugin(const std::string& file) {
49     if (!open(file)) {
50       throw PluginError("Failed to load plugin " + file);
51     }
52   }
53   /// Load a plugin by trying the given DLL file paths
Plugin(const std::vector<std::string> & files)54   Plugin(const std::vector<std::string>& files) {
55     for (const auto& file : files) {
56       if (open(file)) {
57         return;
58       }
59     }
60     bool first = true;
61     std::stringstream ss;
62     ss << "Failed to load plugin. Tried ";
63     for (const auto& file : files) {
64       if (first) {
65         first = false;
66       } else {
67         ss << ", ";
68       }
69       ss << file;
70     }
71     throw PluginError(ss.str());
72   }
73 
~Plugin()74   ~Plugin() { close(); }
75 
76   /// Get the path to the loaded DLL
path()77   const std::string& path() { return _loaded; }
78 
79 protected:
80   /// Load a symbol from this DLL
symbol(const char * name)81   void* symbol(const char* name) {
82     void* ret;
83 #ifdef _WIN32
84     ret = (void*)GetProcAddress((HMODULE)_dll, (LPCSTR)name);
85 #else
86     ret = dlsym(_dll, name);
87 #endif
88     if (ret == nullptr) {
89       throw PluginError(std::string("Failed to load symbol ") + name);
90     }
91     return ret;
92   }
93 
94 private:
95   void* _dll;
96   std::string _loaded;
open(const std::string & file)97   bool open(const std::string& file) {
98 #ifdef _WIN32
99     const std::string ext = ".dll";
100 #elif __APPLE__
101     const std::string ext = ".dylib";
102 #else
103     const std::string ext = ".so";
104 #endif
105     bool hasExt =
106         file.size() >= ext.size() && file.compare(file.size() - ext.size(), ext.size(), ext) == 0;
107     auto path = (hasExt || MiniZinc::FileUtils::is_absolute(file)) ? file : (file + ext);
108 #ifdef _WIN32
109     auto dir = MiniZinc::FileUtils::dir_name(path);
110     if (!dir.empty()) {
111       // Add the path with the DLL to the search path for dependency loading
112       SetDllDirectoryW(MiniZinc::FileUtils::utf8_to_wide(dir).c_str());
113     }
114     _dll = (void*)LoadLibrary((LPCSTR)path.c_str());
115     if (!dir.empty()) {
116       SetDllDirectoryW(nullptr);
117     }
118 #else
119     _dll = dlopen(path.c_str(), RTLD_NOW);
120 #endif
121     if (_dll != nullptr) {
122       _loaded = path;
123       return true;
124     }
125 
126     return false;
127   }
close()128   void close() {
129 #ifdef _WIN32
130     FreeLibrary((HMODULE)_dll);
131 #else
132     dlclose(_dll);
133 #endif
134     _dll = nullptr;
135   }
136 };
137 }  // namespace MiniZinc
138