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