Writing AutoCAD Macros with VBA
- Components and Automation
- Creating a Drawing with Visual Basic
- More About Components
- Method, Property, and Event Matrices
- Summing Up
All consistent axiomatic formulations of number theory include undecidable propositions. Kurt Gödel
It is fitting, I suppose, that having begun my professional life as an architect, I continue to look for the elegance of structural details. Later in this book you will find this predilection reflected in examples demonstrating the use of AutoCAD objects. Buckminster Fuller's vector equilibrium, a deceptively simple structure defined by the close packing of spheres, is almost infinitely scalable in the form of the geodesic dome. In Chapter 9 we use it to demonstrate AutoCAD's PolyfaceMesh entity.
In his book Gödel, Escher, Bach: An Eternal Golden Braid, Douglas Hofstadter discusses metamathematics in relation to music, visual art, and computer programming. The common thread in his dialogues is that understanding such systems generally requires jumping out of them in order to view them from a higher level. This is suggestive of an ancient model of creation, in which this world of action is contained within another, that of formation. AutoCAD's object model is built upon a similar and ingenious organizational foundation, the Component Object Model (COM). In the domain that is the subject of this book, the center of that next-higher shell has the appropriate name IUnknown.
The development of integrated solutions in Visual Basic and VBA depends on COM. The AutoCAD 2002 object model is constructed according to the rules of COM, which provides the shell in which it operates. A short course in COM may not be what you signed on for here, but understanding its basic concepts is important if you really want to take control of AutoCAD and make it work with other applications. Its fundamentals are simpler than you might imagine.
Components and Automation
COM establishes a standardized means by which one piece of software can call upon another for services. A server application shares its objects with other applications. Conversely, a program that uses other applications' objects is called a client application. This sharing of objects is accomplished through the COM technology known as Automation.
As Figure 1-1 illustrates, AutoCAD and Excel can fill the role of either client or server in VBA. The client application is the one that is launched by the user, which then calls upon the objects in the server application through COM interfaces. The components from both object models are then executed in-process with the client application. We will see examples of both configurations in later chapters.
Figure 1-1 COM Automation
By contrast, a Visual Basic application executes in its own memory space but calls upon the object models of other applications through the same COM interfaces. This is not to say that a VBA procedure cannot access more than one server application. It can. But a VB application runs independently, out-of-process, as an EXE program. In either case the actual components are located in dynamic link libraries (DLLs) or ActiveX controls.
Since the focus of this book is on AutoCAD VBA macros, most of the examples we present are written in that context. Later in this chapter, though, we look at a short VB program that passes information from Excel into AutoCAD without either application ever being visible. First, however, let's delve a little more deeply into COM.
The Foundation
To understand how Automation works, we need to look above the AutoCAD object model (which is the subject of Chapter 4) to see how COM-enabled applications communicate.
Characteristics of Objects
First, what is an Object? Objects are fundamentally regions of computer memory. A particular object is a specific region of memory with a name, a defined set of code and data (the object's attributes), and an interface. Each object is an instance, a specific occurrence of a (general) class. When an object is created, it is said to be instantiated from its class. Each object in C++, the language in which AutoCAD itself is now written, supports a single interface with a single set of methods. A COM object, on the other hand, has multiple interfaces, each set of which is identified by a different Class.
We speak of computer languages as being object-oriented. In addition to creating objects made up of methods and data, then organizing them according to classes, object orientation requires that three additional characteristics be present. Inheritance is one of them. COM objects support interface inheritance, which allows a child object to build on the features of the parent object, making them specific. In AutoCAD, for example, a Line is a special case of an Entity. But there is more to this hierarchy, as we shall see momentarily.
The second characteristic, polymorphism, allows a single object to appear in different guises at different times. COM allows Visual Basic objects to implement multiple interfaces, thus an Entity can be a Line, or it can be a Circle, or it can be a PolyfaceMesh! Moreover, COM provides for the evolution of software applications so that new functionality can be introduced without breaking old code.
The third defining characteristic of objects is encapsulation. The only way to access an object is through its methods, properties, or events. Methods are actions that you can tell the object to perform. Properties are characteristics that an object possesses, some of which you can set or modify. Events occur when an object changes its state, and you can create code that will execute when triggered by a specific event. The object's internal operation, however, is always concealed from the user in order to protect the object's data from being modified either accidentally or by design. (Visual Basic and VBA modules themselves implement another kind of encapsulation by defining procedures as being either public or private.)
Classes and Interfaces
But what, then, is a Class? A Class is a user-defined data type, an aggregation of standard data types (byte, double, string, etc.) used together for a specific purpose. COM classes are the means of defining interfaces with objects, complete with their own methods, properties, and events. An object's class defines whether the object is public and in what circumstances it can be created. Type libraries, the contents of which can be viewed using object browsers, are used to store descriptions of classes and their interfaces.
Automation Interfaces
An Automation interface, or simply interface, is a defined group of member functions through which clients communicate with component objects. It is important not to confuse the interface with the classes or objects themselves. An interface represents the functionality and expected behavior of a COM object in a definite (and permanent) manner. The uniqueness of each interface is guaranteed by its globally unique identifier (GUID), a 128-bit value assigned when the interface is initially defined. Once defined, interfaces are never changed. If a new version of an interface is required for whatever reason, a new interface is defined with its own GUID, and the old interface remains in place. Thus applications relying on the old interface can continue to function.
Binding
When you use an object in Visual Basic or VBA, you first declare it as an object and then create a reference to the object in an object variable. This process is known as binding. There are two types of binding, early and late, and as you might guess, late binding is the slower of the two. For example:
Dim xAP As Object Set xAP = CreateObject("Excel.Application")
When a variable is declared simply as object or as variant, VB/VBA does not have enough information to determine at compile time what sort of object reference the variable will ultimately contain. This determination must be made at run time, hence the term late binding.
Early binding occurs when a specific type of object is specified in the declaration, as in the following code fragment:
Dim xAP as Excel.Application Set xAP as Excel.Application
It follows, of course, that a variable declared as belonging to a specific class may only contain references to objects of that class. Whether object references are early or late bound is completely dependent on the way the variables are declared and has nothing to do with the manner of creating the objects. Use of early binding in creating the AutoCAD Application object is recommended, as the VB example later in this chapter shows.
Early binding is further subdivided into two types: vtable and DispID. Every property or method in a type library has a procedure identification number or DispID (dispatch identifier). DispID binding uses this number. If a component is represented in a type library but does not support vtable binding, VB uses the DispID during compilation to locate and bind the function.
With vtable binding, the fastest method, an offset address into a virtual function table provides direct access to the function. In general, if a client application declares object variables using explicit class names, vtable binding is assured. This is the method recommended in most circumstances and the one used by AutoCAD 2002. This is fortunate, because although you can control whether early or late binding is used by the way you declare object variables, the use of vtable versus DispID binding is controlled by the component object.
A High-Level View
Except when it comes to the question of bandwidth, it doesn't matter whether COM components are in the same place or on the other side of the planet. The terms COM and DCOM (Distributed COM) are often confused because the very concept of component implies distribution. Strictly speaking, COM becomes DCOM when network protocols replace local procedure calls. George Gilder, the pundit of the telecosm, tells us that soon we will enjoy infinite bandwidth at zero cost. Then it really won't matter!
Figure 1-2a illustrates an in-process client call with no intermediaries and therefore no overhead. Different processes that need to interact introduce some overhead because of the need to protect the processes from one another. This is the function of the operating system, which manages interprocess communication through run-time libraries while providing the required shielding. Figure 1-2b shows this link as a local procedure call (LPC).
When the client and the components reside on different machines, the COM run-time uses the operating system's security provider, together with remote procedure calls (RPCs) to generate network packets in accordance with the DCOM wire-protocol standard. This arrangement is pictured in Figure 1-2c. The only essential difference between Figure 1-2b and c is the length of the connecting fiber.
Figure 1-2 Component Object Model
Details
There are two COM interfaces above the AutoCAD object model that are essential to its operation: IDispatch and IUnknown. (Interface names begin with the letter I by convention.) These primary interfaces are located in your Windows\System subdirectory in a type library file called StdOle2.tlb.
Explicitly declared object variables provide access to an identification number called a procedure ID, or DISPID, for every property and method belonging to the object. AutoCAD's DISPIDs are found in its type library, Acad.tlb, and establish the necessary link to IDispatch through early binding. If an object variable is not explicitly declared, as an entity for example, without specifying what kind of entity, the method or property is accessed by name at run time, which is known as late binding.
IDispatch
All the interfaces in AutoCAD's object model except one, IAcadObjectEvents, inherit methods from the IDispatch interface that allow for late binding. If declared explicitly, they obtain type information from the Acad.tlb type library at compile time, supporting direct access through early vtable binding. For this reason they are said to support dual interfaces. As we have seen, the type of binding used is determined by the manner in which object variables are declared.
The IDispatch interface supports four methods:
GetTypeInfoCount
Retrieves the number of type information interfaces that the object provides (either 1 or 0); always 1 for AutoCAD objects.
GetTypeInfo
Retrieves the type information for an object, which can then be used to get the type information for an interface.
GetIDsOfNames
Maps a single member, along with an optional set of argument names, to a corresponding set of DispIDs (integers), which caches them for later use in subsequent calls to the Invoke method. GetIDsOfNames is used in late binding, when an IDispatch client binds to names at run time.
Invoke
Provides access to the methods and properties exposed by an object.
IUnknown
The IUnknown interface is quite literally the center of the COM universe. It allows clients to obtain pointers to other interfaces belonging to a given object and manages the existence of every object throughout its lifetime. All interfaces, including IDispatch, inherit from IUnknown, whose three methods constitute the uppermost entries in the vtable for all other interfaces. These three methods are as follows:
QueryInterface
Returns a pointer to the specific interface on an object to which a client currently holds an interface pointer. When a client accesses a component object to perform a function, all aspects of its internal behavior are hidden. Only through the interface pointer can the client access the functions exposed in the interface. It is this enforced encapsulation that enables COM to provide both local and remote transparency through an effective binary standard.
AddRef
Increments the reference count of calls to an object's interface.
Release
Decrements the reference count of calls to an interface on an object.
AddRef and Release together control the life spans of the objects in an executing program. This provides the mechanism by which, through inheritance, references to all components are dynamically resolved. These two methods simply maintain a count of the references to each component object while it is using the interface. As long as the reference count is greater than zero, the object must remain in memory. When the reference count decrements to zero, no other components reference the object, which can then unload safely.
Example 1-1 illustrates IUnknown. It is written in IDL (Interface Development Language), which looks something like C/C++ and nothing like Visual Basic. A distinguishing feature is the use of attributes, which are the keywords in square brackets that specify the characteristics of the interface together with the data and methods within. The standard format in IDL begins with a header containing the interface attributes followed by the body of the interface enclosed in braces (curly brackets).
Example 1-1. IUnknown
[ odl, uuid(00000000-0000-0000-C000-000000000046), hidden ] interface IUnknown {[restricted] HRESULT _stdcall QueryInterface ([in] GUID* riid, [out] void** ppvObj); [restricted] unsigned long _stdcall AddRef(); [restricted] unsigned long _stdcall Release(); };
The most significant part of IUnknown's header is its UUID, the universally unique identifier (same as GUID). This is its 128-bit ID in the form of a five-node string comprised of 8 hexadecimal digits followed by three groups of 4 digits, and finally 12 digits. Hidden suppresses the display of the item in an object browser, as it cannot be accessed directly. The ODL attribute is no longer required but remains for backward compatibility.
The body of IUnknown contains the declarations of the three remote procedures in the interface, along with their data types. The restricted keyword specifies that a method cannot be called arbitrarily. In the QueryInterface method, riid is the requested interface identifier of the client program passing data into the remote procedure. ppvObj contains the address of the pointer variable requested in riid, which is passed out of the remote procedure.
So What Does this Mean to AutoCAD?
Earlier we pointed out that all except one of the interfaces in AutoCAD's object model inherit methods from the IDispatch interface, which in turn inherits from IUnknown. Figure 1-3 uses the IAcadObject interface to illustrate how AutoCAD objects inherit the necessary methods that allow them to interoperate through COM.
Figure 1-3 Object Inheritance
IAcadObject inherits IDispatch's four methods along with the three belonging to IUnknown. These methods are thus passed down to all the objects that inherit from IAcadObject. There are 13 methods and properties to which IAcadObject provides direct access, of which you will see only 11 in the VBA object browser. The method and property designated with an H are hidden, meaning that they serve internal functions in AutoCAD and are not directly accessible. The Database property returns the database object, and the Erase property erases the entity object. These member functions, which are essential to maintaining the AutoCAD database, are inherited by virtually all the objects in AutoCAD along with the visible ones.