MapObjects Connection

 

MapObjects Tech Note 101: Understanding Memory Allocation when using MapObjects from C++

Last Update: 15/Feb/99

You 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 Classes

MapObjects 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 Allocated

Normally 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 objects

Most 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());
   // constructor method
r.AttachDispatch(m_map.GetExtent());
   // AttachDispatch method

COM Programming 101: AddRef and Release

Since 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());
if (LPDISPATCH(r))
m_map.SetExtent(r);

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();
if (r)
{
m_map.SetExtent(r);
r->Release();
}

Creating new Automation objects

So 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;
   // equivalent to LPDISPATCH m_lpDispatch;
r.SetLeft(0.0);
   // equivalent to m_lpDispatch->Invoke();
   // protection violation: using uninitialized pointer

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 objects

The 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.
   // Ask the OLE subsystem to look up the CLSID using the ProgID
CLSID clsid;
CLSIDFromProgID("MapObjects2.Rectangle", &clsid);
   // Use the class factory to create a new rectangle and return
   // an IUnknown interface pointer to it.
LPUNKNOWN pUnknown = 0;
CoCreateInstance(clsid, 0, CLSCTX_ALL, IID_IUnknown, &pUnknown);
   // Get the IDispatch interface pointer for the rectangle
LPDISPATCH pDispatch = 0;
pUnknown->QueryInterface(IID_IDispatch, &pDispatch);
   // Follow the AddRef/Release convention and give up our pUnknown pointer
pUnknown->Release();
   // pDispatch is now ready to use.

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;
    // equivalent to LPDISPATCH
m_lpDispatch;
r.CreateDispatch("MapObjects2.Rectangle");
   // create object, get IDispatch pointer,
   // assign to m_lpDispatch
r.SetLeft(0.0); // equivalent to m_lpDispatch->Invoke();
   // works this time!