Phasor 3.3.0
Stack VM based Programming Language
Loading...
Searching...
No Matches
ffi.cpp
Go to the documentation of this file.
1#include "ffi.hpp"
2#include "../VM/VM.hpp"
3#include <vector>
4#include <iostream>
5#include <memory>
6#include <string>
7#include <stdexcept>
8#include <sstream>
9#include <filesystem>
10#include <format>
11#if defined(__APPLE__)
12#include <mach-o/dyld.h>
13#elif defined(__linux__)
14#include <unistd.h>
15#endif
16
19#define INSTANCED_FFI(fn) [this](const std::vector<Value> &args, VM *vm) { return this->fn(args, vm); }
20
21namespace Phasor
22{
23
27bool FFI::loadPlugin(const std::filesystem::path &library, VM *vm)
28{
29#ifdef TRACING
30 vm_->log(std::format("FFI::{}(\"{}\")\n", __func__, library.string()));
31 vm_->flush();
32#endif
33 using PluginEntryFunc = void (*)(const PhasorAPI *, PhasorVM *);
34
35#if defined(_WIN32)
36 HMODULE lib = LoadLibraryA(library.string().c_str());
37 if (!lib)
38 {
39 std::stringstream ss;
40 ss << "FFI Error: Failed to load library " << library.string() << ". Code: " << GetLastError();
41 throw std::runtime_error(ss.str());
42 return false;
43 }
44 auto entry_point = (PluginEntryFunc)GetProcAddress(lib, "phasor_plugin_entry");
45#else
46 void *lib = dlopen(library.string().c_str(), RTLD_NOW);
47 if (!lib)
48 {
49 std::stringstream ss;
50 ss << "FFI Error: Failed to load library " << library.string() << ". Error: " << dlerror();
51 throw std::runtime_error(ss.str());
52 return false;
53 }
54 auto entry_point = (PluginEntryFunc)dlsym(lib, "phasor_plugin_entry");
55#endif
56
57 if (!entry_point)
58 {
59 throw std::runtime_error(
60 std::string("FFI Error: Could not find entry point 'phasor_plugin_entry' in " + library.string()));
61#if defined(_WIN32)
62 FreeLibrary(lib);
63#else
64 dlclose(lib);
65#endif
66 return false;
67 }
68
69 PhasorAPI api;
71
72 entry_point(&api, reinterpret_cast<PhasorVM *>(vm));
73
74 plugins_.push_back(Plugin{.handle = lib, .path = library.string(), .init = entry_point, .shutdown = nullptr});
75 return true;
76}
77
78bool FFI::addPlugin(const std::filesystem::path &pluginPath)
79{
80#ifdef TRACING
81 vm_->log(std::format("FFI::{}(\"{}\")\n", __func__, pluginPath.string()));
82 vm_->flush();
83#endif
84 return loadPlugin(pluginPath, vm_);
85}
86
90std::vector<std::string> FFI::scanPlugins(const std::filesystem::path &folder)
91{
92#ifdef TRACING
93 vm_->log(std::format("FFI::{}(\"{}\")\n", __func__, folder.string()));
94 vm_->flush();
95#endif
96 if (folder.empty())
97 return {};
98
99 std::vector<std::string> plugins;
100 std::filesystem::path exeDir;
101 std::vector<std::filesystem::path> foldersToScan;
102
103 if (folder.is_absolute())
104 {
105 foldersToScan.push_back(folder);
106 }
107 else
108 {
109#if defined(_WIN32)
110 char path[MAX_PATH];
111 GetModuleFileNameA(nullptr, path, MAX_PATH);
112 exeDir = std::filesystem::path(path).parent_path();
113#elif defined(__APPLE__)
114 char path[1024];
115 uint32_t size = sizeof(path);
116 if (_NSGetExecutablePath(path, &size) == 0)
117 exeDir = std::filesystem::path(path).parent_path();
118 else
119 exeDir = std::filesystem::current_path();
120#elif defined(__linux__)
121 char path[1024];
122 ssize_t count = readlink("/proc/self/exe", path, sizeof(path));
123 if (count != -1)
124 exeDir = std::filesystem::path(std::string(path, count)).parent_path();
125 else
126 exeDir = std::filesystem::current_path();
127#else
128 exeDir = std::filesystem::current_path();
129#endif
130
131 foldersToScan.push_back(exeDir / folder);
132 if (!std::filesystem::equivalent(exeDir, std::filesystem::current_path()))
133 foldersToScan.push_back(std::filesystem::current_path() / folder);
134 }
135
136 for (auto &folderPath : foldersToScan)
137 {
138 if (!std::filesystem::exists(folderPath) || !std::filesystem::is_directory(folderPath))
139 continue;
140
141 for (auto &p : std::filesystem::directory_iterator(folderPath))
142 {
143 if (!p.is_regular_file())
144 continue;
145 auto ext = p.path().extension().string();
146#if defined(_WIN32)
147 if (ext == ".dll" || ext == ".phsp")
148#elif defined(__APPLE__)
149 if (ext == ".dylib" || ext == ".phsp")
150#else
151 if (ext == ".so" || ext == ".phsp")
152#endif
153 plugins.push_back(p.path().string());
154 }
155 }
156 return plugins;
157}
158
163{
164#ifdef TRACING
165 vm_->log(std::format("FFI::{}()\n", __func__));
166 vm_->flush();
167#endif
168 for (auto &plugin : plugins_)
169 {
170#ifdef TRACING
171 vm_->log(std::format("FFI::{}(): \"{}\"\n", __func__, plugin.path));
172 vm_->flush();
173#endif
174#if defined(_WIN32)
175 FreeLibrary(plugin.handle);
176#else
177 dlclose(plugin.handle);
178#endif
179 }
180 plugins_.clear();
181}
182
183FFI::FFI(const std::filesystem::path &pluginFolder, VM *vm) : pluginFolder_(pluginFolder), vm_(vm)
184{
185#ifdef TRACING
186 vm_->log(std::format("Phasor::FFI::{}(): created {:#x}\n", __func__, (uintptr_t)this));
187 vm_->flush();
188#endif
189 vm_->registerNativeFunction("load_plugin", INSTANCED_FFI(FFI::native_add_plugin));
190 auto plugins = scanPlugins(pluginFolder_);
191 for (const auto &pluginPath : plugins)
192 {
193 try
194 {
195 loadPlugin(pluginPath, vm);
196 }
197 catch (const std::runtime_error &e)
198 {
199 std::cerr << e.what() << std::endl;
200 }
201 }
202}
203
205{
206 unloadAll();
207#ifdef TRACING
208 vm_->log(std::format("Phasor::FFI::{}(): deconstructed {:#x}\n", __func__, (uintptr_t)this));
209 vm_->flush();
210#endif
211}
212
213bool FFI::native_add_plugin(const std::vector<Value> &args, VM *)
214{
215 if (args.size() != 1)
216 {
217 throw std::runtime_error("load_plugin expects exactly 1 argument: the plugin path.");
218 }
219 std::filesystem::path pluginPath = args[0].asString();
220 if (!std::filesystem::exists(pluginPath))
221 {
222 throw std::runtime_error("Plugin file does not exist: " + pluginPath.string());
223 }
224 return addPlugin(pluginPath);
225}
226} // namespace Phasor
struct PhasorVM PhasorVM
Phasor Virtual Machine pointer.
Definition PhasorFFI.h:59
VM * vm_
Pointer to the Phasor VM.
Definition ffi.hpp:119
void unloadAll()
Unloads all currently loaded plugins and clears internal state.
Definition ffi.cpp:162
std::filesystem::path pluginFolder_
Plugin search folder.
Definition ffi.hpp:118
~FFI()
Destructor. Unloads all loaded plugins.
Definition ffi.cpp:204
std::vector< Plugin > plugins_
Loaded plugins.
Definition ffi.hpp:117
std::vector< std::string > scanPlugins(const std::filesystem::path &folder)
Scans a folder for plugin libraries.
Definition ffi.cpp:90
bool loadPlugin(const std::filesystem::path &library, VM *vm)
Loads a single plugin from a library file.
Definition ffi.cpp:27
bool addPlugin(const std::filesystem::path &pluginPath)
Adds a single plugin from the specified path.
Definition ffi.cpp:78
bool native_add_plugin(const std::vector< Value > &args, VM *vm)
Native function to load a plugin at runtime.
Definition ffi.cpp:213
FFI(const std::filesystem::path &pluginFolder, VM *vm)
Constructs the FFI manager and loads plugins.
Definition ffi.cpp:183
Virtual Machine.
Definition VM.hpp:33
#define INSTANCED_FFI(fn)
Definition ffi.cpp:19
The Phasor Programming Language and Runtime.
Definition AST.hpp:12
void register_native_c_func(PhasorVM *vm, const char *name, PhasorNativeFunction func)
The concrete implementation of the PhasorRegisterFunction API call.
Definition api.cpp:124
The collection of API functions that the Phasor host provides to the plugin.
Definition PhasorFFI.h:211
PhasorRegisterFunction register_function
Registers a native C function with the given name.
Definition PhasorFFI.h:213
Represents a loaded plugin.
Definition ffi.hpp:37