This article is written for programmers writing applications for Bentley Systems' MicroStation. The MicroStation Development Library (MDL) is a C-style API. The MicroStationAPI provides a C++ interface that supplements MDL.
Q C++ Containers
Q 20th Century Containers
Arrays or containers that are a relic from the last century. If you're writing C++, use the containers from the Standard Library.
Prefer the C++ Standard Library. Explore the Boost extensions. The Standard Library is part of the current C++ standard, and is supported by all compilers.
If you are writing C++, then use the C++ Standard Library.
In particular, use the Standard Library collections such as std::vector, std::list and std::deque.
Why? Because the Standard Library provides a lot of useful algorithms, and goes a long way to relieving you of memory management.
In the remainder of this article, I've omitted the Standard Library namespace prefix std.
For example, rather than write std::vector, it's easier on your eyes if I write vector.
You don't have to do anything, other than acquire a C++ compiler, to have the Standard Library. It's part of the C++ language. To put it another way, if your C++ compiler doesn't include the Standard Library, then it's not a C++ compiler. If you've read this far, and you are an MDL developer, then you will most likely be using the Microsoft development tools including Visual C++. Visual C++ version 6 and subsequent revisions include implementations of the Standard Library. If you're developing for the MicroStation V8i, then you should be using Visual Studio 2005. If you're developing for MicroStation CONNECT Edition Update 6 or later, then you should be using Visual Studio 2015.
The C++ Standard Library has been part of the C++ language for many years. Consequently, many books about C++ also discuss the Standard Library, its containers, algorithms and iterators. Visit our books page for a small list of suggestions.
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 an MDL context.
std::vector is used to create a contiguous list of data.
Because the Standard Library consists of templated classes, the data you put in a std::vector
can be pretty well any struct or class.
In the examples, we're using the MDL DPoint3d struct that you've probably used many times before.
Don't confuse std::vector with MDL vectors.
A Standard Library std::vector means an array of data.
It has nothing to do with 3D geometry.
An MDL vector means a directed line, in the
Cartesian Geometry sense.
It has nothing to do with data storage.
When using C++ templates, variable declarations can become hard to read, due to the proliferation of < and >,
which tend to generate lines of code that look like a cat just walked across your keyboard.
It becomes tedious typing std::vector<MyDataStruct>, and prone to error.
Use a typedef to clarify your code and reduce typing errors.
For example, in the code below we use a std::vector of DPoint3d structs.
The syntax to declare a variable of that type is:
std::vector<DPoint3d> variable;
Minimise the keyboard challenge and clarify your code!
Use typedef to make your own reusable, easy to type, classes:
typedef std::vector<DPoint3d> DPoint3dCollection;
Now, wherever you had previously to type std::vector<DPoint3d>,
you can use DPoint3dCollection. Here's the previous variable declaration using your new type:
DPoint3dCollection variable;
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 …
#include <vector>// Standard Library header for vector template classtypedef std::vector<DPoint3d> DPoint3dCollection;// typedef clarifies subsequent codeDPoint3dCollection pointList;// Elastic array of DPoint3d// Initialise some points and add to the listAddPoint (pointList, 1, 1); AddPoint (pointList, 10, 12); AddPoint (pointList, 20, 25); AddPoint (pointList, 30, 50);// List contains 4 pointsASSERT (4 == pointList.size ());
This helper function adds a point to the list.
The z argument defaults to zero to simplify the previous code.
For afficionados of const, note that C++ allows us to initialise a const value or object.
With MDL, you have to pass the address of a value because of C-calling requirements,
and the value therefore cannot be const.
This const-ness give additional freedom to your compiler to make some optimisations …
void AddPoint (DPoint3dCollection& pointList, double x, double y, double z = 0.0)
{
const DPoint3d point = { x, y, z }; // Initialise struct the C++ way
// mdlVec_fromXYZ (&point, x, y, z); // Initialise struct the MDL way
pointList.push_back (point);
}
There is a number of ways to extract point data from DGN elements.
For example, if we have a linear element, then mdlLinear_extract() does the job.
However, that function is a child of its time.
To use it successfully, you need to …
mdlLinear_getPointCount() first
mdlLinear_extract()
In other words …
void ExtractPoints (MSElement const* linearElement, DgnModelRefP modelRef)
{
int pointCount = mdlLinear_getPointCount (linearElement);
DPoint3d* points = (DPoint3d*)malloc (pointCount * sizeof (DPoint3d));
if (SUCCESS == mdlLinear_extract (points, &pointCount, linearElement, modelRef))
{
// Do something with points ...
}
// Don't forget to free() allocated memory
free (points);
}
It's critical to free the memory after using it, otherwise you end up with a memory leak. Here's the same function using std::vector. Because the Standard Library takes care of memory management, you don't have to …
void ExtractPoints (MSElement const* linearElement, DgnModelRefP modelRef)
{
int pointCount = mdlLinear_getPointCount (linearElement);
DPoint3dCollection points (pointCount);
// Ensure that the vector knows how many points it contains
points.resize (pointCount);
if (SUCCESS == mdlLinear_extract (&points[0], &pointCount, linearElement, modelRef))
{
// Do something with points ...
}
// No need to free() anything
}
There are several ways to iterate a vector …
In the following examples, we use the vector of DPoint3d declared above.
C-style iteration is directly analogous to iterating a traditional C array. A benefit provided by the Standard Library collection class is that it knows its own length, so we don't have to remember …
for (int i = 0; i != pointList.size (); ++i)
{
DPoint3d temp = pointList [i];
}
Standard Library iterator usage …
typedef DPoint3dCollection::iterator DPoint3dIterator; // typedef clarifies subsequent code
DPoint3dIterator End = pointList.end ();
for (DPoint3dIterator it = pointList.begin (); it != End; ++it)
{
DPoint3d temp = *it; // Dereference iterator like a pointer
}
Standard Library algorithm usage depends on what you want to do. You can sort a collection, search for a value, modify values — all without writing a loop. For example, sometimes you may want to reverse the order of vertices of a polygon to invert its normal. Try writing a loop for that, and you can see that the Standard Library algorithm makes life a lot simpler …
#include <algorithm> std::reverse (pointList.begin (), pointList.end ());
When you've finished using your pointList, you simply stop caring about it.
When it goes out of scope, std::vector cleans itself up and deallocates memory.
The above code builds a list of points.
A characteristic of std::vector is that data are stored contiguously, just like a C array.
That is, the data bytes at address &vector[0] look just like the data bytes of a C array.
This characteristic makes std::vector the most appropriate of the Standard Library containers to use when interfacing with a C-style API such as the MDL.
You can use our pointList with a C-style API, such as MDL, by passing the address of the first member of the vector.
For example, we can use the above pointList to construct a MicroStation line string.
Note that, because a std::vector knows its own size, we can pass the vertex list and its length like this …
MSElement line;
if (SUCCESS == mdlLine_create (&line, NULL, &pointList [0], pointList.size ()))
{
// We constructed a line string
}
Memory Management is sooo last century!
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 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. Read more about Boost.
Boost.Geometry provides some useful tools for computational geometry.
We provide an example that uses Boost.Geometry's complex_hull algorithm to
generate a complex hull
from the points in a DGN model.
Many years ago, when the Standard Library was young or perhaps even before then, Microsoft wanted to introduce template collection classes to Visual C++. For various reasons, they devised their own classes for object collections CList, CArray and CMap. They provided similar collections for pointers: CTypedPtrList, CTypedPtrArray and CTypedPtrMap.
Those templated collections provided a useful service until Microsoft was able to include full support for the C++ Standard Library in Visual C++.
If you have code that uses those collections, it probably works well and you don't need to change anything.
However, for new projects you should recognise that the Standard Library provides a wider range of collection classes,
and a wide range of algorithms that operate on those classes.
Moreover, because of the availability of smart pointers (e.g. boost::shared_ptr), you can use
the Standard Library collections to store pointer-to-objects as well as objects.
With MDL, it's common to use C-style arrays for many purposes — a list of vertices, for instance, would be declared …
DPoint3d points [MAX_VERTICES];
C arrays are fixed-length.
They waste space: the MAX_VERTICES supplied by the MDL header files translate as an integer value of 5,000.
If your arrays don't need 5,000 members, then the above declaration wastes space.
You may feel drawn towards dynamic allocation of C arrays …
DPoint3d* points = (DPoint3d*)malloc (MAX_VERTICES * sizeof (DPoint3d);
if (NULL != points)
{
... use array
free (points);
}
You are responsible for using malloc correctly;
you're responsible for checking that allocation succeeded;
and you're responsible for deallocating memory.
A std::vector<DPoint3d> is an incontestible substitute
for a dynamically-allocated C array.
In most cases, you can substitute a std::vector<DPoint3d> for a C array.
Using pure MDL you can use two elastic array types …
mdlDArray_xxx)
jmdlEmbeddedArray_xxx API
Dynamic Arrays and jmdlEmbeddedArrays both provide a way to
set or get a member of the array.
Neither API has any algorithm: if you want to sort or otherwise process one of those
elastic arrays, you have to write your own code.
In a C++ context, those elastic arrays are as redundant as C-style arrays. The Standard Library collections offer a better and wider choice of containers. The Standard Library also provides a number of algorithms, such as sort, that work on the standard collections.
Dynamic Arrays are provided by the Dynamic Array API (mdlDArray_xxx).
Dynamic Arrays are not type safe: they accept any type, represented by a void*.
mdlDArray_processAll is a callback iterator that applies your function to each member of the
array.
jmdlEmbeddedArrays are restricted to a fixed set of types
(int, double, and 2D and 3D points).