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