MapObjects Tech Note 101: Understanding Memory Allocation when using MapObjects from C++Last Update: 15/Feb/99You are probably familiar with using 3rd party libraries such as MFC or the standard C library with C++. These packages come in the form of static libraries or DLLs. They are simply linked into your application and the routines within them are called just like the routines you write yourself. Many times you need to be careful so that memory allocated by a library is also released by the library. The libraries generally use the WIN32 memory allocation routines or some higher level memory manager built on top of WIN32. MapObjects is similar in that it's a class library written in C++, It's packaged in a "DLL" (OCXs are a form of DLL), and it's based on a specialized memory manager, the one defined by OLE/COM (Object Linking and Embedding is built on the foundation of the Component Object Model). MapObjects is different though because instead of being linked directly to your application, it's accessed via OLE using the control and automation interfaces. Fortunately Microsoft has written some MFC classes that make it fairly easy to work with OLE controls and automation objects from C++. In fact, except for a couple of issues dealing with memory management, MapObjects objects look very similar to C++ objects. Wrapper ClassesMapObjects defines its properties, methods, and events in a type library embedded in the MOxx.OCX file. Visual C++ is capable of reading the type library and generating a set of C++ classes that allow you to easily work with MapObjects. These classes are known as wrapper classes since they wrap pure automation and control objects with classes derived from COleDispatchDriver and CWnd respectively. The wrapper classes make the MapObjects objects look just like C++ objects with one simple exception. The way you manage memory for these objects is very different from the familiar new/delete or malloc/free schemes normally used. Memory is managed the OLE/COM way as described in any good OLE programming book. The following sections discuss managing memory in MapObjects applications and provide the information needed to successfully mange your map objects. Further information can be found in the documentation for MFC's COleDispatchDriver class. How Map Controls are AllocatedNormally when using MapObjects in an application, you don't need to worry about allocating and deallocating the map control since this happens automatically when the map is embedded on some form in the application. If you want to create a map control from scratch, make sure to use the MFC CWnd::CreateControl routine. In either case, form-based or dynamic, deallocation is handled automatically when the control window is destroyed. Working with existing Automation objectsMost of the
objects that MapObjects defines are automation objects that can
be accessed using a COleDispatchDriver based wrapper class. When
you are working with a map, many of the objects already exist and
you simply need a pointer to the existing object to work with it.
In the case of automation objects, the pointer you need is the
IDispatch pointer. You may be wondering "where do I get an
object's IDispatch pointer in the first place?" It's a good
question. Look In MapObjects.h. Wherever you see a method or
property returning LPDISPATCH, you are getting back a pointer to
an existing object's IDispatch interface. Unless you're a COM
programming guru, the LPDISPATCH pointer by itself isn't of much
use to you. This is where the wrapper classes come in handy. By
attaching a wrapper class to the IDispatch pointer, you can use
the object like any other C++ object. A wrapper is attached to an
automation object by setting the wrapper's m_lpDispatch member to
the IDispatch pointer for the object. Take care not to do this
manually, instead, use one of the following techniques. Either
pass the IDispatch pointer to the wrapper's constructor, or pass
the IDispatch pointer to the wrapper's AttachDispatch() method.
For example: CMoRectangle r(m_map.GetFullExtent());
COM Programming 101: AddRef and ReleaseSince MapObjects objects are really automation objects and since automation objects are really COM objects. You need to know a little about COM's reference counted memory management scheme. Here is some Microsoft documentation to get the ball rolling: "Objects use a reference counting mechanism to ensure that the lifetime of the object includes the lifetime of references to it. You use IUnknown::AddRef to stabilize a copy of an interface pointer. It can also be called when the life of a cloned pointer must extend beyond the lifetime of the original pointer. The cloned pointer must be released by calling IUnknown::Release." By convention,
routines that return a COM interface pointer for other
objects to use must increase the COM object's reference count by
one. In turn, routines that receive an interface pointer
from some other object are responsible for decrementing the
reference count by calling Release(). Carefully following this
convention results in no memory leakage in your applications.
MapObjects follows this convention and all LPDISPATCH pointers
that it returns are properly AddRef'd. It's very important to
remember that whenever you receive one of these pointer's you are
responsible for calling pointer->Release() when you are
finished using it. Fortunately,
the MFC wrappers give us help in this area. COleDispatchDriver's
destructor takes care of the Release call for you. So, if you
remember to wrap any call that returns LPDISPATCH with one of the
wrapper classes, you will never have to worry about releasing
memory. If you forget to wrap an LPDISPATCH return, for example
like in the following code: m_map.SetExtent(m_map.TrackRectangle());
The code will
work but when your application exits, you will leak the memory
allocated for the rectangle returned by TrackRectangle. The
following code avoids the leak by relying on the
COleDispatchDriver superclass to release the automation
rectangle: CMoRectangle r(m_map.TrackRectangle());
When r goes
out of scope, the IDispatch pointer returned by TrackRectangle is
released. Alternatively, the following code is also
"correct" with respect to memory management:
LPDISPATCH r = m_map.TrackRectangle();
Creating new Automation objectsSo far, we've
only talked about using existing objects. But, there are times
when you need to create new objects from scratch. In C++ you do
this either by declaring a new object like this, "int
I;", or by allocating a new object like this, "int*
pInteger = new int;". Automation objects are always
referenced via their IDispatch pointer. As a result, you
can't simply declare new automation objects and start working
with them. The following code would be nice but it won't work: CMoRectangle r;
If you study the comments in the previous code, you can see that before a wrapper class can be used, a new object must be allocated and attached to the wrapper, i.e., its IDispatch pointer assigned to the m_lpDispatch member. COM Programming 102: Creating new objectsThe starting
point for working with COM objects is always a class ID. Every
COM object has a unique class ID (it's simply a 32 digit number
that was specially generated to ensure uniqueness). The class IDs
for the OLE objects installed on your system are recorded in the
registry under HKEY_CLASSES_ROOT/CLSID. OLE keeps track of
objects by their class ID. Each object in MapObjects has one of
these unique IDs. Since 32 digit numbers are not very easy to
remember, each object in MapObjects also has a programmatic ID
which is a simple text name for the object. These also are found
in the registry. Look under HKEY_CLASSES_ROOT/MapObjects.*. The
entries you'll find define the programmatic IDs and associate
each one with the corresponding class ID. To create a
new automation object and get an IDispatch pointer for it, simply
do the following using COM (error handling and UNICODE conversion
have been left out for the sake of clarity): // Start with a ProgID. In this case we'll create a rectangle.
Fortunately,
you don't need to do this every time you want to create a new
MapObjects object. Once again the COleDispatchDriver class gives
us a hand with the CreateDispatch method. It carries out all of
the steps necessary to create a new object, get the object's
IDispatch pointer, and assign the pointer to the wrapper's
m_lpDispatch member. Using this new method, we can add one line
to our previous example to make the code work correctly:
CMoRectangle r;
|