Bentley Technology Partner

Here are answers to questions about MicroStation® application development when implementing functionality in a DLL written using Visual Studio. The questions are posted by MDL developers on the Bentley Discussion Groups.

Q Hints about implementing an application for MicroStation using Microsoft Visual Studio™.

A Since MicroStation V4 was introduced in 1992, you have been able to write applications using the MicroStation Development Library (MDL) and use the MicroStation software development kit (SDK) to create an application (.ma) file. With the introduction of MicroStation V8, you can move your implementation code to C or C++ and write your application as a DLL using Microsoft Visual Studio. With MicroStation XM and MicroStation V8i you can alternatively develop using one of the .NET languages.

Q Which version of Microsoft Visual Studio™ should I use to develop my C/C++ MicroStation application?

A This article discusses MicroStation versions and recommended versions of Visual Studio.

Code Manifests

If you develop using Visual Studio 2005 or later, then your DLL will contain a manifest. The manifest is a list of the run-time DLLs and assemblies called by your code. Compatible versions of Windows examine the manifest when your DLL is called, and match it with the appropriate run-time modules. For example, you may have built your DLL to link with a specific version of the Microsoft Visual C Run-Time (MSVCRT) DLL.

You will find at least one version of the run-time DLLs in the Windows side-by-side (SxS) folder. The location of this folder is %windir%\winsxs (e.g. C:\Windows\WinSxS).

Plain Old MDL

You want to use plain old MDL to create an application, or prefer not to use Visual Studio. Look at this page for information about setting up your Windows environment for MDL.

With MicroStation V8i, Bentley recommend Visual Studio as the development tool. That is, pure MDL applications written in C syntax are deprecated. The new \mdl\MicroStationAPI folder contains C++ class headers: you can use those class headers only with Visual Studio; you can't use them with pure MDL.

Target Folder

The simplest way to deliver your application on your development computer is to tell Visual Studio to build the DLL in MicroStation's \mdlapps folder. That way, you don't have copies of a DLL that get out-of-sync with your development code.

You can specify an output destination with all versions of Visual Studio. The setting is in the Linker section of your Project Properties dialog. Assuming that you have defined Windows environment variable MS, then Visual Studio will understand your directive $(MS)/mdlapps/AppName.dll.

Visual Studio Project Properties Output Destination

DLL Basics

When you move your implementation to a DLL created with Visual Studio, you still need some data in an MDL application (.ma) file. The reason is that you want your users to be able to load your application into MicroStation, typically using a keyin such as MDL LOAD myapp. You probably want to create a command table, so that your application provides its own set of keyins. Visual Studio doesn't understand MicroStation commands or keyins, so you must continue to define at least one MDL resource that is compiled to a (.ma) file.

Load your DLL as you would any MDL application, with the keyin MDL LOAD MYAPP. The DllMdlApp described here tells MicroStation the name of your DLL to load.

DLL Resource

The resource type DllMdlApp lets you associate a DLL file with an MDL application name. The resource definition looks like this …

DllMdlApp  DLLMDLAPP_MYAPP =
{
    //   MDL Application name     VC++ DLL in MDLAPPS or %PATH%
    "MYAPP",                      "MYAPP.DLL"
};

When your user types MDL LOAD myapp, MicroStation looks in myapp.ma to find a main() entry point. If it fails to find main(), then it looks for a DllMdlApp resource. When it finds the DllMdlApp resource it attempts to load the DLL specified.

You must add the necessary lines to your bmake file to compile the DllMdlApp resource so that it's included in your .ma file.

Return to MDL articles index.


Using a DLL with PowerXxx Products

Your DLL won't load in a Bentley Systems PowerXxx product unless you have a password. For example, if you've developed an MDL application SuperDuperApp that is implemented in SuperDuper.dll, you can load it in MicroStation usign keyin
mdl load SuperDuperApp
But if you attempt the same in, say, PowerDraft, you will instead see a message: Unable to load MDL application SuperDuperApp.

Your application needs a password to work with PowerDraft. It needs a separate password to work with each and every other PowerXxx product. Request a password for a particular PowerXxx product from Bentley Systems. Your organisation must be a SELECT subscriber or a member of the Bentley Developer Network (BDN) to qualify.

Contact Bentley Systems or the BDN (bdn @ bentley.com) for more information.

Return to MDL articles index.


Other Resources

A pure MDL application lets you store message lists, dialog definitions, menus, icons, type definitions, and command tables in binary resource files. The resources are merged with your .ma file when you invoke the bmake build. With a DLL, you can continue to use MDL resources. You need a command table defined just as you always have done with MDL. However, since VC++ doesn't understand MicroStation command tables, you still need to define that table in an MDL resource.

