Multiple instances and memory space ( VST 2.4 )...
-
- KVRAF
- 7400 posts since 17 Feb, 2005
Bumping this thread, I have a question. I've been trying to figure out a way to use the PE's data section to store my AudioEffect and AEffect structures. What I am not sure of, is how this will be done WRT multiple plugin instances within one host process that may either thread or sandbox the call to LoadLibrary. Or IOW, I can't figure out if the PE section will need to be large enough for only one instance or all of them.
Will the call to LoadLibrary ever involve duplication of data sections?
I'm attempting this to avoid use of malloc etc. for better code size.
Will the call to LoadLibrary ever involve duplication of data sections?
I'm attempting this to avoid use of malloc etc. for better code size.
-
- KVRAF
- 2256 posts since 29 May, 2012
If the shared library is being loaded to the same process address space, then no. If it's being loaded to a separate process then one starts a separate process first anyway, so that is how data sections are being duplicated.Will the call to LoadLibrary ever involve duplication of data sections?
edit: You can rename your DLL though, and then all its data can be duplicated.
~stratum~
-
- KVRAF
- 2256 posts since 29 May, 2012
Basically there is one instance of each DLL for each process and the data section is shared among threads. It's not thread safe for anything other than the constants (the data that you don't modify, not necessarily the data declared to be constant). The variables that you mark to be 'thread local' with _declspec( thread ) are safe, though as the compiler generates special code for these and they aren't really in the DLL 'data section'.
Per-instance data (in the sense of plugin instance not DLL instance) is in the heap, not in the DLL data section.
Per-instance data (in the sense of plugin instance not DLL instance) is in the heap, not in the DLL data section.
~stratum~
- KVRAF
- 12555 posts since 7 Dec, 2004
Someone needs to determine where those non-overlapping addresses are on a dynamic basis, which is the very malloc/heap allocation you're trying to avoid.
Avoiding heap allocation on a dynamic system like a modern OS is simply impossible. Don't even go there.
To reduce code size you might use large chunks: so rather than using heap allocation for all objects limit your allocation to only the instance itself.
This is like:
Vs.
This is the proper way to write C++ anyway: RAII
You should never be manually calling new/delete, ever. Objects should never contain pointers to other objects allocated by/within the object itself. That doesn't make sense at all and is just fragmenting the object all over the heap with 100s of allocations rather than a single allocation.
If you know ahead of time the maximum length of a string; it means you can use templates and allocate into a static array with an index like so:
Obviously an ordered stack is more expensive than an unordered stack: if the order isn't important you can replace a removed element with the last element and decrement the length.
Obviously stl/boost contain all these things. I don't like the fact they include obscure rules that can lead to bug-prone code. I prefer "exactly as much as is needed" vs. "everything and more".
Avoiding heap allocation on a dynamic system like a modern OS is simply impossible. Don't even go there.
To reduce code size you might use large chunks: so rather than using heap allocation for all objects limit your allocation to only the instance itself.
This is like:
Code: Select all
struct dynamic_t
{
enum class constants
{
members = 16,
};
dynamic_t()
{
for (int i = 0; i < constants::members; i++) {
member[i] = new type_t(...);
}
}
~dynamic_t()
{
for (int i = 0; i < constants::members; i++) {
delete member[i];
}
}
type_t *member[constants::members];
};
Code: Select all
struct static_t
{
enum class constants
{
members = 16,
};
static_t() {}
~static_t() {}
type_t member[constants::members];
};
You should never be manually calling new/delete, ever. Objects should never contain pointers to other objects allocated by/within the object itself. That doesn't make sense at all and is just fragmenting the object all over the heap with 100s of allocations rather than a single allocation.
If you know ahead of time the maximum length of a string; it means you can use templates and allocate into a static array with an index like so:
Code: Select all
template <typename T, int max_length>
struct static_stack_t
{
static_stack_t()
{
reset();
}
~static_stack_t()
{
}
T operator[](int i)
{
return stack[i];
}
void add(T x)
{
stack[length] = x;
length++;
}
void remove(T x)
{
int i;
for (i = 0; i < length; i++) {
if (stack[i] == x) {
break;
}
}
if (i < length) {
// found the entry, remove it from the stack
length--;
for (; i < length; i++) {
stack[i] = stack[i + 1];
}
}
}
void reset()
{
length = 0;
}
int length;
static_array_t<T, max_length> stack;
};
Obviously stl/boost contain all these things. I don't like the fact they include obscure rules that can lead to bug-prone code. I prefer "exactly as much as is needed" vs. "everything and more".
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.
- KVRAF
- 12555 posts since 7 Dec, 2004
camsr asked me about the aeffect struct: VST2's primary interface.
You create an interface in C or C++ to maintain state. For example all the things NOT IN aeffect go in your interface, let's call this:
So aeffect->object is a pointer to the C++ class "AudioEffectX", if it does not point to that interface (if you don't use the VSTSDK) it MUST BE NULL! (C++ = nullptr;)
aeffect->user is a pointer which MAY BE used by the plug-in if it does NOT USE the VSTSDK.
So in this case:
Now when a function like aeffect->process(aeffect, ...) is called:
aeffect->reserved[2] MUST BE zeroed in the entry point function before the new aeffect instance is returned. These are "host pointers" which the host MAY USE to point to anything it wants. These pointers MUST NOT be read, dereferenced or written by the plug-in or anywhere other than the host!
The host MUST NOT ever read, dereference or write either aeffect->object or aeffect->user!
I, lord Vhtthisy hath spoken.
You create an interface in C or C++ to maintain state. For example all the things NOT IN aeffect go in your interface, let's call this:
Code: Select all
struct my_vst_plugin
{
...
};
aeffect->user is a pointer which MAY BE used by the plug-in if it does NOT USE the VSTSDK.
So in this case:
Code: Select all
aeffect->object = NULL;
aeffect->user = &my_vst_plugin;
Code: Select all
process(aeffect *aeff_ptr, **inputs, **outputs, samples)
{
my_vst_plugin *interface = (my_vst_plugin *)aeff_ptr->user;
interface->process(inputs, outputs, samples);
}
The host MUST NOT ever read, dereference or write either aeffect->object or aeffect->user!
I, lord Vhtthisy hath spoken.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.
-
- KVRAF
- 7400 posts since 17 Feb, 2005
Thank you for the clarification.
Is your code above (using process) some kind of wrapper, or is it referencing itself?
EDIT: just realized that it's referencing a different function (but the use of the same identifier is bad imo)
Is your code above (using process) some kind of wrapper, or is it referencing itself?
EDIT: just realized that it's referencing a different function (but the use of the same identifier is bad imo)
Last edited by camsr on Sat Feb 03, 2018 7:49 pm, edited 1 time in total.
-
- KVRAF
- 2256 posts since 29 May, 2012
I have only told what was happening in general because I haven't been working with the VST sdk for a long time and didn't really understood what you are trying to do. Global data is not thread safe for anything other than read-only access because it's shared after all. The only global data that is thread-safe to write access are those you declare to be 'thread local' and while these are 'global' they aren't shared among threads that's why they are both global and thread-safe. "Non-overlapping addresses" also means that that data is not shared among 'something', and whether it's thread safe or not depends on what that 'something' is. If 'something' refers simply to 'plugin instances', then obviously a single plugin instance contains multiple threads therefore it's not thread safe in that case.Why would it not be thread safe, if each instance read and write to non-overlapping addresses?
~stratum~
- KVRAF
- 12555 posts since 7 Dec, 2004
In C++ we use something called polymorphism which means 1000s of objects with 1000s of functions can have the same name: process()camsr wrote:Thank you for the clarification.
Is your code above (using process) some kind of wrapper, or is it referencing itself?
EDIT: just realized that it's referencing a different function (but the use of the same identifier is bad imo)
In fact the proper way to do this is generally to use operator(), which makes the code look like:
Code: Select all
my_object &ref = *ptr;
auto result = ref(io_t(inputs, outputs, channels, flags));
Especially so when you're writing c++ template functions that use generic types and operator() (this is called a "functor") where you can specify behaviors as template parameters.
For example:
Code: Select all
process_stereo_t<tanh_t, oversampler_t> ws_tanh;
process_stereo_t<acos_t, oversampler_t> ws_acos;
process_stereo_t<parabola_t, oversampler_t> ws_parabola;
process_stereo_t<cubic_t, oversampler_t> ws_cubic;
process_stereo_t<sigmoid_t, oversampler_t> ws_sigmoid;
process(...)
{
io_t io(inputs, outputs, samples, flags);
switch (shaper.type) {
case shaper_type_t::tanh: ws_tanh(io); break;
case shaper_type_t::acos: ws_acos(io); break;
case shaper_type_t::parabola: ws_parabola(io); break;
case shaper_type_t::cubic: ws_cubic(io); break;
case shaper_type_t::sigmoid: ws_sigmoid(io); break;
}
This can be expanded to as many dimensions as possible: for example why not have 30 waveshapers and 30 over-sampling modes with 30 filters and 30 types of modulation? That might seem incredibly complicated but you only need to handle 4 objects to accomplish this and it all works out to a single function call: process().
This makes code much easier to maintain and multiplies the scale of the code you can maintain by many orders of magnitude compared to C.
It is however dangerous if you make mistakes: "C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off."
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.