Getting Started With MDL in MicroStation V8
Getting Started With MDL in MicroStation V8
With all the benefits that MicroStation V8 provides, MDL applications need to be recompiled.
This is mainly due to many changes in underlying structures. Now is as good a time as any to
revisit the roots of MDL.
The documentation now covers more functions than ever before. New features have been
added and support for "Native Code" has been enhanced -- in fact, some of the core concepts
have changed quite significantly. For applications to take advantage of these enhancements,
some change is required. But the good news is that in the majority of cases, the logic of the
application should remain the same.
Where should you start? With all MDL applications, the Make file must be addressed first.
The Make file syntax has remained constant since its introduction; the new changes apply
only to some of the macros and they have been in place since MicroStation/J. The macro
"LinkCmd" is now "mLinkCmd" and "rscLibCmd" is now "rLibCmd." These changes will be
evident during compilation when the compile process stops and a system error message
appears. Another change to the Make files is the move to "Native Code," which creates .dlo
file references that take the place of .ml files.
One of the often used, but rarely understood, parameters found in Make files is -s, which sets
the stack size. The -s parameter is only necessary when you are not using the common stack,
which few applications need.
The next thing that you will notice about the compile process is the new options that make the
compiler stricter. By default, the compiler is set to the highest level of checking. Although not
recommend, you can decrease the level of "compiler strictness" by using the dNOSTRICT
option. This may be useful for getting started but should definitely not be used for final
compilation. Also helpful is the -X option, which will force short path names to be used. This
may be necessary in some instances since, by default, MicroStation is installed to the Program
Files directory structure.
Another change in the environment that goes with this move to "Native Code," is that
additional libraries have been added to the MLINK_STDLIB environment variable. This
variable should now include toolsubs.dlo and dgnfileio.dlo. By starting in the Make file, the
application build process can be utilized to find any changes in the source code that have to be
made. While getting the build to go through, it may be necessary to comment out or #ifdefine
sections of the code to allow the application to build.
The GUI for applications should only need changing for applications that call upon the level
map or level picker items. These items are no longer valid with unlimited level support in V8
DGN files. Other than those two instances, dialogs should function as they always have, with
one exception: some of the spacing may need to be updated, because the dialog font
information has changed.
The big news is the addition of tree items and editable List box cells. There is a replacement
for the StringList in List boxes, which may be useful for applications. The new ListModel
should make the alignment of data and its presentation easier and more powerful than ever
before. The ListModel allows you to separate the data from the presentation in the List box.
pListCell = mdlListRow_getCellAtIndex (pRow, CELLNAMECOL);
mdlListCell_setStringValueW (pListCell,wCellName,TRUE);
iStatus = mdlListCell_setInfoField
(pListCell,0,(long)pChildNode);
long infoField;
iStatus = mdlListCell_getInfoField (pListCell,0,&infoField);
//do the description column
mdlModelItem_getDescription (pDgnIndexItem, wCellDescription,
MAX_CELLDSCR_LENGTH);
pListCell = mdlListRow_getCellAtIndex (pRow,CELLDESCCOL);
mdlListCell_setStringValueW (pListCell,wCellDescription,TRUE);
Conversion from StringList to ListModel is not a necessary change, but it is certainly a simple
change that will result in a better application in the long run. To make the list cell editable, the
application needs to add an attribute to the List box definition. In the application, the code
will then need to provide a hook for the edit event or use a simple text field as the editor.
/*----------------------------------------------------------------------
+*//**
* creates the listmodel that the listbox will use.
*
* @bsimethod createListBox *
*
* @param nCols number of columns in the listbox *
* @return the listmodel to connect to the listbox
*
* *
+----------------------------------------------------------------------*/
Private ListModel* createListBox
(
int nCols
)
{
ListModel *pListModel;
ListRow *pRow;
int i;
int colIndex;
if (colIndex == 2)
mdlListCell_setEditor (pCell, RTYPE_Container,
CONTAINERID_TextStyleColorPicker, mdlSystem_getCurrMdlDesc(), FALSE, TRUE);
}
mdlListModel_addRow (pListModel,pRow);
}
return pListModel;
}
The place where applications will see the most change is in the core application code. This
can be divided into two sections: one for changes to make the application work just as it did in
previous versions, and the other to allow changes to take advantage of new capabilities in
MicroStation V8.
The first thing anyone will notice about MicroStation V8 is the new level structure, which
gives you "unlimited levels." This could affect applications in a number of ways, since
historically the level information was held in an array of short integers. Level information is
now a BitMask data type. Another thing to note regarding levels is that each element holds on
to an unsigned long integer, which is the internal level ID. Since not every level exists in
every file any more (due to the potential use of DGN Libraries), any code that creates an
element that should be on a particular level will need to first ensure that level exists;
otherwise the element will end up on the default level.
Another enhanced capability in MicroStation V8 is scanning. While the API for scanning a
DGN file is still in place and will still work for most applications, the new ScanCriteria API
should be used instead. ScanCriteria offers new capabilities like callbacks and an API for
setting the criteria. This API is no longer shared, so things like performing scans within a scan
are no longer issues.
Most information in the DGN file that involves storing character data is now stored in
Unicode. To convert between Multibyte and Unicode character format, you can use the utility
functions mdlCnv_convertMultibyteToUnicode and mdlCnv_convertUnicodeToMultibyte.
Strings in Unicode format will be needed for cell names, level names, and model names.
Models are essentially the encapsulation of DGN files within a DGN file. DGN files can have
more than one model in the file. There are some parts that are shared between the models, like
shared cell definitions and level tables. When an application needs to copy elements from one
model to another the mdlElmdscr_copy function should be used. This copy function uses the
mdlCopyContext to remap the association information from one file to another.
To process a set of Models the application will need to create a ModelRefIterator then set it to
go through the set of Models. The options are to process the current model and its references
and then to control the depth to which the iteration will process. An example for processing
the current active modelreference and all of its references including the nested references:
int iteratorType =
MRITERATE_Root|MRITERATE_PrimaryChildRefs|MRITERATE_ExtendedChildRefs;
ModelRefIteratorP iterator = NULL;
DgnModelRefP pModelRef = mdlModelRef_getActive ();
References are also ModelReferences. The process used to attach a reference has been
improved by making it a three-step process. First, begin the attachment by using the
mdlRefFile_beginAttachment, then set the parameters of the attachment, and finally complete
the attachment by using mdlRefFile_completeAttachment.
/*----------------------------------------------------------------------+
| |
| name createAttachments |
| |
+----------------------------------------------------------------------*/
cmdName void createAttachments
(
char *unparsed
) cmdNumber CMD_REFATT_SIMPLE
{
char fileName[MAXFILELENGTH];
int status;
DgnModelRefP modelRefP;
FileOpenParams fParams;
BoolInt isThreeD;
int noSnap = 0;
DgnFileObjP dgnFileObjP;
int format;
int lastAction;
if (!*unparsed)
{
memset (&fParams,0,sizeof fParams);
mdlDialog_fileOpenExt (fileName,NULL,&fParams,0);
}
else
if (SUCCESS != mdlFile_find (fileName, unparsed, NULL, NULL))
{
char buffer[80];
snprintf ("File %s not found\n",sizeof buffer, unparsed);
mdlDialog_openAlert (buffer);
return;
}
Another capability of models is that they can be cells. In MicroStation V8, the cell now
contains scale information to allow for "true scale" placement in designs. Cell libraries are
now DGN files that contain a collection of models. 3D cells also can be placed in 2D DGN
files -- the cell will simply be "flattened." By using the mdlCell_getElmDscrExtended, the
application can control the orientation of the "flattening" as well as the level information that
is copied into the destination file.
There are two main parts to the level system that application developers will need to work
with. The first is the level itself, and the other is the level table that contains the available
levels in a file. To determine the levels that are in a file, the application will need to create a
level iterator that will loop through all the levels.
/*----------------------------------------------------------------------
+*//**
* Callback function for processing the members of the level table in a
file.
*
* @bsimethod traverseFunction *
*
* @param levelID The ID number of the level to be processed
*
* @param dataP This is the data added to the function by the
implementation. *
* @return SUCCESS to continue or !SUCCESS to stop processing
*
* *
+----------------------------------------------------------------------*/
DLLEXPORT Public int traverseFunction
(
ULong levelID,
void *dataP
)
{
TraverseInfo *tInfoP=dataP;
MSWChar levelNameOutput[MAX_LEVEL_NAME_LENGTH*2];
ULong color, style, weight;
ListRow *pRow;
BoolInt override;
LineStyleParams lsStyleInfo;
/* Get fileNumber */
lvlmangr_setLevelFilenumberAtColumn (pRow, COL_REFERENCE,
MASTERFILE);
return SUCCESS;
}
The level system can be used to create filters to reduce the number of levels that are actively
displayed. One of the data types used in working with levels is a BitMask. Access to the
BitMask is gained through the API.
/*----------------------------------------------------------------------
+*//**
* This function sets the levels to use in the scan from a string that is
passed into it.
*
* @bsimethod kscanSetLevelsFromString
* *
* @param *pScanCriteria The ScanCriteria to establish *
* @param *levelString The string that represents the levels to
scan on. *
* @param modelRef The model that is used to set the level
information. *
* @return SUCCESS to continue not SUCCESS to stop the scan. *
* *
+----------------------------------------------------------------------*/
Private void kscanSetLevelsFromString
(
ScanCriteria *pScanCriteria,
char *levelString,
DgnModelRefP modelRef
)
{
BitMask *pBitMask;
mdlBitMask_create (&pBitMask,FALSE);
mdlBitMask_setFromString (pBitMask,levelString,0,64); //this uses level
ids!
mdlScanCriteria_setLevelTest (pScanCriteria,pBitMask,TRUE,TRUE);
return;
}
/*----------------------------------------------------------------------
+*//**
* This function sets the levels to scan based on those visible in the view
passed in.
*
* @bsimethod kscanSetScanLevelsFromView
* *
* @param *pScanCriteria The ScanCriteria to set *
* @param viewNum The view number to get the display information
from. *
* @param modelRefP The model that the level information is from.
*
* @return SUCCESS to continue not SUCCESS to stop the scan. *
* *
+----------------------------------------------------------------------*/
Private void kscanSetScanLevelsFromView
(
ScanCriteria *pScanCriteria,
int viewNum,
DgnModelRefP modelRefP
)
{
const BitMask *pLevelMask = NULL;
if (NULL != (pLevelMask = mdlView_getLevelDisplayMask (modelRefP,
viewNum,VIEW_LEVEL_DISPLAY_TYPE_EFFECTIVE)))
mdlScanCriteria_setLevelTest(pScanCriteria, (BitMask *) pLevelMask,
FALSE,FALSE);
}
The scanning process still uses the same basic methodology, however, the new API makes it
much easier and more robust than ever. The ScanCriteria API almost completely mimics the
Scan API. The ScanCriteria is not shared, the tests are built through API calls, and it features
the ability to use callbacks to process the found data. The non-shared aspect of the
ScanCriteria allows scans within scans, since the context no longer needs to be saved and
restored. To create a ScanCriteria, the application will call the mdlScanCriteria_create and
when the application is finished, it must free the memory used by calling the
mdlScanCriteria_free.
ScanCriteria *pScanCriteria = mdlScanCriteria_create ();
mdlScanCriteria_setReturnType
(pScanCriteria,MSSCANCRIT_ITERATE_ELMDSCR,FALSE,TRUE);
mdlScanCriteria_setElmDscrCallback
(pScanCriteria,interogateElements,(void*)pListModel);
mdlScanCriteria_setModel (pScanCriteria,MASTERFILE);
mdlScanCriteria_scan (pScanCriteria,NULL,NULL,NULL);
mdlScanCriteria_free (pScanCriteria);
For application changes, you will need to use the mdlLocate functions to set the element
mask. The new HitPath allows the application to query the related information on the element
that is selected. From the HitPath information, the application can look at other possible
elements that meet the location criteria and possibly adjust what is found. The callback
changes involve adding more parameters to the callback that include the HitPath.
/*----------------------------------------------------------------------+
| |
| name singlelocate_ElmFilter |
| |
+----------------------------------------------------------------------*/
Private int singlelocate_ElmFilter
(
int preLocate, /* => TRUE if pre-locate */
MSElementUnion *selElm, /* => current element */
DgnModelRefP modelRefP,
unsigned long filePosition,
Point3d *pointP,
int viewNumber,
HitPathP hitPath,
char *cantAcceptReason
)
{
ULong filePos;
DgnModelRefP currFile = MASTERFILE;
MSElementUnion headElm;
char reason[]="can only work for cells and text";
strcpy (cantAcceptReason,reason);
Within the locate process is AutoLocate. To enable that, the application must add the
mdlAutoLocate_enable function call. Other features in the locate process include the ability to
identify elements with a "flyover" balloon. To add this feature to the application, use the
mdlLocate_setFunction with the LOCATE_PROVIDE_PATH_DESCRIPTION. This
callback will look something like this:
/*----------------------------------------------------------------------
+*//**
* Demonstrates using the locate callback to modify the string shown when
* hovering over an element. *
* @param path The DisplayPath for this element
* @param description the description that is used in the balloon
these are the sum of all and limited to 256 chars
* @param path The reference path text for this element
* *
+----------------------------------------------------------------------*/
Public void myappLocateInfo
(
DisplayPathP path,
MSWChar* description,
MSWChar* refStr
)
{
ElementRef eRef;
MSElement el;
int eSize;
Some may say that the most important capability in MicroStation V8 is the ability to develop
applications in "Native Code". That is a program written and built using Visual C rather than
the MDL tools. While the user interface is still MDL-based, the code portion is pure C/C++
and is compiled into a DLL. To make a "Native Code" application, first define a resource of
type DLLMDLAPP that will relate the application name to the DLL name.
#define DLLAPPID 1
/* associate app with dll */
DllMdlApp DLLAPPID =
{
"xmlSample", "xmlSample"
}
The source code is written in a .c or .cpp file to identify it as native. Within the source code is
an MdlMain that will take the place of the main in normal applications.
extern "C" DLLEXPORT int MdlMain
(
int argc,
char *argv[]
)
{
RscFileHandle rHandle;
void *setP= NULL;
mdlResource_openFile (&rHandle,NULL,RSC_READONLY);
mdlParse_loadCommandTable (NULL);
mdlSystem_registerCommandNames (cmdNames);
mdlSystem_registerCommandNumbers (cmdNumbers);
return SUCCESS;
}
To integrate command numbers and command names, there are two structures that need to be
populated.
Private MdlCommandName cmdNames [] =
{
{xmlSample_mainDialog,"xmlSample_mainDialog" },
{placeLine_start,"placeLine_start"},
{xmlSample_addLargeXMLFragment,"buildLargeFragment"},
{xmlSample_buildDOMfromDB,"buildDOM"},
{xmlSample_purgeXMLData,"purgeDBXML"},
0
};
Private MdlCommandNumber cmdNumbers[] =
{
{placeLine_start,CMD_PLACE_XMLLINE},
{xmlSample_mainDialog,CMD_XMLSAMPLE_DIALOG},
{xmlSample_addLargeXMLFragment,CMD_DOMTEST_BUILD},
{xmlSample_buildDOMfromDB,CMD_DOMTEST_READ},
0
};
In the Make file, things will need to be rearranged to build the application with the correct
tools. To build a DLL, you need to use some additional rules that are defined in the dlmlink
and dlmcomp mki files. There are a number of macros that need to be defined for these files.
#------------------------------------------------
# Set up to use dlmcomp.mki and dlmlink.mki
#------------------------------------------------
dlmObjs = $(o)$(appName)$(oext)
DLM_NAME = $(appName)
DLM_SYM_NAME = $(appName)sym
DLM_RESL_NAME = $(appName)res
DLM_OBJECT_DEST = $(o)
DLM_LIBDEF_SRC = $(baseDir)
DLM_OBJECT_FILES = $(dlmObjs)
DLM_LIBRARY_FILES = $(dlmLibs)
DLM_NO_DLS = 1 # use DLLEXPORT instead
DLM_NO_DEF = 1
DLM_NOENTRY = 1
DLM_LIBRARY_FILES = $(mdlLibs)dgnfileio.lib \
$(mdlLibs)toolsubs.lib \
$(mdlLibs)ditemlib.lib \
$(mdlLibs)mdllib.lib \
$(mdlLibs)mgdshook.lib
#------------------------------------------------
# Compile the source files for the DLM
#------------------------------------------------
%include dlmcomp.mki
$(o)$(appName)$(oext): $(baseDir)$(appName).cpp
#------------------------------------------------
# Use dlmlink.mki to compile the Dynamic
# Load Specification (.dls) file and link
# the DLM.
#------------------------------------------------
%include dlmlink.mki
You will also need to have Microsoft's Visual C/C++ 6.0 SP4 development tools installed.
The caveat: the application will not run on Bentley PowerDraft since DLLs are not enabled.
The upgrade path should not involve major logic changes so it can become a mechanical
exercise to get the application to compile. Once the application is compiling, the process
becomes little more than refining the application to work with new and improved capabilities
in MicroStation.