File: host.cpp

package info (click to toggle)
pd-vstplugin 0.6.1-1~exp1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 2,008 kB
  • sloc: cpp: 22,783; lisp: 2,860; makefile: 37; sh: 26
file content (283 lines) | stat: -rw-r--r-- 8,120 bytes parent folder | download | duplicates (2)
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;
}