Introduction

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.

C++ Containers

Q

Bentley Collection Classes

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.

C++ Standard Library

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.

Standard Library Bibliography

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

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.

Standard Library std::vector

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.

typedef is Your Friend

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;

Using 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);
}

Extracting Point Data from an Element

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 …

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
}

Iterating a vector

There are several ways to iterate a vector …

  1. Iterate in the C way, using an index into the array
  2. Use a Standard Library iterator, which is analogous to using a pointer
  3. Use an algorithm from the Standard Library algorithm API

In the following examples, we use the vector of DPoint3d declared above.

C-style Iteration

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 Iteration

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
}

Algorithmic Iteration

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 ());

Deallocating a vector

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.

Using 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: Who needs it?

Memory Management is sooo last century!

C++ SmartPointers

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.

C-style Arrays

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.

Questions

Post questions about C++ and the MicroStationAPI to the MicroStation Programming Forum.