written by Sumbera, S., April. 2003
6. Going native with MDL
this chapter DISCUSS how to easily migrate interpreted MDL code into native code. |
No doubt that V8 brought relief for Windows based developers in a way that Bentley made improvements for interoperability between MDL and native code and moreover they partially enabled to write entire code natively. I wrote partially because it is not still 'perfect', however good enough to get rid of interpreted MDL, command line debugger, or may be make files or some slow performance issues. Well, this chapter is intended for those who would like to hear my opinion and suggestion when porting some MDL code into native. if you have already some code in traditional MDL and you want smoothly try the native compilation, you are the right person to read this short tutorial.
Well there are a lot of nice IDE for C/C++ code, however I would recommend you to use Microsoft Visual Studio (because I don't have opportunity to try other one ..and I guess Borland C++ builder could be also gorgeous... ;). So read and understand the article and FAQ of the research article :Setting Up Visual Studio 6 IDE for MDL programming.
Well, if you have some amount of code written in MDL which works correctly you have may be less reasons to port it into native, however who said you cannot have both at the same time? I mean that you may just do slight modification of your code to enable native compilation while preserving backup way with compilation into interpreted code... this mean that if you will find any problems in native code execution you may check the execution in interpreted code to see if all work correctly in original form...Well you may be surprised but not all still works as would be expected in native code (for instance some events in version of v8.0..)
Any extensions made in MDL against ANSI- C - it covers usage of function modificators cmdName and cmdNumber. The way to change them is similar to way of publishing dialog hooks... First you need to create map of functions and its commands - this is done by array of MdlCommandName or MdlCommandNumbers respectively and then register the map, for instance replacing cmdNumbers:
Private
MdlCommandNumbers commandNumbers [] ={
{foo_do, CMD_DOING_DO},
{foo_undo, CMD_DOING_UNDO},
{foo_redo, CMD_DOING_REDO},
0
/* The table must be NULL-terminated */};
simmilary you need to replace cmdName with :
Private
MdlCommandNames commandNames [] ={
{foo_do, "foo_do"},
{foo_undo, "foo_undo"},
{foo_redo, "foo_redo"},
0 /* The table must be NULL-terminated */
};
..in Main function register your command number and command name maps :
mdlSystem_registerCommandNumbers (commandNumbers);
mdlSystem_registerCommandNames (commandNames);
alternatively you may use functions
mdlSystem_registerCommandNumbersByDesc or
mdlSystem_registerCommandNamesByDesc
Some mdl includes type name conflict with windows defined types. To avoid
this mismatch you need to add into begining of your source file this if defs:
#if
defined winNT#define
MAIN MdlMain /* native entry point */#include
<windows.h>#define
__NORECTANGLE__ 1/*default entry point for dll */
BOOL WINAPI DllMain (HANDLE hMod, DWORD reason, LPVOID res){ return 1;}
#else
#define
MAIN main /* interpreted entry point */#endif
As you may see above, we defined different entry points for interpreted and native code. This way you will be able to maintain in one source file different builds - either for native or for interpreted release. But you need to reflect the definition above in your main function:
DLLEXPORT
int MAIN (int argc, char *argv[] )
DLLEXPORT is defined in basedefs.h as :
defined (winNT) && !defined (mdl)#if
# define
DLLEXPORT __declspec( dllexport )# define
DLLIMPORT __declspec( dllimport )#else
# define
DLLEXPORT# define
DLLIMPORT#endif
If code is compiled and loaded as native, first is called DllMain with reason DLL_PROCESS_ATTACH then mdlMain. In unload process DllMain is called as well with reason DLL_PROCESS_DETACH.
This is little bit boring, but you need to make resource through which MicroStation will be able to load dll. This resource is then compiled into MA file which you normaly load as it would be interpreted code, but it actually does only one thing - loading of your dll file.It may look in resource file like this:
#include
"rscdefs.h"#define
DLLAPP_SMAZ 1DllMdlApp DLLAPP_SMAZ =
{
"smaz"
, "smaz4.dll"}
...and you need to add this resource into your make file for compilation of course...
If you wish to have allocated memory and resources connected with the taskid of the starting MA file, you may change memory allocating function with its corresponding dlmSystem_ functions. This step is optional. Functions are listed below:
dlmSystem_mdlMalloc, dlmSystem_mdlCalloc, dlmSystem_mdlRealloc, dlmSystem_mdlFree, dlmSystem_mdlStrdup, dlmSystem_mdlFopen, dlmSystem_mdlFclose. You may do #define for these function:
#if
defined winNT#include
<dlmsys.fdf>#include
<msvar.fdf>#define
MALLOC dlmSystem_mdlMalloc /* native memory allocation */#else
#define
MALLOC malloc /* interpreted memory allocation */#endif
dlmSystem_callAnyFunction - this is very useful function for calling either mdl of native function. The function recognize if the function pointer you try to invoke is in native or in interpreted code. This function should be prefered rather than
mdlDialog_callFunction - you may use it to call only mdl function from another mdl (you probably already used it ) to call mdl function from native code use dlmSystem_callMdlFunction instead...again these two functions has same signature so you may easily make #define
The variables are defined in msvar.fdf so you need to include it for native code.
You need to add something into your make file for DLL compilation and linking. First you need to properly define all variables that bmake.exe uses for DLL compilation...you may find them in file dlmlink.mki. Then define the rule for compilation your mc file into native obj file - this is important cause you need not to change extension of your original file into c, you may maintain your code in one source file (with .mc extension) rather than in two files with slight modifications and make compilation into interpreted or native code.
The make file may look like this:
#-- here goes you initial definitions of variables like debug,baseDir etc...
MAKE_BSC = 1 #-- generate
browse info
%include mdl.mki
dlmObjs = $(o)$(appName)$(oext)
DLM_NAME = $(appName)
DLM_DEST = $(mdlapps)$(appName).dll
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
%include dlmcomp.mki
#-- the rule for
compilation of mc file into obj
.mc.obj:
$(msg)
$(CCompCmd) $(cIncs) $(cDefs) $(cuser) $(ustationIncludes) $(ustationIdIncludes)
$(CCompOpts) -TC -Fo$@ $%$*.mc
~time
#-- native code generation
(o)$(appName)$(oext): $(baseDir)$(appName).mc
#-- native code linking
%include dlmlink.mki
#-- make dll loader
appDllRscs = $(o)$(appName).rsc
$(o)$(appName).rsc : $(baseDir)$(appName).r
#-- the appliaction with extension mal is ma loader
#-- the extension .ma is leaved for interpreted code generation
$(mdlApps)$(appName).mal : $(appDllRscs)
$(msg)
> $(o)make.opt
-o$@
$(appDllRscs)
<
$(RLibCmd) @$(o)make.opt
~time
#-- ..continue with your interpreted code make
This issue has been already discussed in article Setting up Visual studio for MDL programming. However, there is one interesting point to mention. You may directly debug your native code from IDE ebven you have defined the project for Visual Studio as MAKE FILE !
If you add this into Program arguments of Project/Settings you will run your native code as console application
..and execution lokos like this - IDE debugging:
while adding this will run your application as interpreted code:
..and exectution looks as you know I guess perfectly - command line debugger:
Visual Studio needs to load symbols for your dll into memory before the dll is really loaded by MicroStation process. So you need to specify Additional DLLs in Projects/Settings/Debug Tab/Category combo Box/Additional Dlls. There you specify your dll with full path:
Then Debugger will first load your dll symbols and then others dlls:
For more information for dll breakpoints follow this link to MSDN
..to be ..may be... continued
mail comments to stanislav@sumbera.com