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.

structs and typedefs

Q I want to declare an C++ typedef in a struct (to pass some data to my application) but I am getting an error saying some C++ typedef is not a valid type.

How can I include some C++ typedef as a valid type in my header file?

Q I don't know how to get from DPoint3dCP to DPoint3dCR. I scanned the C++ header files and didn't find anything …

Where are those Hungarian notation suffixes defined?

Q What's an opaque pointer?

Contents

C++ Structs, Aliases and Typedefs

A  The MicroStationAPI defines an enormous variety of structs and typedefs in its header files.

In a default MicroStation CONNECT installation, those headers are in a sub-folder of the SDK's \include folder, similar to this …
C:\Program Files\Bentley\MicroStation\SDK\include

As an C++ developer, you must become familiar with that folder and its contents. It also includes, by the way, all the function definition files (.fdf files) that you'll need. Function definition files are just a header file with an .fdf extension. You will often need to find a particular structure to use in your own code. A text editor with file-search capability is invaluable. It will improve your productivity by providing search operations from your editing environment. We mention some useful text editors elsewhere.

C++ 11 Aliases

The typedef alias mechanism has existed since the C language was conceived. C++ 11 and later provide a new type alias mechanism that some consider easier to read. For example …

using DPoint3dP = DPoint3d*;

The using mechanism has benefits other than clarity. You'll find it handy when you venture into template meta-programming.

However, the MicroStationAPI sticks, as far as I can see, exclusively to the typedef alias mechanism. That doesn't mean you have to use typedef: the using idiom means just the same to the C++ compiler, and you may find it easier to read.

Dialog Manager requires C-Compatible structs

Because MicroStation's resource compilers have grown from C-style programming, structs that are shared with the MicroStation's Dialog Manager must conform to C rules.

Hungarian Notation Suffixes

A  C++ and the MicroStationAPI define and use a large number of typedefs. A C/C++ typedef lets you — or Bentley Systems in this case — extend the compiler's type checking to cover your custom data type definitions. DPoint3dCP to DPoint3dCR are two examples that extend the well-known DPoint3d data type. But what do those suffixes mean?

Somebody at Bentley thought it would be a good idea to have a consistent way to append a Hungarian-notation suffix to type names. Not a bad idea! It clarifies code for human consumption and provides consistently-derived types for the compiler to check. For example, we're all familiar with a DPoint3d data type …

struct _dpoint3d
{
  double x;
  double y;
  double z;

} dpoint3d;

It's common to pass address-of a DPoint3d when we want data copied into our variable …

DPoint3d var;
MicroStationAPI_someFunction (&var, … );

What's the data type in the function prototype? A pointer to DPoint3d (DPoint3d*).

void MicroStationAPI_someFunction (DPoint3d* copyDataToMe, … );

We also pass address-of a DPoint3d when we want data copied from our variable to somewhere inside the function. In this case, we should really pass DPoint3d const* to indicate that it's our data and we don't want it changed. The call site looks identical …

DPoint3d var = { 1., 2., 3. };
MicroStationAPI_someFunction (&var, … );

 …but the function prototype changes …

void MicroStationAPI_someFunction (DPoint3d const* copyDataFromMe, … );

The Hungarian-suffix notation for the above data types uses P for pointer and CP for const-pointer. Expressed formally …

typedef DPoint3d* DPoint3dP;
typedef DPoint3d const * DPoint3dCP;

Using those typedefs, the above function prototypes could be written …

void MicroStationAPI_someFunction (DPoint3dP copyDataToMe, … );
void MicroStationAPI_someFunction (DPoint3dCP copyDataFromMe, … );

When writing C++, it's convenient to pass a reference-to-a variable. As with pointers, a reference can be const or non-const. Expressed formally …

typedef DPoint3d& DPoint3dR;
typedef DPoint3d const& DPoint3dCR;

So there are four common Hungarian-suffix labels for const and non-const references and pointers …

There are how many data types in C++ and the MicroStationAPI? Don't answer the question; the answer is 'plenty'.

For a short example, look at #include file <bentleytype.h>.

