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 …

Microsoft Visual Studio versions matching MicroStation versions
MicroStation Version MDL Code Native Code Managed Code
08.11.XX.XX Y Visual Studio 2005 Visual Studio 2005
08.09.XX.XX Y Visual Studio 2003 Visual Studio 2005
08.05.XX.XX Y VisualC/C++ 6.0 N/A

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.

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.


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.

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

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 )

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.