VC++ lets you define dialogs, menus, icons, and message lists in a resource.

You make the decision whether to use MDL resources, VC++ resources, or a mixture of both. MDL resources are useful for small resources, or resources where a close coupling with MicroStation is required. VC++ resources, particularly dialogs, are useful when you want to make use of the rich set of user interface widgets in Windows.

An example of an MDL resource that can't be replicated in VC++ is a dialog box that contains widgets having access strings. An access string is your global variable. The MicroStation dialog manager takes care of setting your variable in response to a user manipulation of a dialog item. This is an especially effective item in modeless dialogs used as tool settings — the VC++ dialogs come a poor second in comparison with the elegant MicroStation dialog manager.

Return to MDL articles index.


Command Table

Create a command table for your application, in the usual way, as an MDL resource file. Your bmake file should include the usual rules to build the command table resource and include it in your .ma file.

Traditionally, MDL code has provided an effective but non-standard way to associate a command with its implementation. An MDL command function in an .mc file looks like this …

void cmdFunction_command1 (char* unparsed)
cmdNumber CMD_MYAPP_COMMAND1
{
    //   Implementation of cmdFunction_command1
};

The MDL compiler (mcomp) builds a table of function pointers, each identified by your application's unique command associated with each function. In traditional MDL, you can't see the table, because mcomp builds it internally and doesn't publish the result.

With Visual C++ non-standard function calls are not possible, so Bentley now provide a structure MdlCommandNumber that associates a command with its function. You must fill in an array of MdlCommandNumber values, in your VC++ source file, with the list of command IDs and the functions you have written.

MdlCommandNumber         commandNumbers [] =
{
{  cmdFunction_command1, CMD_MYAPP_COMMAND1 },
{  cmdFunction_command2, CMD_MYAPP_COMMAND2 },

// This table must be NULL-terminated
0
};

A command function has the signature void f (char* unparsed), so the implementations of the above two commands are similar …

void cmdFunction_command1 (char* unparsed)
{
    //See MFC documentation about AFX_MANAGE_STATE
    AFX_MANAGE_STATE (::AfxGetStaticModuleState ());
    //   Implementation of cmdFunction_command1
};

