The MicroStation Development Library (MDL) and MicroStationAPI provide APIs for developers wanting to create custom applications for MicroStation® from Bentley Systems. We create a MicroStation application as a DLL, written using C++ and built with the Microsoft C++ compiler and linker provided with Visual Studio.
When editing your source code, you can choose whether to use Microsoft Visual Studio, Microsoft Visual Studio Code, or one of your favourite text editors.
When building your app, you can use Visual Studio or the Bentley Systems make (bmake) tools.
Q
The MicroStationAPI includes a set of collection classes.
These are modelled on the C++ Standard Library collection classes
such as std::vector<>
but avoid certain pitfalls.
The Bentley collection classes include vector, map, set and others.
Their naming convention is simple: bvector<>
is a template vector class whose
behaviour is almost identical to std::vector<>
.
Why does that exist? Here's a comment from the MicroStationAPI documentation …
This class is used in the Bentley APIs to avoid dependencies on compiler-supplied implementations of
std::vector<>
that sometimes vary with compiler settings or compiler versions.
The bvector<>
class does not suffer from these problems.
This makes bvector<>
suitable for use in Bentley public APIs.
To understand the bvector<>
class, consult the documentation for std::vector<>
.
Use bvector<>
when your code must interface with the MicroStationAPI.
Elsewhere in your code you are free to use the C++ Standard Library collection classes.
Prefer the C++ Standard Library in your own code. 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
(but prefer the Bentley collection classes when interfacing with the MicroStationAPI).
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 a MicroStation developer, then you will most likely be using the Microsoft development tools including Visual C++. Visual C++ includes an implementation of the Standard Library. If you're developing for the current version of MicroStation CONNECT, then you should be using Visual Studio 2013.
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.
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 developed an example that uses Boost.Geometry's complex_hull
algorithm to
generate a complex hull
from the points in a DGN model.
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 MicroStation vectors.
A Standard Library std::vector
means an array of data.
It has nothing to do with 3D geometry.
An MicroStation 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.
Write a using
statement (or the older 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!
Write using
to make your own reusable, easy to type, classes:
using DPoint3dCollection = std::vector<DPoint3d>;
Prior to C++ 11 you would have written:
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;
bvector<DPoint3d>
and std::vector<DPoint3d>
Suppose you want to build a list of DPoint3d
vertices,
and use that list to construct a line string element.
Using the Bentley collection classes, you would do something like this …
#include <bvector.h>// Header for Bentley vector template class
using DPoint3dCollection = bvector<DPoint3d>;// Alias clarifies subsequent code
DPoint3dCollection pointList;// Elastic array of DPoint3d
// Initialise some points and add to the list
AddPoint (pointList, 1, 1); AddPoint (pointList, 10, 12); AddPoint (pointList, 20, 25); AddPoint (pointList, 30, 50);// List contains 4 points
ASSERT (4 == pointList.size ());
Using the Standard Library, you would do something like this …
#include <vector>// Standard Library header for vector template class
using DPoint3dCollection = std::vector<DPoint3d>;// Alias clarifies subsequent code
DPoint3dCollection pointList;// Elastic array of DPoint3d
// Initialise some points and add to the list
AddPoint (pointList, 1, 1); AddPoint (pointList, 10, 12); AddPoint (pointList, 20, 25); AddPoint (pointList, 30, 50);// List contains 4 points
ASSERT (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 had 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 (pointList 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.
bvector<DPoint3d>
with the MicroStationAPI
The above code builds a list of points.
A characteristic of bvector<DPoint3d>
and 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 bvector<>
the most appropriate container
when exchanging a C-style array with the MicroStationAPI.
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
}
Note: the above example demonstrates how a C-style API can be used with a bvector<DPoint3d>
.
In practise, you would use MicroStationAPI classes such as CurveQuery
to extract data from a graphic element.
Memory Management is sooo last century!
If you do feel irresistably drawn to manual memory allocation, then you should love C++ smart pointers. They provide all the control of manual memory allocation, plus automatic clean-up: you don't have to remember to deallocate memory.
With MDL for MicroStation V8, it was 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 = new DPoint3d [MAX_VERTICES];
if (nullptr != points)
{
... use array
delete [] points;
}
You are responsible for using new
correctly;
you're responsible for checking that allocation succeeded;
and you're responsible for deallocating memory using the []
form of delete
.
A std::vector<DPoint3d>
is an incontestible substitute
for a dynamically-allocated C array.
In most cases, you can substitute a
bvector<DPoint3d>
or a
std::vector<DPoint3d>
for a C array.
Post questions about C++ and the MicroStationAPI to the MicroStation Programming Forum.