Questions about MDL memory management pop up every now and again on the Be Community Forums. This article provides some hints.
MDL memory management can be confusing. If you are writing a pure MDL application (i.e. not C++) then you have to use C-style memory allocation and deallocation. If you are using some function in the MDL API, however, you are receiving memory allocated using MDL's internal allocator. If you have advanced to writing a DLL using C++, then you can choose between C-style, MDL-style, and C++ style memory management. For the MDL developer, such a bewildering variety is the staff of life? These articles attempt to help sort out the confusion.
Q There seem to be several ways of managing memory when writing an MDL application. I'm confused!
Prefer the C++ Standard Library. Explore the Boost extensions.
If you are writing C++, then use the C++ Standard Library.
In particular, use the Standard Library collections such as vector, list and deque.
Why? Because the Standard Library goes a long way to relieving you of all memory management issues.
Using std::vector provides you with an elastic array of whatever data structure you want.
As the array grows it manages memory allocation for you; as you remove elements from the array, it deallocates memory for you.
Here's an example of std::vector in a MicroStation context.
Suppose you want to build a list of DPoint3d vertices,
and use that list to construct a line string element.
Using the Standard Library, you would do something like this to build a vertex list …
#include <vector>// Standard Library header for vector template classstd::vector<> pointList; // Elastic array of pointsDPoint3d point;// Initialise a point and add it to the listmdlVec_fromXYZ (&point, 1, 1, 0); pointList.push_back (point);// More points ...mdlVec_fromXYZ (&point, 10, 12, 0); pointList.push_back (point); mdlVec_fromXYZ (&point, 20, 25, 0); pointList.push_back (point); mdlVec_fromXYZ (&point, 30, 50, 0); pointList.push_back (point);// List contains 4 pointsASSERT (4 == pointList.size ());
If you want to see what's inside your pointList, you can iterate it just like a normal array …
char msg [128];
for (int i = 0; i < pointList.size (); ++i)
{
sprintf (msg, "Array member [%d] = %.1lf, %.1lf", i, pointList [i].x, pointList [i].y);
mdlOutput_messageCenter (MESSAGE_DEBUG, msg, msg, FALSE);
}
And when you've finished using your pointList, you simply stop caring about it. When it goes out of scope, <vector> cleans itself up and deallocates memory. For more information on using the C++ Standard Library with your MDL code, look at MDL and the C++ Standard Library.
The above code fragments build a vector of DPoint3d structures.
That process uses memory allocation — but we neither know nor care how it was done, because the Standard Library handles that for us: efficiently, correctly, and invisibly.
When we've finished using the std::vector, we don't need to concern ourselves with memory deallocation — it happens automatically.
Boost is the group of C++ heroes who add enormous value to the C++ Standard Library. The code they develop sometimes makes its way into the Standard Library. Read more about Boost.
A
The MDL API conforms to C, not C++. There is no MDL parallel to new and delete,
so the Microsoft compiler has no problem linking to its built-in functions.
C++ provides the [] operator for array allocation and deallocation, which is an improvement on C.
enum ArrayControl { ArraySize = 10, };
DPoint3d* pArray = new DPoint3d [ArraySize];
if (NULL == pArray)
{
// Memory allocation failed!
}
else
{
char msg1 [128];
char msg2 [128];
for (int i = 0; i < ArraySize; ++i)
{
// Initialise members
(pArray + i)->x = i * 1.234, (pArray + i)->y = i * 2.345, (pArray + i)->z = 0.0;
// Demonstrates two ways to get at member data
sprintf (msg1, "Array member [%d] = %.1lf, %.1lf", i, (pArray + i)->x, (pArray + i)->y);
sprintf (msg2, "Array member [%d] = %.1lf, %.1lf", i, pArray [i].x, pArray [i].y);
mdlOutput_messageCenter (MESSAGE_DEBUG, msg1, msg2, FALSE);
}
delete [] pArray;
}
A
Use the C standard language malloc, calloc allocator and the free deallocator functions.
Avoid realloc: it causes more problems than it cures.
enum ArrayControl { ArraySize = 10, };
DPoint3d* pArray = calloc (ArraySize, sizeof (DPoint3d));
if (NULL == pArray)
{
// Memory allocation failed!
}
else
{
char msg1 [128];
char msg2 [128];
for (int i = 0; i < ArraySize; ++i)
{
// Initialise members
(pArray + i)->x = i * 1.234, (pArray + i)->y = i * 2.345, (pArray + i)->z = 0.0;
// Demonstrates two ways to get at member data
sprintf (msg1, "Array member [%d] = %.1lf, %.1lf", i, (pArray + i)->x, (pArray + i)->y);
sprintf (msg2, "Array member [%d] = %.1lf, %.1lf", i, pArray [i].x, pArray [i].y);
mdlOutput_messageCenter (MESSAGE_DEBUG, msg1, msg2, FALSE);
}
free (pArray);
}
A Use the MDL memory management functions instead of your compiler's built-in functions.
The MicroStation® Development Library (MDL) is a comprehensive application programmer's interface (API)
that conforms to ANSI C.
A developer may write code in an MDL (.mc) file and compile it using Bentley's compiler (mcomp.exe),
or write code in native code (.c or .cpp) files and use Microsoft's compiler.
Memory management is an issue because an MDL application executes within MicroStation's address space. If memory is allocated and deallocated by API functions, everything works fine. However, if you want to consume a resource in native code that has been allocated by MicroStation, or you create a resource in native code that is subsequently consumed by MicroStation, a problem arises.
The problem exists because internally MicroStation uses its own malloc(), calloc() and free() definitions.
If you call, for example, malloc(), in native code, the Microsoft compiler (quite justifiably) links
with its own definition of the function. When you come to run your application,
MicroStation exits ungracefully: Microsoft's malloc() has allocated memory on one heap and
Bentley's free() has attempted to deallocate from another heap, or vice versa.
MDL provides a solution to this problem, through a set of memory management functions …
dlmSystem_mdlMalloc allocates a block of memory of the specified size in bytesdlmSystem_mdlCalloc allocates memory for the specified number of objects of the given sizedlmSystem_mdlRealloc reallocates a block of memory to the specified size in bytesdlmSystem_mdlFree frees memory allocated by dlmSystem_mdlMalloc, dlmSystem_mdlCalloc or dlmSystem_mdlReallocFor a full definition of each function, see the MDL API documentation.
So there you have it: use one of the above functions in place of the malloc() or calloc()
you would normally use, and memory management problems evaporate!
If only life were so simple. I've written a prototype application in MDL (.mc): now I want
to move it to a DLL that I plan to write using VC++. What happens to memory management function calls?
Bentley's bmake.exe make-file processor defines a macro mdl before it invokes
mcomp.exe. You can check for mdl in a pre-processor macro, and call the
appropriate memory-management function …
#if defined (mdl)
# define MALLOC(p,T) \
p = (T*)malloc (sizeof (T));
#else
# define MALLOC(p,T) \
p = (T*)dlmSystem_mdlMalloc (sizeof (T));
#endif
Now you can write code that uses the MALLOC macro, which calls either
malloc or dlmSystem_mdlMalloc depending on whether mdl is defined …
DPoint3d* pt = NULL; MALLOC (pt, DPoint3d);
I've created a family of macros that test for mdl before invoking the built-in or native code allocator.
Here's some example usage.
Allocate memory for a single object …
// Allocate memory for a single object DPoint3d* pt = NULL; MALLOC (pt, DPoint3d); // Do something with pt // ... FREE_MALLOC (pt);
Allocate memory for multiple objects …
// Allocate memory for multiple objects
int i = 0;
enum ArrayControl { nVertices = 10, };
DPoint3d* vertices = NULL;
CALLOC (vertices, nVertices, DPoint3d);
// Do something with vertex array
for (i = 0; i < nVertices; ++i)
{
vertices [i].x = 10.0 * i;
vertices [i].y = 20.0 * i;
vertices [i].z = 10.0;
}
FREE_MALLOC (vertices);
/* Memory allocation macros */
#if defined (mdl)
# define MALLOC(p,T) \
p = (T*)malloc (sizeof (T)); \
ASSERT (NULL != p);
#else
# define MALLOC(p,T) \
p = (T*)dlmSystem_mdlMalloc (sizeof (T)); \
ASSERT (NULL != p);
#endif
#if defined (mdl)
# define CALLOC(p,n,T) \
p = (T*)calloc (n, sizeof (T)); \
ASSERT (NULL != p);
#else
# define CALLOC(p,n,T) \
p = (T*)dlmSystem_mdlCalloc (n, sizeof (T)); \
ASSERT (NULL != p);
#endif
#if defined (mdl)
# define ALLOCA(T) \
(T *)alloca (sizeof (T)); \
ASSERT (NULL != T);
#else
/* include */
# define ALLOCA(T) \
(T *)_alloca (sizeof (T)); \
ASSERT (NULL != T);
#endif
/* Memory deallocation macros */
#if defined (mdl)
# define FREE_MALLOC(p) \
if (NULL != p) { \
free (p); \
p = NULL; \
}
#else
# define FREE_MALLOC(p) \
if (NULL != p) { \
dlmSystem_mdlFree (p); \
p = NULL; \
}
#endif