The mysterious AFX_MANAGE_STATE (::AfxGetStaticModuleState ()); is MFC magic. If you call MFC functions from non-MFC code (you'll see later why a command function is non-MFC), the incantation adjusts some internal pointers so everthing continues to work smoothly. Since that macro has no adverse effect if you don't call into the MFC libaries, it is simplest to get into the habit of including it at the beginning of every non-MFC function.

There's some sample C++ code here.

Return to MDL articles index.


Windows Environment Variables

We've documented several Windows environment variables relevant to MDL development and Visual Studio.

Visual Studio 2008

If you develop using Visual Studio 2008 or later, it ignores environment variables that point to a folder not on the .NET trusted sites list.

For example, if your development folder is on a network drive, you might define an environment variable LA_INCLUDE that points to the location of your #include files. Visual Studio 2005 can see that folder, but Visual Studio 2008 will not see it until your local network (Intranet) is granted full trust.

You can adjust the .NET security via the Windows Control Panel. The following steps assume that you have administrator rights on your computer. You may want to involve your IT support group …

  1. Start the .NET Framework 2.0 Configuration applet
    1. In the left pane, click Runtime Security Policy
    2. In the right pane, click Ajust Zone Security
.NET Framework Configuration
  1. The Security Adjustment Wizard dialog opens
    1. Click the Local Intranet icon
    2. Move the slide control to Full Trust
.NET Framework Configuration

Once you have set the Local Intranet to Full Trust you should find that Visual Studio 8 will acknowledge any environment variables or include paths that point to network folders.

Return to MDL articles index.


Calling MDL Functions from VC++

Consider that you are creating a DLL for a platform. The platform is MicroStation, and it provides a large number of DLLs that implement MicroStation functionality. Your DLL is an addition to that list. Your code is running in MicroStation's address space, and it can see and use public functions in those MicroStation DLLs.

To use the MDL API, you must #include the appropriate function prototypes in your .cpp source code. Consult the MDL help documentation, which tells you which file to #include for each MDL function. The documentation also tells you which library implements that function (e.g. dlogitemslib.lib).

MDL Header Files

You must tell Visual Studio where to find MicroStation's header files for the C/C++ preprocessor.

  1. Choose the Visual Studio menu Project|Settings
  2. Select the tab labelled C/C++
  3. Choose Preprocessor from the first combo box
  4. Add the MDL include folder to the list Additional include directories.

    Note: You can use environment variables using syntax $(env-var), so if you have defined environment variable MS then you can use this to designate the include folder

Specify MicroStation's \include folder

Preprocessor Definitions

Your version of MicroStation is a release (as opposed to debug) version, assuming that you're not an internal developer for Bentley Systems. When you build an application you must be sure that your compiler & linker switches are compatible with that release version of MicroStation.

As you probably know, a compiler can change its behaviour depending on various compile-time switches. For more information about compile-time switches, consult the Visual Studio documentation. As developers, we often add preprocessor definitions, such as a #define statement. The compiler may add its own preprocessor definitions depending on those compile-time switches.

In particular, a debug switch may expose quite different source code compare to a non-debug compilation. You have to be sure that a debug switch in your build does not create an incompatibility with the release version of MicroStation.

An example of incompatibility occurs when you use the Standard Library. Visual C++ adds run-time checking and other aids in a debug compilation that don't appear in a release compilation. Some of those changes involve structure or class layout differences. When you run your compiled code against MicroStation, the layout differences cause run-time problems. Here's a comment by Keith Bentley (27-Jan-2011) on this topic …

You must compile with -D_SECURE_SCL_THROWS=1 and -D_SECURE_SCL=0 defined, even for debug builds. Otherwise you get the "checked iterators" in the stl classes and their layout changes (MicroStationAPI WString uses std::string). Likewise you cannot link with the debug CRT since MicroStation isn't linked with it.

We [Bentley Systems] have removed all uses of STL in the public API for future releases to avoid this problem. But for now the above will allow you to compile for debug.

Precompiled Header Files

Precompiled header files are implemented by many C++ compilers, including Visual C++. You must tell the C++ compiler that you want to use a precompiled header by passing the appropriate command-line switch -Yc. In Visual Studio, this is done by default.

When building using BMake, you must pass the switch to the compiler by using the CCPchOpts macro. Here's an example from an .mke file …


#----------------------------------------------------------------------
#	Precompiled header:
#	You need to define CCPchOpts in the .mke before the compiling statement.
#	You can change the name of StdAfx to whatever you like.
#----------------------------------------------------------------------
CCPchOpts = -Yc"StdAfx.h" -Fp$(o)stdafx.pch

MDL Libraries

You must tell Visual Studio where to find MicroStation's libraries for the linker. Put the MicroStation\mdl\library folder in Viz Studio's Additional Library Path field:

  1. Choose the Visual Studio menu Project|Settings
  2. Select the tab labelled Link
  3. Choose Input from the first combo box
  4. Add the MDL library folder to the list Additional library path.

    Note: You can use environment variables using syntax $(env-var), so if you have defined environment variable MS then you can use this to designate the library folder. e.g. $(MS)mdl/library

Specify MicroStation's \library folder

Return to MDL articles index.


DLL Entry Point

As you probably know, an MFC DLL has several functions, such as DllMain that Visual Studio inserts automatically. Windows uses these standard functions as it manages your DLL.

MicroStation requires that your DLL has MdlMain, whose signature is the same as main(). Furthermore, MdlMain must use the C calling convention and not the C++ convention. The simplest approach is to prefix MdlMain with the macro DLLEXPORT …

#define DLLEXPORT __declspec( dllexport )

extern "C" DLLEXPORT int MdlMain
(
int   	argc,
char*	argv[]
)
{
    //See MFC documentation about AFX_MANAGE_STATE
    AFX_MANAGE_STATE (::AfxGetStaticModuleState ());
    ...
    return SUCCESS;
}

Return to MDL articles index.


Name Mangling (using C functions without tears)

As you probably know, C++ mangles function names. This is how it lets you implement function overloading, when a function has multiple signatures and multiple implementations. Unfortunately, C++ mangles function names whether it needs to or not. For example, even when there is only one version of a function, C++ mangles its name. Unless you do something, C++ will change MdlMain to something that MicroStation can't understand, such as MdlMainAPQC01ABR56TuV.

A way to prevent name mangling is to wrap your C functions in scope extern "C" {}. This tells the compiler to use C rather than C++ to compile code within that scope. Defeat the C++ compiler like this …

#define DLLEXPORT __declspec( dllexport )

extern "C"
{
DLLEXPORT int MdlMain
(
    int   argc,
    char  *argv[]
    )
    {
        //See MFC documentation about AFX_MANAGE_STATE
        AFX_MANAGE_STATE (::AfxGetStaticModuleState ());
        ...
        return SUCCESS;
    }
} //extern "C"

Now MdlMain retains its correct name, and MicroStation can find it when your user keys MDL LOAD MYAPP.

Return to MDL articles index.