1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
|
#include "Interface.h"
#include "PluginDesc.h"
#include "Log.h"
#include "FileUtils.h"
#include "MiscUtils.h"
#if USE_BRIDGE
#include "PluginServer.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <mutex>
// a) probe on main thread without event loop
#define PROBE_WITHOUT_UI_THREAD 0
// b) run event loop on main thread, but probe plugin on another thread
#define PROBE_WITH_UI_THREAD 1
// c) probe plugin inside the event loop (on the main thread)
#define PROBE_IN_UI_THREAD 2
#ifndef PROBE_MODE
#define PROBE_MODE PROBE_WITHOUT_UI_THREAD
#endif // PROBE_MODE
using namespace vst;
#ifndef USE_WMAIN
# ifdef _WIN32
# define USE_WMAIN 1
# else
# define USE_WMAIN 0
# endif
#endif
#if !USE_WMAIN
# define shorten(x) x
#endif
void writeErrorMsg(Error::ErrorCode code, std::string_view msg, const std::string& path){
if (!path.empty()){
// this part should be async signal safe
vst::File file(path, File::WRITE);
if (file.is_open()) {
file << static_cast<int>(code) << "\n";
file << msg << "\n";
} else {
LOG_ERROR("ERROR: couldn't write error message");
}
}
}
// probe a plugin and write info to file
// returns EXIT_SUCCESS on success, EXIT_FAILURE on fail and anything else on error/crash :-)
int probe(const std::string& pluginPath, int pluginIndex, const std::string& filePath)
{
setThreadPriority(Priority::Low);
LOG_DEBUG("probing " << pluginPath << " " << pluginIndex);
try {
#if PROBE_MODE != PROBE_WITHOUT_UI_THREAD
// setup UI event loop
LOG_DEBUG("setup event loop");
UIThread::setup();
vst::PluginDesc::const_ptr desc;
Error error;
auto fn = [&]() {
try {
auto factory = vst::IFactory::load(pluginPath, true);
desc = factory->probePlugin(pluginIndex);
} catch (const Error& e) {
error = e;
}
LOG_DEBUG("quit event loop");
UIThread::quit();
};
#if PROBE_MODE == PROBE_IN_UI_THREAD
UIThread::callAsync([](void *x) {
LOG_DEBUG("probe plugin on UI thread");
(*static_cast<decltype(fn)*>(x))();
}, &fn);
#else // PROBE_WITH_UI_THREAD
std::thread thread([&]() {
LOG_DEBUG("probe plugin on thread");
fn();
});
#endif // PROBE_MODE
// run until quit in lambda
LOG_DEBUG("run event loop");
UIThread::run();
LOG_DEBUG("event loop did quit");
#if PROBE_MODE == PROBE_WITH_UI_THREAD
thread.join();
#endif
if (error.code() != Error::NoError) {
throw error; // rethrow!
}
#else // PROBE_WITHOUT_UI_THREAD
auto factory = vst::IFactory::load(pluginPath, true);
auto desc = factory->probePlugin(pluginIndex);
#endif // PROBE_MODE
if (!filePath.empty()) {
vst::File file(filePath, File::WRITE);
if (file.is_open()) {
desc->serialize(file);
} else {
LOG_ERROR("ERROR: couldn't write info file " << filePath);
}
}
LOG_INFO("Probe succeeded.");
return EXIT_SUCCESS;
} catch (const Error& e){
writeErrorMsg(e.code(), e.what(), filePath);
LOG_ERROR("Probe failed: " << e.what());
} catch (const std::exception& e) {
writeErrorMsg(Error::UnknownError, e.what(), filePath);
LOG_ERROR("Probe failed: " << e.what());
} catch (...) {
writeErrorMsg(Error::UnknownError, "unknown exception", filePath);
LOG_ERROR("Probe failed: unknown exception.");
}
return EXIT_FAILURE;
}
#if USE_BRIDGE
#if VST_HOST_SYSTEM == VST_WINDOWS
static HANDLE gLogChannel = NULL;
#else
static int gLogChannel = -1;
#endif
#ifdef _WIN32
namespace vst {
void setParentProcess(int pid); // WindowWin32.cpp
}
#endif
static std::mutex gLogMutex;
void writeLog(int level, const char *msg){
LogMessage::Header header;
header.level = level;
header.size = strlen(msg) + 1;
#if VST_HOST_SYSTEM == VST_WINDOWS
if (gLogChannel) {
std::lock_guard lock(gLogMutex);
DWORD bytesWritten;
WriteFile(gLogChannel, &header, sizeof(header), &bytesWritten, NULL);
WriteFile(gLogChannel, msg, header.size, &bytesWritten, NULL);
}
#else
if (gLogChannel >= 0) {
std::lock_guard lock(gLogMutex);
write(gLogChannel, &header, sizeof(header));
write(gLogChannel, msg, header.size);
}
#endif
}
// host one or more VST plugins
int bridge(int pid, const std::string& path, int logChannel){
#ifdef _WIN32
setParentProcess(pid); // see WindowWin32.cpp
#endif
#if VST_HOST_SYSTEM == VST_WINDOWS
// setup log channel
auto hParent = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid);
if (hParent){
if (DuplicateHandle(hParent, (HANDLE)(uintptr_t)logChannel,
GetCurrentProcess(), &gLogChannel,
0, FALSE, DUPLICATE_SAME_ACCESS)) {
setLogFunction(writeLog);
} else {
LOG_ERROR("DuplicateHandle() failed: " << errorMessage(GetLastError()));
}
} else {
LOG_ERROR("OpenProcess() failed: " << errorMessage(GetLastError()));
}
if (hParent) {
CloseHandle(hParent);
}
#else
gLogChannel = logChannel;
setLogFunction(writeLog);
#endif
LOG_DEBUG("bridge begin");
// main thread is UI thread
setThreadPriority(Priority::Low);
try {
// create and run server
PluginServer server(pid, path);
server.run();
LOG_DEBUG("bridge end");
return EXIT_SUCCESS;
} catch (const Error& e){
// LATER redirect stderr to parent stdin to get the error message
LOG_ERROR(e.what());
return EXIT_FAILURE;
}
}
#endif // USE_BRIDGE
#if USE_WMAIN
int wmain(int argc, const wchar_t *argv[]){
#else
int main(int argc, const char *argv[]) {
#endif
if (argc >= 2){
std::string verb = shorten(argv[1]);
argc -= 2;
argv += 2;
if (verb == "probe" && argc > 0){
// args: <plugin_path> [<id>] [<file_path>] [<timeout>]
std::string path = shorten(argv[0]);
int index = -1;
if (argc > 1) {
try {
index = std::stol(argv[1], 0, 0);
} catch (...) {} // non-numeric argument, e.g. '_'
}
std::string file = argc > 2 ? shorten(argv[2]) : "";
return probe(path, index, file);
}
#if USE_BRIDGE
else if (verb == "bridge" && argc >= 3){
// args: <pid> <shared_mem_path> <log_pipe>
int pid, logChannel;
try {
pid = std::stol(argv[0], 0, 0);
} catch (...) {
LOG_ERROR("bad 'pid' argument: " << argv[0]);
return EXIT_FAILURE;
}
std::string shmPath = shorten(argv[1]);
try {
logChannel = std::stol(argv[2], 0, 0);
} catch (...) {
LOG_ERROR("bad 'log_pipe' argument: " << argv[2]);
return EXIT_FAILURE;
}
return bridge(pid, shmPath, logChannel);
}
#endif
else if (verb == "test" && argc > 0){
std::string version = shorten(argv[0]);
// version must match exactly
if (version == getVersionString()) {
return EXIT_SUCCESS;
} else {
LOG_ERROR("version mismatch");
return kHostAppVersionMismatch;
}
} else if (verb == "--version") {
std::cout << "vstplugin " << getVersionString() << std::endl;
return EXIT_SUCCESS;
}
}
std::cout << "usage:\n"
<< " probe <plugin_path> [<id>] [<file_path>]\n"
#if USE_BRIDGE
<< " bridge <pid> <shared_mem_path> <log_pipe>\n"
#endif
<< " test <version>\n"
<< " --version" << std::endl;
return EXIT_FAILURE;
}
|