Writing Static DLL's

Navigation:  Language Reference > Functions > External >

Writing Static DLL's

Previous pageReturn to chapter overviewNext page

Almost every compiler that can build Windows applications, can also build dll-files containing dll-functions. For two compilers an example will be given how to build the static dll's (i.e. dll functions that do not contain internal model states) in combination with 20-sim. The two compilers are: Visual C++ and Borland C++ .

 

When the simulator has to call the user defined dll for the first time the dll is linked and the appropriate function is called. At the end of the simulation run the dll will be disconnected from the simulator. In general the dll-function called by the simulator has the following syntax in 20-sim:

 

Y = dll(filename,functionname,X);

 

For example (equation model):

 

parameters

 string dll_name = 'example.dll';

 string function_name = 'myFunction';

variables

 real in[2],out[2];

equations

 in = [ramp(1),ramp(2)];

 out = dll(dll_name, function_name, in);

 

 

The name of the function is the actual name which was given to the corresponding string parameter. The return value of the function determines whether the function was successful or not. A return value of 0 means success, every other value error. When a nonzero value is returned the simulation stops after finishing its current simulation step. The parameters given to the function correspond directly to the 20-sim parameters: double pointers for the inputs and outputs point to an array of doubles.

Function arguments

The user-function in the dll must have certain arguments. The function prototype is like this:

 

 int myFunction(double *inarr, int inputs, double *outarr, int outputs, int major)

 

where

inarr: pointer to an input array of doubles. The size of this array is given by the second argument.
inputs: size of the input array of doubles.
outarr: pointer to an output array of doubles. The size of this array is given by the fourth argument.
outputs: size of the output array of doubles.
major: boolean which is 1 if the integration method is performing a major integration step, and 0 in the other cases. For example Runge-Kutta 4 method only has one in four model evaluations a major step.
 

Initializing the DLL-file

When the dll is linked to the simulator it is often useful to perform some initializations. There are several ways to perform these initializations.

Method 1

When the simulator has attached the dll-file it automatically searches for a function with the name ‘int Initialize()’. If this function is found it is called. The return value is checked for success, 1 means success, 0 means error. At the end of the simulation run just before the dll-file is detached the simulator searches for a function called ‘int Terminate()’. In this function the necessary termination action can be performed like cleaning up allocated memory.

 

At the start of every run the simulator also searches a function with the name 'int InitializeRun()'. If it is found it is called the same way the Initialize function was called. At the end the same happens with the function 'int TerminateRun()'. This is very useful in multiple run simulations, in this case the dll-file is linked only once with the simulator, and only once the Initialize and Terminate functions are called. But for every subsequent run the InitializeRun and TerminateRun functions are called so every run can be initialized and terminated gracefully. In this case in the Terminate function collection of multiple run data could be collected and saved for example.

 

So suppose you have a multiple run with 2 runs, the following functions (if they exist) will be called in the dll-file in this order:

 

Initialize()

InitializeRun()

TerminateRun()

Terminate()
 

Method 2

Using the DLLEntryPoint function. This function is automatically called when the library is linked or detached. Both in Visual C++ and in Borland C++ this function has the same syntax. However when using visual C++ as the compiler, our simulator which is build with Visual C++ does not seem to be able to call this function. So the framework as given below only works with a Borland C++ dll.

 

// Every dll has an entry point LibMain || DllEntryPoint

// and an exit point WEP (windows exit point).

BOOL WINAPI DllEntryPoint(HINSTANCE hinstDllDWORD fdwRreasonLPVOID plvReserved)

{

  if (fdwRreason == DLL_PROCESS_ATTACH)

  {

    return 1; // Indicate that the dll was linked successfully.

  }

  if (fdwRreason == DLL_PROCESS_DETACH)

  {

    return 1; // Indicate that the dll was detached successfully.

  }

  return 0;

}

 

Method 3

