t e m p o r a l 
 d o o r w a y 

Using MapX With C++ Builder 3

 

Introduction

The component model of C++ Builder 3 differs in significant respects from that of C++ Builder 1. You will need to be prepared to make a number of changes in your handling of MapX in the new version.

Run-Time Packages

The new component model allows you to develop each component in a separate package. A package is essentially a .dll, but it produces a .bpl file. The .bpl file must be on the search path as defined by the PATH environment variable. This document does not presume you use run-time packages, but if you do, you must have the directory used for the MapX package in the PATH, the appropriate option needs to be checked in the project options, and you must remember to deploy the .bpl file to your users.

Getting Ready

  1. Install C++ Builder 3.
  2. Get the patch from Inprise. This is essential because of a fix which is required to properly install MapX in C++ Builder.
  3. Install any non-ActiveX controls.

Preparing For Migration

Migration of existing applications will be much more painless if you use the Decorator or Proxy pattern for developing a mapping component based on MapX. This pattern should localize all of the dependencies on exported data objects and methods (as well as all of the MFC classes) into a single component, simplifying your changes when you migrate. I would recommend taking this step, converting your applications to work with the new component, and then performing the migration.

Installing MapX

As the patch documentation instructs, you will need to run a special utility from the command prompt to create the appropriate type library header and body. Do not use the IDE Project | Import Type Library or Component | Import ActiveX Control. These work with the old type library generator and will not create the appropriate files. If you get a compiler message "Not an allowed type" you have used one of these options to create the type library files, and should go back and regenerate them using the utility as instructed in the patch documentation. Fortunately, you only have to do this once.

The type library files will be in ($BCB)\lib as MapXLib_TLB.*

Compatibility Note - Unfortunately, the system will persist in attempting to call the resulting class TMapProxy. If you have controls or projects from C++Builder 1, where the class was named TMap, this will be a problem. You must, therefore, edit the .h and .cpp and replace TMapProxy with TMap to maintain compatibility. Note - even with this change, the include file names will require changing in your old projects. There may be other problems with legacy applications. I will update this as I get to that stage.

At this point you need to create the package. To do this, use Component | Import ActiveX Control. Pick the MapX Control. change the name of the class from TMapXProxy to TMap if you are trying to stay compatible with your C++ Builder 1 installation. Click OK. Do not regenerate the TLB files, as this will destroy the result of your having used the utility mentioned above.

You will be asked whether to create a new package or add it to the current package. You should create a new package. I recommend creating a separate directory for this package, since it will also create a package project (.bpk) and a variety of other files. These files are separate from but use the TLB files previously created. I use a directory structure of drive:develop\cbuilder\activex\mapx for this package

Once the package is created, you will see the unit file and you will be told that the package will be built and then installed. Click Yes.

If everything is OK, the control is now on the ActiveX page, ready for use.

If Something Goes Wrong

If you made an error, for instance forgetting to rename the class in the TLB files, use Component | Install Packages to remove the control. Make changes. Recompile the TLB if it changed. Rebuild the package. Use Component | Install to install the package. The fix is complete.

Don't Forget

If you use run time packages, you must deploy the .bpl. Don't forget to add your package directory to your PATH environment variable.

Referencing Things

There have been a few changes when it comes to accessing parts of your map. Some of these are very nice. You now have property-style access to datasets, layers, etc. But to access these, you need to understand what was generated.

There is the class you are using. In this case, TMap or TMapProxy.

Then there is an IDispatch interface, accessed from the TMap with the ControlInterface property. So if you need the Layers collection you use myMap->ControlInterface.Layers_ (note the trailing underscore - there are a few funky items like that, though most have normal names without underscores). Note that ControlInterface returns a MapXInterface object, not a pointer, so you don't use -> to access its members.

You can still use the MFC interfaces to MapX. Just cast the object returned from whatever ControlInterface property you access. For instance:

CMapXDatasets Datasets((LPDISPATCH) (IDispatch *) myMap->ControlInterface.Datasets_);

to initialize a datasets collection in MFC.

C++ Builder 3 comes with its own nafxcw.lib which you need to include in the package or project. You will also need to apply modifications to the MFC header files. There are some differences, however, when you modify these new header files. Here are the complete changes, including those mentioned in the above linked document:

Make sure ($BCB)\include\mfc is on your include path.
Make sure to add ($BCB)\lib\nafxcw.lib to your package or project.
comment out
#ifdef _WINDOWS_
#error WINDOWS.H already included. MFC apps must not #include <windows.h>
#endif
in afxv_w32.h
comment out
struct HKEY__;
typedef struct HKEY__ *HKEY;
at line 139 of afxv_w32.h
comment out
BOOL DrawState(CPoint pt, CSize size, HICON hIcon, UINT nFlags, HBRUSH hBrush = NULL);
at line 804 of afxv_w32.h
comment out
_AFXWIN_INLINE int CDC::EnumObjects(int nObjectType, int (CALLBACK* lpfn)(LPVOID, LPARAM), LPARAM lpData) { ASSERT(m_hAttribDC != NULL); return ::EnumObjects(m_hAttribDC, nObjectType, (GOBJENUMPROC)lpfn, lpData); }
at 560 in afxwin1.inl
in afxwin.h change BEGIN_MESSAGE_MAP and END_MESSAGE_MAP to CBEGIN_MESSAGE_MAP and CEND_MESSAGE_MAP to eliminate conflicts between the definition of these macros in VCL and MFC
afxres 238 comment out #define ID_HELP 0xE146 // first attempt for F1
afxmsg_h 129 comment out #define CN_COMMAND 0

Using The ActiveX Interface Rather Than The MFC Interface

In C++ Builder 1, it was required that we use the Microsoft Foundation Class interface to access the properties and methods of the MapX control. The greater sophistication of C++ Builder 3 allows for more direct access to all of the properties and methods of an ActiveX control. These properties and methods are now as accessible as those of any other control.

However, the complexity of ActiveX in general and MapX in particular require some special steps on the part of the developer.

First, you need to understand the basics of ActiveX.

ActiveX is a framework which prescribes a specific interface to a DLL. That framework is designed to allow the properties and methods exported by that DLL to be resolved at run time, rather than compile time. To that end, every ActiveX control provides two special interfaces - IDispatch and IUnknown. The IUnknown interface allows a control to be queried as to the name, identifier and data types of properties and the name, identifier, and parameters of methods. This is the interface which is called upon by C++ Builder when the control is installed into the component palette, and it is this information and the information in the .tlb file (a type library) that allows the construction of the appropriate .h and .cpp files.

The IDispatch interface is the interface which is accessed by the .h and .cpp file methods to get and set property values or cause actions to be performed by the control. The basic interpretation of the control by C++ Builder is that there is the control itself (TMapXProxy) and that there is a dispatch interface, which must be initialized using the pointer to the control (MapXDisp). Several basic properties and methods are provided directly by the control, but many others (such as setting the map title) are performed by the Dispatch interface.

To avoid having to deal with this duality, I strongly recommend that you create a descendant of the control (TMapProxy) which will be the platform from which you will offer a combination of the features provided directly by the control and indirectly through the Dispatch interface.

This class needs to declare a private or protected variable MapXDisp MapInterface; Note: This is NOT "CMapXDisp" - it is not an MFC class.

This variable should be initialized in the constructor as follows:

DefaultDispatch->QueryInterface(::DIID_MapX, (LPVOID*)&MapInterface);

The ::DIID_MapX constant is declared as follows:

extern "C" const GUID DIID_MapX = {0xBB5C2B21, 0xF30E, 0x11D0,{ 0x9D, 0xB6, 0x00, 0xAA, 0x00, 0xA4, 0x78, 0xBC} };

This is derived from the MapXLib_TLB.cpp file. This is required because otherwise the constant is considered an unresolved external reference at the time the component is linked into its package.

The next issue is providing a property to the user of the derived control. The following header file declaration provides the TitleText property (this overrides the property provided by the control itself, which is not safe to assign values to, for some unknown reason).

String __fastcall GetTitleText(void);

void __fastcall SetTitleText(String theTitleText);

__property String TitleText = {read=GetTitleText,write=SetTitleText};

The content of those getter and setter methods is as follows:

//---------------------------------------------------------------------------
String __fastcall YourDerivedMapX::GetTitleText(void) // Modeled after the functions in MapXLib_TLB for TMap
{
   return OleStrToString(MapInterface.get_TitleText());
}
//---------------------------------------------------------------------------
void __fastcall YourDerivedMapX::SetTitleText(String theTitleText)
{
 MapInterface.set_TitleText(StringToOleStr(theTitleText));
}

As you can see, it is necessary to look at the MapXLib_TLB.h and .cpp files to know which routines to call.

You may consider this to be an excessively complex process. However, it works.

If you need to access a property from the Dispatch interface which is itself a complex class with a Dispatch interface, you should follow the same pattern described above for the map interface in those classes (create a descendant class, initialize from the class, initialize a Dispatch pointer, and use the Dispatch pointer internally to provide properties and methods from the Dispatch interface).

Problems With Control Properties

Some of the control properties (such as the map bounds rectangle) cause access violations because C++ Builder has not properly initialized the map control internal IDispatch pointer. In this case you can either modify the getter / setter method to call CreateControl() before the access of the property, or you can use the technique described above (which is probably the preferred way of handling the prospect).

Copyright © 2004 by Mark Cashman (unless otherwise indicated), All Rights Reserved