Max 5 API Reference
00001 /** 00002 @page chapter_msgattached Sending Messages, Calling Methods 00003 00004 Max objects, such as the one you write, are C data structures in which methods are dynamically bound to functions. Your object's methods are called by Max, but your object can also call methods itself. When you call a method, it is essential to know whether the method you are calling is <strong>typed</strong> or not. 00005 00006 Calling a typed method requires passing arguments as an array of atoms. Calling an untyped method requires that you know the exact arguments of the C function implementing the method. In both cases, you supply a symbol that names the method. 00007 00008 In the typed method case, Max will take the array of atoms and pass the arguments to the object according to the method's argument type specifier list. For example, if the method is declared to have an argument type specifier list of #A_LONG, 0, the first atom in the array you pass will be converted to an int and passed to the function on the stack. If there are no arguments supplied, invoking a typed method that has #A_LONG, 0 as an argument type specifier will fail. To make typed method calls, use object_method_typed() or typedmess(). 00009 00010 In the untyped method case, Max merely does a lookup of the symbol in the object, and, if a matching function is found, calls the function with the arguments you pass. 00011 00012 Certain methods you write for your object, such as the assist method for describing your object and the DSP method in audio objects, are declared as untyped using the #A_CANT argument type specifier. This means that Max will not typecheck the arguments you pass to these methods, but, most importantly, a user cannot hook up a message box to your object and send it a message to invoke an untyped method. (Try this for yourself -- send the assist message to a standard Max object.) 00013 00014 When you use an outlet, you're effectively making a typed method call on any objects connected to the outlet. 00015 00016 00017 @section chapter_msgattached_attrs Attributes 00018 00019 Attributes are descriptions of data in your object. The standardization of these descriptions permits Max to provide a rich interface to object data, including the pattr system, inspectors, the quick reference menu, @ arguments, etc. 00020 00021 It is essential that you have some understanding of attributes if you are going to write a UI object. But non-UI objects can make use of attributes as well. The discussion below is not specific to UI objects. It does however, use the recently introduced system of macros in ext_obex_util.h (included in ext_obex.h) for defining attributes, as well as describing them using attributes of attributes (attr attrs). You can read more detailed descriptions of the underlying attribute definition mechanisms on a per-function basis in the @ref attr reference. 00022 00023 00024 @subsection chapter_msgattached_attr_basics Attribute Basics 00025 00026 While attributes can be defined for a specific instance of an object, it's much more common to define an attribute for a class. In such a case, each instance of the class will have the attribute description, but the value will be instance specific. The discussion here focuses only on class attributes. 00027 00028 When an attribute is declared and is made user-settable, a user can send a message to your object consisting of the attribute name and arguments that represent the new value of the attribute. For example, if you declare an attribute called trackcount, the message trackcount 20 will set it to 20. You don't need to do anything special to obtain this behavior. In addition, user-settable attributes will appear when the user opens the inspector on your object. 00029 00030 If you define your attribute as an offset attribute, you describe its location (and size) within your object's C data structure. Max can then read and write the data directly. You can also define custom getter and setter routines if the attribute's value is more complex than simply a stored number. As a theoretical example, you could have an object with an attribute representing the Earth's population. If this value was not able to be stored inside your object, your custom getter routine could initiate a global census before returning the result. A custom setter for the earth's population might do something nasty if the value was set to zero. If you are not a misanthrope, you can take advantage of the ability to set such an attribute to be read-only. 00031 00032 00033 @subsection chapter_msgattached_attr_def Defining Attributes 00034 00035 Attributes are defined when you are defining methods in your initialization routine. You can define your attributes before your methods if you like, but by convention, they are typically defined after the methods. For each definition, you'll specify the name, size, and offset of the corresponding member in your object's data structure that will hold the data. For example, let's say we have an object defined as follows: 00036 00037 @code 00038 typedef struct _myobject { 00039 t_object m_ob; 00040 long m_targetaddress; 00041 t_symbol *m_shipname; 00042 char m_compatmode; 00043 } t_myobject; 00044 @endcode 00045 00046 We want to create attributes for m_targetaddress, m_shipname, and m_compatmode. For each data type (and a few others), there are macros in ext_obex_util.h that will save a fair amount of typing. So, for example, we can define an attribute for m_targetaddress that uses CLASS_ATTR_LONG. Here are attribute definitions for all of the members of our data structure above. 00047 00048 @code 00049 CLASS_ATTR_LONG(c, "targetaddress", 0, t_myobject, m_targetaddress); 00050 CLASS_ATTR_SYM(c, "shipname", 0, t_myobject, m_shipname); 00051 CLASS_ATTR_CHAR(c, "compatibilitymode", 0, t_myobject, m_compatmode); 00052 @endcode 00053 00054 @subsection chapter_msgattached_attr_custom Attributes With Custom Getters and Setters 00055 00056 In some cases, it is not enough to have Max read and write data in your object directly. In some cases (as in the world population example above) you may have data you need to calculate before it can be returned as a value. In other cases, you may need to do something to update other object state when an attribute value changes. To handle these challenges, you can define custom attribute getter and setter routines. The getter will be called when the value of your attribute is accessed. The setter will be called when someone changes the value of your attribute. 00057 00058 As an example, suppose we have an object that holds onto an array of numbers, and we want to create an attribute for the size of the array. Since we'll want to resize the array when the attribute value changes, we will define a custom setter for our attribute. The default getter is adequate if we store the array size in our object, but since we want to illustrate how to write an attribute getter, we'll write the code so that the array size is computed from the size of the memory pointer we allocate. First, here is our object's data structure: 00059 00060 @code 00061 typedef struct _myobject { 00062 t_object m_ob; 00063 long *m_data; 00064 } t_myobject; 00065 @endcode 00066 00067 We also have prototypes for our custom attribute setter and getter: 00068 00069 @code 00070 t_max_err myobject_size_get(t_myobject *x, t_object *attr, long *argc, t_atom **argv); 00071 t_max_err myobject_size_set(t_myobject *x, t_object *attr, long argc, t_atom *argv); 00072 @endcode 00073 00074 Here is how we define our attribute using #CLASS_ATTR_ACCESSORS macro to define the custom setter and getter. Because we aren't really using an "offset" due to the custom setter and getter, we can pass any data structure member as a dummy. (Only the default attribute getter and setter will use this offset, and they are out of the picture.) 00075 00076 @code 00077 CLASS_ATTR_LONG(c, "size", 0, t_myobject, m_ob); 00078 CLASS_ATTR_ACCESSORS(c, "size", myobject_size_get, myobject_size_set); 00079 @endcode 00080 00081 Now, here is an implementation of the custom setter for the array size. For the setter, we use the handy Max API function sysmem_resizeptr so we can effectively "resize" our array and copy the data into it in one step. The setter uses atoms, so we have to obtain the value from the first item in the argv array. 00082 00083 @code 00084 t_max_err myobject_size_set(t_myobject *x, t_object *attr, long argc, t_atom *argv) 00085 { 00086 long size = atom_getlong(argv); 00087 00088 if (size < 0) // bad size, don't change anything 00089 return 0; 00090 00091 if (x->m_data) 00092 x->m_data = (long *)sysmem_resizeptr((char *)x->m_data, size * sizeof(long)); 00093 else // first time alloc 00094 x->m_data = (long *)sysmem_newptr(size * sizeof(long)); 00095 return 0; 00096 } 00097 @endcode 00098 00099 The getter also uses atoms for access, but we are returning a pointer to an array of atoms. The caller of the getter has the option to pre-allocate the memory (passing in the length in argc and the pointer to the memory in argv) or pass in 0 for argc and set the contents of argv to NULL and have the getter allocate the memory. The easiest way to handle this case is to call the utility function atom_alloc, which will figure out what was passed in and allocate memory for a returned atom if necessary. 00100 00101 @code 00102 t_max_err myobject_size_get(t_myobject *x, t_object *attr, long *argc, t_atom **argv) 00103 { 00104 char alloc; 00105 long size = 0; 00106 00107 atom_alloc(argc, argv, &alloc); // allocate return atom 00108 00109 if (x->m_data) 00110 size = sysmem_ptrsize((char *)x->m_data) / sizeof(long); // calculate array size based on ptr size 00111 00112 atom_setlong(*argv, size); 00113 return 0; 00114 } 00115 @endcode 00116 00117 @section chapter_msgattached_receiving Receiving Notifications 00118 00119 As an alternative to writing a custom setter, you can take advantage of the fact that objects receive a "notify" message whenever one of their attributes is changed. The prototype for a notify method is as follows: 00120 00121 @code 00122 t_max_err myobject_notify(t_myobject *x, t_symbol *s, t_symbol *msg, void *sender, void *data); 00123 @endcode 00124 00125 Add the following to your class initialization so your notification method will be called: 00126 00127 @code 00128 class_addmethod(c, (method)myobject_notify, "notify", A_CANT, 0); 00129 @endcode 00130 00131 The notify method can handle a variety of notifications (more documentation on this is coming soon!), but the one we're interested in is "attr_modified" -- the notification type is passed to the notify method in the msg argument. Here is an example of a notify method that prints out the name of the attribute that has been modified. You could take any action instead. To obtain the name, we interpret the data argument to the notify method as an attribute object. As an attribute is a regular Max object, we can use object_method to send it a message. In the case we are sending the message getname to the attribute object to obtain its name. 00132 00133 @code 00134 t_max_err myobject_notify(t_myobject *x, t_symbol *s, t_symbol *msg, void *sender, void *data) 00135 { 00136 t_symbol *attrname; 00137 00138 if (msg == gensym("attr_modified")) { // check notification type 00139 attrname = (t_symbol *)object_method((t_object *)data, gensym("getname")); // ask attribute object for name 00140 object_post((t_object *)x, "changed attr name is %s",attrname->s_name); 00141 } 00142 return 0; 00143 } 00144 @endcode 00145 00146 00147 */
Copyright © 2008, Cycling '74