When using an object oriented language it is possible to have a global variable which is an instance of a class. When linking the dll-file to the simulator the constructor function of this global variable will be called automatically. In this constructor the initialization can be performed. When the dll-file is detached the destructor of the global variable is called. Some care should be taken with this method of terminating. E.g. if the user is asked for a filename using a FileDialog this sometimes causes the complete application to crash. The reason for this is that some of the dll-files contents was already destructed and that some functionality may not work anymore resulting in an application error. The solution for this problem is tot use method 1 of initialization and destruction.

 

Frame work for a Visual C++ dll-file implementation.

#include <windows.h>

#define DllExport __declspecdllexport )

extern "C"

{

  DllExport int myFunction(double *inarrint inputsdouble *outarrint outputsint major)

  {

    ...       // function body

    return 0; // return successful

  }

  DllExport int Initialize()

  {

    ...       // do some initializations here.

    return 0; // Indicate that the dll was initialized successfully.

  }

  DllExport int Terminate()

  {

    ...       // do some cleaning here

    return 0; // Indicate that the dll was terminated successfully.

  }

}

 

Frame work for a Borland C++ dll-file implementation.

#include <windows.h>

extern "C"

{

  int _export myFunction(double *inarr, int inputs, double *outarr, int outputs, int major)

  {

    ...       // function body

    return 0; // return successful

  }

  int _export Initialize()

  {

    ...       // do some initializations here.

    return 0; // Indicate that the dll was initialized successfully.

  }

  int _export Terminate()

  {

    ...       // do some cleaning here

    return 0; // Indicate that the dll was terminated successfully.

  }

}

 

// Every dll has an entry point LibMain || DllEntryPoint

// and an exit point WEP.

BOOL WINAPI DllEntryPoint(HINSTANCE hinstDllDWORD fdwRreasonLPVOID plvReserved)

{

  if (fdwRreason == DLL_PROCESS_ATTACH)

  {

    ...       // do some initializations here.

    return 1; // Indicate that the dll was initialized successfully.

  }

  if (fdwRreason == DLL_PROCESS_DETACH)

  {

    ...       // do some cleaning here

    return 1; // Indicate that the dll was initialized successfully.

  }

  return 0;

}

 

Note

Make sure that Borland C++ does not generate underscores in front of the exported function names! This can probably be found in the compiler settings.

Working example

Here is a complete working example of how to use a dll-function from within the simulator. This code will compile with Visual C++ or gcc. See the Borland C++ framework how to write this code in Borland C++.

 

/* Example C++ DLL for 20-sim */

#include <windows.h>

#include <math.h>

#include <fstream>

#include <stdio.h>

 

#define DLLEXPORT __declspecdllexport )

 

using namespace std;

 

ofstream outputStream;

#ifdef _MSC_VER

#define snprintf _snprintf

#endif

 

extern "C"