Somebody at Bentley decided it would be a good idea to use a macro to create those type definitions. Instead of laboriously writing out the above typedefs by hand, why not invoke a pre-processor macro to do the hard work? Some people will tell you that pre-processor macros are evil (macros are not really evil: this is a programmers' light-hearted indication that they present unforseen problems). In this case, the use of a macro to perform a typedef (in fact, multiple typedefs) obfuscates their meaning.

In #include file <msgeomstructs_typedefs.h> you will find macro DEFINE_GEOM_STRUCT. It uses the pre-processor's stringize capability to append the appropriate Hungarian notation suffix to an existing type name …

DEFINE_GEOM_STRUCT (DPoint3d,_dPoint3d)

That's why you can't find a type definition for DPoint3dP and its relatives. They're hidden behind some pre-processor prestidigitation.

Bentley Smart Pointers

You're probably aware that C++ 11 and later have smart pointers.

You're free to use the standard smart pointers (std::unique_ptr) and std::shared_ptr) in your own code. When interfacing with the MicroStationAPI we must use smart pointers defined by Bentley Systems. For an explanation of Bentley smart pointers, please address your queries to the Be Communities MicroStation Programming Forum.

The RefCountedPtr class is declared in header file RefCounted.h. You'll often find it when looking up type definitions in the MicroStationAPI help documentation. For example, here's the alias of a smart pointer to a ClipVector …

typedef RefCountedPtr <DgnPlatform::ClipVector> ClipVectorPtr

Bentley TypeDef Macros

The following C++ #include files (there may be more that I haven't found) provide typedef-generator macros …

These are complex nested macros that use C pre-processor stringization to construct a number of typedefs from a given type …

The macros behave differently in C++ compared to C or MDL and place the typedefs in a namespace. The end result is that you can pass in a basic type, say my_data_struct, and the macro generates Hungarian-notation suffix typedefs for your struct's pointer and const pointer, in this case …

 //	C++ typedefs
typedef struct my_data_struct* my_data_structP;
typedef struct my_data_struct const* my_data_structCP

For example, <mstypes.h> runs the ScanCriteria struct through macro GLOBAL_TYPEDEF. That macro creates the pointer and const pointer typedefs of ScanCriteria, which are ScanCriteriaP and ScanCriteriaCP.

When building a C++ project, the macros are more complex and produce both pointer and C++ reference typedefs. Something like this …

//	C++ typedefs
typedef struct Bentley::my_data_struct*        Bentley::my_data_structP;
typedef struct Bentley::my_data_struct const*  Bentley::my_data_structCP
typedef struct Bentley::my_data_struct&        Bentley::my_data_structR;
typedef struct Bentley::my_data_struct const&  Bentley::my_data_structCR

Opaque Pointers

A  Often you may find that a typedef exists for a struct but you can't find the struct definition. For example, StringList is a typedef but its struct is not published.

It doesn't matter that you can't see the definition of the StringList struct, because you can declare a pointer of type StringList*. In fact, you only ever need to declare a pointer to a StringList*. The StringList API only ever requires a StringList* and never a StringList.

Bentley documentation calls this an opaque pointer, because you can declare and use a pointer to that data structure, but can't see the struct definition. Other examples of an opaque pointer include the MSWindow* and TransDescrP. As with the StringList*, you only ever manipulate a MSWindow* or TransDescrP using the API.

If you mistakenly declare a variable of a type only referenced through an opaque pointer, the compiler issues an error. That is, if you attempt this …

DialogBox db;
db.id = 1324;

You will see a compiler error because you can't instantiate a struct whose definition does not exist.

StringList Example

For example, a StringList is a common data type that is typedef'd in an C++ header file. Suppose you want to define a struct that includes a StringList. You write something like this in your header file …

typedef struct globals
{
  int          intVal;
  double       doubleVal;
  char         stringVal [45];
  StringList*  strListP;
} Globals;

However, the C++ compiler doesn't like that struct definition and issues a rude message along the lines StringList is not a valid type. To fix that error, you must find and #include the relevant C++ header.

Using a text editor with built-in file search makes that task easy. In this case you will find that typedef struct stringList StringList; appears in several header files. The more relevant files are msstrlst.h and msstrngl.h. Include either of those in your header to obtain a compilable typedef …

#include <msstrngl.h>
typedef struct globals
{
  int          intVal;
  double       doubleVal;
  WChar        stringVal [45];
  StringList*  strListP;
} Globals;

With the vital #include <msstrngl.h> your C++ code should compile without an error message about an undefined type.

Hide Pointers from the Resource Compiler

It's common practise to use a header (.h) file that you #include in your implementation (.cpp) file and in your resource data (.r) files. Unfortunately, the Bentley Systems resource compiler (rcomp.exe) only knows about data and structures. It doesn't know about pointers.

When rcomp.exe finds a pointer in a data structure, it doesn't know what to do with it and issues an error. You need to hide pointers from the resource compiler in your header file. Bentley Systems anticipated that problem (or maybe they discovered it before we did) and define a symbol resource when rcomp.exe is executing. You can test for that symbol in your header file to hide structure definitions that contain pointers …

#if !defined (resource)
#include <msstrngl.h>
typedef struct globals
{
  int          intVal;
  double       doubleVal;
  WChar        stringVal [45];
  StringList*  strListP;
} Globals;
#endif // !defined (resource)	

Return to C++ articles index.

Questions

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