AUTOMATING COLLECTIONS Frequently an application organizes data and objects in collections of various types, such as arrays, containers, or linked lists, or it utilizes such capabilites inherent in the operating platform. Collections generally expose an iterator, a counter, a random-access function, and optionally methods for managing the collection. Exposing collections for automation generally involves three steps: 1. The collection is exposed as a property which returns an object. 2. A C++ class is created, or enhanced, to support the collection methods. 3. An iterator is defined to provide code to iterate through the collection. The iterator is internally exposed to the script controller as a property having the reserved name "_NewEnum" which returns an object supporting the IENUMVariant interface. This interface contains methods to perform iteration. With VisualBasic for Applications, the following syntax is used for iteration: For Each Thing In Owner.Bunch ("Thing" is an arbitrary iterator name) Thing.Member...... (can access methods and properties) Next Thing (loops through all items in collection) Note that the server can return any data type for items, values or objects. CREATING C++ CLASSES SPECIFICALLY FOR AUTOMATION Frequently C++ classes do not already exist to encapsulate items to be exposed for automation, such as collections, simple structures, and system objects represented by handles. The only C++ methods required are a constructor and support methods for the exposed automation. Instances of these classes are exposed as properties of their parent classes and are constructed as the properties are retrieved (as opposed to returning references to existing C++ instances). The constructor must accept a single argument from the parent class: member data, result of an accessor function, or the object's this pointer. The parent class must supply an AUTOxxx macro that supplies this constructor argument as a data member or function, and declares its type with the templatized pointer class TAutoObjectByVal, where T is the new automated C++ class. TAutoObjectByVal causes an instance of T to be constructed that persists until all external references to that instance are released (when the exposed object goes out of scope in the automation controller). Examples of the macros used within the DECLARE_AUTOCLASS and DEFINE_AUTOCLASS sections are given below: AUTODATARO(Documents, DocList, TAutoObjectByVal,) // DocList is a TDocument*, head of a linked list of. // type TDocument. TDocumentList is a new class, constructor: // TDocumentList(TDocument*) EXPOSE_PROPRO(Documents, TDocumentList, "Documents", "Doc Collection", 270) AUTOTHIS(Views, TAutoObjectByVal,) // TViewList is a new class, constructor: TView(TDocument* owner) // In this case the parent's this pointer is passed to the constuctor EXPOSE_PROPRO(Views, TViewList, "Views", "View Collection", 240) AUTOFUNC0(Buttons, GetWindow(), TAutoObjectByVal, ) // GetWindow() gets the window handle of the parent window of // the buttons. TCalcButtons is a new class, constructor: // TCalcButtons(HWND parent) EXPOSE_PROPRO(Buttons, TCalcButtons, "Buttons", Button Collection", 170) AUTODATARO(MyArray, Elem, TAutoObjectByVal,) // Elem is an array of shorts, defined as short Elem[COUNT] // TMyArray is a new class, constructor: TMyArray(short* array) EXPOSE_PROPRO(MyArray, TMyArray, "Array", "Array as collection", 110) COLLECTION CLASSES A C++ class must be defined to host the automation declarations along with the supporting C++ methods. For some collections, a C++ class might already exist. Otherwise one must be defined to represent a collection object. EXPOSING COLLECTION OBJECTS EXPOSING ITERATORS Iterators are defined for automation using the AUTOITERATOR macro, which defines the iteration algorithm for the enclosing collection class. No internal name is supplied, as only one iterator may exist within a class. The macro has five arguments, each representing a code fragment, ordered as in a "for" loop. 1. Declaration of state variables, e.g. int Index 2. Loop initializer assigments, e.g. Index = 0 3. Loop entry test boolean expression, e.g. Index < This->Total 4. Loop iteration statements, e.g. Index++ 5. Current element access expression, e.g. (This->Array)[Index] Commas cannot be used unless inside parentheses. Semicolons can be used to separate multiple statements, but cannot be used to end a macro argument. In common with automated methods, "This" is defined to be the "this" pointer of the enclosing C++ class, in this case the collection itself. The AUTOITERATOR macro generates a nested class definition. For complex iterators, this class can be specified directly in C++ as shown below: class TIterator : public TAutoIterator { public: ThisClass* This; /* declare state variables here as members */ void Init() {/* loop initialization function body */} bool Test() {/* loop entry test function body */} void Step() {/* loop iteration function body;} void Return(TAutoVal& v) {/* current element return: v = expr */} TIterator* Copy() {return new TIterator(*this);} TIterator(ThisClass* obj, TServedObject& owner) : This(obj), TAutoIterator(owner) {} static TAutoIterator* Build(ObjectPtr obj, TServedObject& owner) { return new TIterator((ThisClass*)obj, owner); } }; friend class TIterator; Iterators are exposed as properties using the EXPOSE_ITERATOR macro. Note that no internal or external names are supplied (the external name is internally hard-wired as "_NewEnum"). The automation type describes the type of the items returned from the iterator, in the same manner as a function return. AUTOITERATOR(int Index, Index=0, IndexArray)[Index]) // Array is a member array of shorts for which an iterator // is defined. It will exposed as a "_NewEnum" property which // returns an OLE enumerator. EXPOSE_ITERATOR(TAutoShort, "Array Iterator", HC_ARRAY_ITERATOR) In addition to exposing an iterator, a collection class by convention exposes a "Count" method to return the number of items in the collections, an "Index" method to randomly access an element of the collection, and optionally, methods to externally manage the collection, such as add and delete. ____________________________________________________________ ----example code from AutoCalc------ class TCalcButtons { public: TCalcButtons(HWND window) : HWnd(window) {} short GetCount() { return IDC_LASTID - IDC_FIRSTID; } HWND HWnd; DECLARE_AUTOCLASS(TCalcButtons) AUTOFUNC0 (Count, GetCount, short,) AUTOITERATOR(int Id, Id = IDC_FIRSTID+1, Id <= IDC_LASTID, Id++, TAutoObjectByVal(::GetDlgItem(This->HWnd,Id))) }; DEFINE_AUTOCLASS(TCalcButtons) EXPOSE_PROPRO(Count, TAutoLong, "!Count","Button Count", HC_TCALCBUTTONS_COUNT) EXPOSE_ITERATOR(TCalcButton, "Button Iterator", HC_TCALCBUTTONS_ITERATOR) EXPOSE_METHOD_ID(0, Item, TCalcButton,"!Item", "Button Collection Item", 0) REQUIRED_ARG(TAutoShort, "!Index") END_AUTOCLASS(TCalcButtons, "ButtonCollection", "Button Collection", HC_TCALCBUTTONS)