{

  char g_lasterrormessage[255]; /* Used to store a DLL error message

                                   for transfer to 20-sim */

  const char* g_modelpath;

 

  /**

  * This is an example of a function that can be called in your 20-sim

  * model using the dll() Sidops call

  *

  * @param inarr   This double array contains all inputs that will be

  *                send from 20-sim to the other side

  * @param inputs  20-sim tells the dll how many elements inarr[]

  *                contains.

  * @param outarr  Result from this dll function that will be returned

  *                to 20-sim

  * @param outputs 20-sim tells the dll how many elements it expects

  *                (and allocated) in outarr.

  * @param major   1=major integration step, 0=minor step (e.g. an

  *                intermediate integration method step in Runge Kutta

  *                4)

  **/

  DLLEXPORT int myFunction(double *inarrint inputsdouble *outarrint outputsint major)

  {

    // Check the sizes of our input and output arrays

    if (inputs != outputs)

    {

      snprintf(g_lasterrormessage, 255, "%s: expects that the number of inputs is equal to the number of outputs.", __FUNCTION__);

      return 0; // Failure

    }

 

    for (int i = 0; i < inputs; i++)

    {

      outarr[i] = cos(inarr[i]);

      outputStream << inarr[i] << " " << outarr[i] << " ";

    }

    outputStream << endl;

    return 1; // Success

  }

 

  /****** Initialization and cleanup ******/

 

  /* Note 1:

   *   The Initialize(), InitializeRun(), Terminate() and TerminateRun()

   *   functions are optional.

   *   Implement them when you need to initialize something before the

   *   actual experiment is started and to cleanup/reset your DLL

   *   functionality for a next run.

   * Note 2:

   *   When these functions are implemented, the "continue run"

   *   functionality in 20-sim is disabled.

   */

 

  /**

   * Initialize() [optional]

   *

   * This function is called by the 20-sim simulator BEFORE starting the

   * simulation experiment (and only once in a multiple run experiment)

   * to initialize the dll properly.

   */

  DLLEXPORT int Initialize()

  {

    outputStream.open("c:\\temp\\data.log");

    return 0; // Indicate that the dll was initialized successfully.

  }

  /**

   * InitializeRun() [optional]

   *

   * This function is called by the 20-sim simulator BEFORE starting the

   * simulation experiment (and only once in a multiple run experiment)

   * to initialize the dll properly.

   */

  DLLEXPORT int InitializeRun()

  {

    /* Clear lasterrormessage before every run. */

    snprintf(g_lasterrormessage, 255, "");

 

    return 0; /* Indicate that the dll was initialized successfully. */

  }

 

  /**

   * TerminateRun() [optional]

   *

   * This function is called by 20-sim after each finished run

   */

  DLLEXPORT int TerminateRun()

  {

    /* Cleanup / reset your DLL here for the next run

     * (e.g. in a multiple run experiment)

     */

    return 0; // Indicate that the dll was terminated successfully.

  }

 

  /**

   * Terminate() [optional]

   *

   * This function is called by 20-sim on a DLL unload

   */

  DLLEXPORT int Terminate()

  {

    outputStream.close();

    return 0; // Indicate that the DLL was terminated successfully.

  }

 

  /**

   * LastErrorMessage() [optional]

   * Used by 20-sim to fetch a string with the last error that occurred

   * within the DLL

   * @return A char pointer to a string indicating the error message

   */

  DLLEXPORT char* LastErrorMessage()

  {

    return g_lasterrormessage;

  }

 

  /**

  * RegisterModelPath() [optional]

  * This function is called by 20-sim before Initialize() to learn the

  * DLL where the model is located. It can be used e.g. to find data

  * files stored in the same folder as the model.

  *

  * @param modelPath char pointer to the model directory

  * @return 0 if parameter is set successfully, 1 if not successful.

  */

  DLLEXPORT int RegisterModelPath(const char * modelPath)

  {

    g_modelpath = modelPath;

  }

}

 

Usage within 20-sim

Suppose the dll has been created as "example.dll". With the following code this model can be tested:

 

parameters

 string dll_name = 'example.dll';

 string function_name = 'myFunction';

variables

 real in[2],out[2];

equations

 in = [ramp(1),ramp(2)];

 out = dll(dll_name, function_name, in);

 

Note that the general function "dll" is used. The arguments of this function, dll_name and function_name, are parameters which are used to denote the dll that should be used and the function of that dll that should be called. You can load this model from the Demonstration Models Library:

1.Open the Editor.
2.From the demo library open the model DllFunction.emx (choose File and Open)
3.Start the simulator (Model menu and Start Simulator).
4.Start a simulation run (select Run from the Simulation menu).

Code generation notice

20-sim allows for code generation of the 20-sim model. In case of a DLL call 20-sim cannot generate the complete code for a DLL call since it only knows the DLL file name, function name and arguments and not the internal DLL code.

Therefore, in the generated code, the dll(dll_name, function_name, in) will be generated as:

 
%dll_name%_%function_name % (double *inarr, int inputs, double *outarr, int outputs, int major)

Where %dll_name% and %function_name% are replaced with the actual DLL and function name. You have to add the C-code implementation of this function yourself to the generated code before you can compile it.