How to pass variable arguments around in a tree data structure API
Hi folks,
for quite some time I have been feeling the lack of a t_tree data structure in the current Max/MSP API. So lately I decided to build one myself in order to be able to use it in a variety of projects I am working on. The SDK already includes a very good API for linked lists, so building t_tree on top of t_linklist seemed like the obvious way to go.
In my API, one of the methods I would like to implement is the following:
void *tree_node_methodchild(t_tree_node *x, t_atom_long index, t_symbol *sym, ...)
{
if (x->children) {
return linklist_methodindex(x->children, index, sym, ...);
}
return NULL;
}
Obviously this code is incorrect and doesn't even compile, but it clearly shows the problem I am facing: I don't know how to pass variable arguments to the linklist_methodindex() function. Things are made more complicated by the fact that the code needs to work both for 32-bit and 64-bit architectures.
Now, don't get me wrong... I have done my homework and I am VERY familiar with variadic functions and variadic macros in the C and C++ languages. I have also read every single post on the subject that I could find on the internet. However - for the life of me - I can't find a way to solve this problem that is consistent with the Max/MSP API and that works with the limitations imposed by the API itself.
In previous versions of Max/MSP I would use the following:
typedef struct _stack_splat
{
char b[64];
} t_stack_splat;
void *tree_node_methodchild(t_tree_node *x, long index, t_symbol *sym, ...)
{
if (x->children) {
return linklist_methodindex(x->children, index, sym, *((t_stack_splat *)(((char *)(&sym)) + 4)));
}
return NULL;
}
which passes whatever is on the stack after the last typed argument in an easy and convenient manner.
In the past, using the C74_SPLAT set of macros also worked well and was compact and straight-forward.
#define C74_SPLAT_PREPARE(splat_prev_arg) va_list splat_va_args; \
void *sp_arg1, *sp_arg2, *sp_arg3, *sp_arg4, *sp_arg5, *sp_arg6, *sp_arg7, *sp_arg8; \
va_start(splat_va_args, splat_prev_arg); \
sp_arg1 = va_arg(splat_va_args, void*); \
sp_arg2 = va_arg(splat_va_args, void*); \
sp_arg3 = va_arg(splat_va_args, void*); \
sp_arg4 = va_arg(splat_va_args, void*); \
sp_arg5 = va_arg(splat_va_args, void*); \
sp_arg6 = va_arg(splat_va_args, void*); \
sp_arg7 = va_arg(splat_va_args, void*); \
sp_arg8 = va_arg(splat_va_args, void*);
#define C74_SPLAT_PASS sp_arg1, sp_arg2, sp_arg3, sp_arg4, sp_arg5, sp_arg6, sp_arg7, sp_arg8
#define C74_SPLAT_CLEANUP va_end(splat_va_args);
void *tree_node_methodchild(t_tree_node *x, long index, t_symbol *sym, ...)
{
void *result = NULL;
if (x->children) {
C74_SPLAT_PREPARE(sym);
result = linklist_methodindex(x->children, index, sym, C74_SPLAT_PASS);
C74_SPLAT_CLEANUP
}
return result;
}
However, because of the most recent changes in the Max 6 API (mainly because of the 64-bit architecture support) all of the above no longer works reliably.
The latest SDK (6.1.4) seems to be using some clever but convoluted preprocessor macros to make this work. I have been trying to make sense of those, but without success.
Is anybody able to shed some light on this, possibly showing some example code?
Thanks so much for any help.
- Luigi
Hi folks,
overwhelmed by the amount of replies to my post, I realized that I might have been too convoluted and/or not clear enough in my explanation. Therefore I narrowed it down to one simple question.
Basically - taking the ext_linklist.h API as an example - I see the header file prototypes the following function:
/**
Call the named message on an object specified by index.
The item must be an object instance with a valid t_object header.
@ingroup linklist
@param x The linklist instance.
@param i The index of the item to which to send the message.
@param s The name of the message to send to the objects.
@param ... Any arguments to be sent with the message.
@remark Internally, this function uses object_method(), meaning that no errors will be
posted if the message name does not exist for the object. It also means that
messages sent methods with #A_GIMME definitions will need to be given a symbol
argument prior to the argc and argv array information.
*/
void *linklist_methodindex(t_linklist *x, t_atom_long i, t_symbol *s, ...);
#ifdef C74_X64
#define linklist_methodindex(...) C74_VARFUN(linklist_methodindex_imp, __VA_ARGS__)
#endif
void *linklist_methodindex_imp(void *x, void *i, void *s, void *p1, void *p2, void *p3, void *p4, void *p5, void *p6, void *p7); // 10 arg varfun, so we lose an arg
which makes linklist_methodindex() expand to a clever combination of macros (defined in ext_preprocessor.h) in case we are compiling for 64-bit architectures. The nice thing is that the variadic macro enable us to pass the variable arguments forward. So far so good.
Now, even if I don’t have access to the source code, a possible simplistic implementation of linklist_methodindex() could be something along these lines:
void *linklist_methodindex(t_linklist *x, t_atom_long i, t_symbol *s, ...)
{
t_object *o;
if (x) {
o = linklist_getindex(x, i);
if (o) {
return object_method(o, s, ...);
}
}
return NULL;
}
So the question is:
how do I implement linklist_methodindex() and/or linklist_methodindex_imp() so that:
1) I am able to pass the variable arguments to object_method() in a reliable manner.
2) It is compatible to both 32-bit and 64-bit architectures
3) It is consistent with the linklist_methodindex() function prototyped in the header file.
Please give me even a small hint or suggestion, because I am really stuck on this one.
Thanks a lot.
- Luigi
hi Luigi are u italian ?
Yes, I am...