Min-DevKit sharing data between objects
To brush up a bit my C++ knowledge and out of curiosity I started to dip my toes into the Min-Devkit.
As a starting project I chose to work on a new implementation of an object to connect to the ENTTEC DMX USB Pro, because it has a simple serial API and offers some interesting possibilities like multithreading, concurrency etc. One object I am working on is called [jam.dmxusbpro].
As part of the project I want to allow only one instance of [jam.dmxusbpro] to be able to open a serial port to an ENTTEC DMX USB Pro.
The way implemented this is by creating a separate device manager class - a singleton - that tracks which devices are opened by which instance of [jam.dmxusbpro] . That works fine between objects off the same class.
As a second step I am creating another object, called [jam.dmxusbpro~] that allows to control DMX channels with MSP signals.
However, my approach of using a singleton device manager class only works within instances of the same Max object not between [jam.dmxusbpro] and [jam.dmxusbpro~].
Is there a way to share data between different Max objects using the Min-DevKit?
PS: I am working on MacOS and am using the POSIX termios API to open the serial ports.
PPS: Min-DevKit is a great addition!! It got me started very fast and is easily accessible for me who's working more in other languages and has a quite basic experience with C and C++.
I got it figured out: Dictionaries are global data structures and can do the job of sharing data between different Max classes.
There are various other global data structures in addition to Dicts too: buffers, tables, various
You can also make your own in C, but it requires you making an "extension" instead of an "external". This allows you to define your datastructures on Max boot. I do this for Scheme for Max with the s4m-arrays (for passing data between s4m objects) and the Bach project does too for their llllls.
Scheme for Max is open source in case you want to know how I did the s4m-arrays. They are very fast unprotected global data structures meant for single-producer-single-consumer communication.
hth!
Thanks for the insights, Iain!
Your device manager class approach will work as an extension btw. I'm not sure if this is the case here, but it's worth mentioning that there are still a lot of SDK facilitiies that are either not in the min-devkit or aren't well documented in it, but which have examples in the plain C API.
My device manager class works fine for sharing data between objects of the same Max class (Max object) with the Min-DevKit but I couldn't get it to work across Max objects. It is defined in its own file (translation unit) and the header included in both objects.
Maybe it gets compiled directly into the .mxo and therefore acts like two different classes?
I can work around that issues with regular dict, but i would be curious how to make and include an extension within the Min-DevKit if it is possible. I saw your code, but I couldn't figure out how to implement it.
... much to learn still ....
Hi Jan, this is why you need to make an extension, not an external. An external loads only when the object is created, while an extension load on Max startup (assuming the package is installed). So you can run code that is necessary to have initialized before any objects are made that way. I had to switch to an extension for this same reason when I implemented s4m-arrays for sharing between the s4m objects and the s4m.grid objects.
Hello Iain,
would you mind to explain to a noob like me a bit the concept of an extension? After looking into same code and other packages I saw that that extensions are places inside the extensions folder in a package and that they use the same .mxo extension. I also figured out that externals, that are placed inside the extensions folder seem to load just like regular externals.
Are extensions just externals that are placed in a different folder? Do they have a special structure and/or do they need to be compiled differently that externals? And am I asking the right questions?
Thanks a lot!
Hi Jan, I'm travelling right now so not at a proper desk, but in brief it's a different folder you put your compiled object in so that Max will load it on start. I can't recall right now whether I had to do anything other than that to get it to work, I seem to remember I had some mucking about with class names that I had to do. If you want to check the Scheme for Max source code, you can see how i have it loading.
If you look in my main file, s4m.c, you will see ext_main is the function that will run on startup (without the user needing to make an s4m object). If you have it right, a post message in your ext_main will show up as soon as you open Max.
HTH!
coming back to it a year later. building an object as an extension works fine.
As a note to my future self and maybe anyone who might have the same question:
When using the Min-DevKit building and extension instead of an object is actually as easy as adding one line to the beginning of the objects CMakeLists.txt:
set(C74_BUILD_MAX_EXTENSION 1)
this will configure the project correctly.
Hi Jan!
Thanks for sharing this! I had a quick look at your code, and just purely out of curiosity, did you ever try to build your singleton manager as a min object?
It should at least in theory be possible to make a min::object
nobox by declaring MIN_FLAGS{behavior_flags::nobox}
somewhere in the body of the class, but I have no idea if it's possible to pass objects like your _jam_im
with wrapped message<>
functions. Did you ever try something like this?
Hi JBG,
First some context about the code: Currently I am working on a set of externals to be able to load, render and edit laser projector animation ILDA files. Down the road I want to integrate as well an object to control the (open source) Helios Laser DAC. All this work is in the branch helios-ilia-dev. And I am learning as I go along. So the code in this branch is in various stages of good :)
Back to your question. I haven't tried to build the global manager singleton extension (jam.ilda.manager.cpp) as a min object yet - it is a traditional c object but written inside a Min Project/Package. Precisely for that reason (setting the CLASS_NOBOX flag). It may be possible though - my best guess would be to try something inside the constructor, but only during the "creation" phase of the object. Something like this:
if (!dummy()) {
[do something here]
}
and/or
if(!this->initialized()) {
[do something here]
}
P.S:
There are still some other minor issues which I haven't yet looked into with my code.
the extension loads and works fine, but it is still listed in the autocomplete suggestions even though it cannot be instanciated:

If happen to know how to exclude and extension from being index I'd be happy to learn about it.
Update: I was overthink obviously. The solution seems very straight forward:
class mypobject : public object<mypobject> {
public:
mypobject(const atoms& args = {}) {[...]};
~mypobject() {};
[...]
MIN_FLAGS {behavior_flags::nobox};
};
MIN_EXTERNAL(mypobject);
After some more playing around with this: It seems to work fine creating an extension as a Min-Object.
What I couldn't figure out yet is how to get a return value when calling a message from such an extension from inside a Max Object:
Using
typedmess(...)
returns a void pointer, that needs to be casted to it's actual type. Doing this on Min-Object seems to add some type translation that is opaque to me so far.
Yep, that's about as far as I got too. If your current solution works as intended, I would've assumed that something like
atoms atms{c74::max::typedmess(m_obj, symbol("get_singleton"), 0, nullptr)};
auto* s = static_cast<MyPseudoSingleton*>(static_cast<void*>(atms[0]));
would work just as well (assuming atoms
rather than a single atom
as return type from typedmess since the return type of any message<>
is atoms
and using a nested static cast since we need to ensure that we invoke atom
's operator void*
). But even if this calls the other object's (min) message get_singleton
correctly, it seems that the value passed from there and the value received in the typedmess
call points to different addresses.
Either way, it's obviously not that important since you already have a working solution, again this was just purely out of curiousity, to see if it could be done with (more or less) pure min-api calls!