Planimate as a DLL

From Planimate Knowledge Base
Jump to navigation Jump to search

Planimate can convert itself into a DLL which can be embedded in other applications. This is achieved using the Planimate-as-a-DLL API (loadpl) which is made available to licensed Planimate users.

Planimate has been successfully incorporated into dotNET applications using the Planimate dotNET wrapper.

This page documents the use of the Planimate DLL and the win32 sample application loadpl.It is current as of Planimate 6.4.0.

Note that the API makes reference to Planimate5 in file and class names but the latest version is compatible with and intended for use with version 6.

Creating A DLL

A Planimate DLL can be created in various ways.

Converted Planimate EXE

A Planimate EXE can be make itself into a DLL by running it with the command line option /MAKEDLL. If the original EXE is called planimate.exe, this creates planimate.dll which can then be loaded by another application and commanded to load a model.

PBA Compiled To DLL

When compiling a Planimate Based Application, the Standalone EXE Creation dialog includes the option to "Make A DLL as well". This option requires an Open PBA Level 4 License and selection of the Unkeyed Standalone EXE Type.

Directly Compiled By InterDynamics

Planimate is not normally distributed as a DLL but this is a build option in its source project and may be useful for debugging.

Planimate API

The Planimate API is distributed as a Visual Studio project containing two targets which together form an application which demonstrates loading/running two instances of Planimate within a simple win32 container application.

Complete source to the two targets is provided.

pl5loader DLL

This implements a loader which manages the creation of separate Planimate instances, each running in a thread of its own. The thread management requires great care, this loader encapsulates the complexity into a simple C API which is defined in pl5loader.hpp.

LoadPL EXE

This is a simple Win32 C++ application which demonstrates using pl5loader to load/run two instances of Planimate.

The LoadPL.cpp sample includes demonstrations of:

  • Start/stop/pause/close PL using the PL_Run() function
  • Re-loading a model in a given PL instance (without unloading the instance)
  • Re-loading an entire instance
  • Switching between advance to time / animation (with ETA dialog)
  • Setting data in a model
  • Broadcasting to a model
  • Broadcast callbacks from model

Due to the way LoadLibrary() operates, it is necessary that each Planimate instance be loaded from a separate DLL file, even though the DLLs may be identical. In this example demo.dll and demo2.dll are standard Planimate EXEs which have been converted into DLLs by running them with the /makedll command line option.

In your own application you should convert your version of Planimate (which most likely will be newer than the example in this API) to a DLL. This is best done using the "Make A DLL as well" option when creating a Planimate Based Application.

API File Summary

The Planimate API and loadPL sample has the following key files.

units.hpp
planidll.hpp

These define data types, export function typedefs and enums used when calling Planimate. The exported function names are as for the typedefs without the preceeding 't', eg: tPL_LoadModel() is exported as PL_LoadModel().

pl5loader.cpp
pl5loader.hpp
planimate5.hpp
planimate5.cpp

These build DLL pl5loader.dll which implements the multi-threaded management of multiple planimate dll instances.
loadpl.cpp
resource.h
loadpl.rc
stdafx.cpp

These build EXE loadpl.exe which demonstrates loading two Planimate instances using the pl5loader.dll.
demo.dll
demo2.dll

These are Planimate developer EXEs converted into DLLs using the /makedll command line option and are loaded by loadpl.
demo.mdl
demo.db

The test model loaded by the loadpl sample application.
matmult.dll
matmult64.dll

A planimate callable DLL which the demo.mdl test model uses to demonstrate how Planimate-as-a-DLL can call other DLLs.
loadpl.vcproj
Visual Studio 2008 project which builds both pl5loader.dll and loadpl.exe targets. 32 and 64 bit targets are supported.


Usage walkthrough

This documents loading Planimate-as-a-DLL using the pl5loader DLL.

If you choose to load Planimate-as-a-DLL directly, you must take great care in the setup and managegment of the Planimate thread. Use Planimate5.cpp as a reference as any deviation from the procedure may lead to hard to replicate multi-threading bugs, race conditions, lock ups etc.

Pre-requisites

You need either a Planimate application converted to a DLL (Make A DLL as well in the PBA compiler) or a Planimate modeller EXE converted to a dLL (use the /MAKEDLL command line option).

Firstly, your code should #include pl5loader.hpp.

You will need a HWND (window handle) of the parent window into which Planimate will create a child window which completely fills the parent window. Planimate uses its own child window internally and this child window will have its message queue in Planimate's thread instead of the thread of the parent window.

Initialisation

You use LoadLibrary() to load the pl5loader DLL and bind to its functions (as in loadpl.cpp:InitPLLoader()). Call PLL_Init() with the path to the Planimate DLL, command line arguments ("/debugfile" enables Planimate to create the planimat.dbg file) and the parent window handle.

PLHANDLE pl = PLL_Init(dll_name,"/debugfile",hwnd);

This will return a PLHANDLE object which you use in further API calls. This is NOT a win32 HANDLE.

Before you work with this handle, you must call PLL_WaitRunning(handle,60000). This waits until the PL instance is ready (model data loaded for a PBA). You might do this after having initiated a number of PLL_Init() operations. the 60000 sets a timeout in ms in case something goes very wrong. Increase it for a slow to load PBA. If PLL_Init() fails, it returns NULL.

At this stage Planimate is loaded. If you passed a PBA DLL, it will have the model loaded but still stopped. If you specified an PL (editing) DLL, you will want to load a model.

Interacting with a given Planimate instance (there may be multiple loaded) involves retrieving a handle to the function you want to call from the loader DLL. The following demonstrates calling PL_LodModel() for an instance of Planimate called "pl" returned from PLL_Init().

 ((tPL_LoadModel*)PLL_GetProc(pl,ePL_LoadModel))(modelname,NULL);

Notice the distinction between the Planimate Loader (PLL_xxxx) and Planimate (PL_xxxx) functions. PLL_GetProc() will be explained in a section below.

Suspend/Resume of Planimate

Since Planimate is running in its own thread, data corruption can occur if you read or write data which a running model may be simultaneously accessing, even if the model is written to anticipate such changes in the data. The pl5loader DLL provides functions PLL_SuspendThread() and PLL_ResumeThread() which are used to temporarily suspend Planimate from running the model. These should be wrapped around data access functions, as documented in planidll.hpp.

Calling Planimate API functions and PLL_GetProc()

The pl5loader DLL does not directly implement all the calls supported by Planimate itself (as declared in planidll.hpp). It does bind to all the functions and gives access to function pointers. You request a function using PLL_GetProc() and the enum ePLProcs.

For example, reading a running model's run clock involves calling planimate function:

PL_GetSystemInfo(PLSI_CLOCK).

To do this in C++ you first need the function pointer for PL_GetSystemInfo() for that loaded instance of Planimate. Note the function pointers differ per loaded instance.

Calling functions the long way

You get a PL_xxxx function pointer from the loader using PLL_GetProc() and the corresponding function type and enum defined in planidll.hpp:

tPL_GetSystemInfo * PL_GetSystemInfo = (tPL_GetSystemInfo*)PLL_GetProc(pl,ePL_GetSystemInfo);

Now you have the function pointer, you can call it:

double clock_time = PL_GetSystemInfo(PLSI_CLOCK);

Using a macro to simply calls to Planimate

In the loadpl sample, the two step process above is greatly simplified using a macro that you can copy and use in your own project:

  1. define GETPLPROC(pl,procname) ((t##procname *)PLL_GetProc(pl,e##procname))

Using this macro you can retrieve the run clock using:

double clock_time = GETPLPROC(pl,PL_GetSystemInfo)(PLSI_CLOCK);

The macro expands the call to get the enum and corresponding typecast, in this case:

double clock_time = ((tPL_GetSystemInfo*)PLL_GetProc(pl,ePL_GetSystemInfo))(PLSI_CLOCK);

This works in a C++ hosting application. In C# a similar approach to wrapping the functions might be useful, and remember each instance of Planimate will have its own set of function pointers.

Run Control

The PL_Run() function enables control of the Planimate run engine including starting, pausing, stopping and closing the Planimate instance.

To get a newly loaded model running, call:

((tPL_Run*)PLL_GetProc(pl,ePL_Run))(PLRUNCMD_Run);

This will set the model running. The host application does not need to do anything else since Planimate runs in its own thread, keeps its window updated and accepts any input when its window has focus.

The only requirement is that the host application continue to process its own window events since its child Planimate window may send it messages and not processing them could cause a deadlock.

Terminating Planimate

The host application can use the loader to terminate and unload a Planimate instance:

PLL_Term(pl);

This gracefully stops the model, deletes it from memory, stops the thread and unloads the DLL from memory.

PL5Loader Exported Functions

The functions exported by the pl5loader DLL greatly simplify dealing with Planimate5 instances running in their own threads.

See pl5loader.hpp for function typedefs, comments and notes on the exported functions. See loadpl.cpp for use of LoadLibrary() and GetProcAddress() to bind to the functions.

Planimate DLL Exported Functions

Each Planimate DLL instance exports functions with types declared in planidll.hpp. If you are using the PL5Loader loader DLL (highly recommended) you do not need to directly initialise Planimate or manage the thread it runs in. You get access to the DLL functions using the loader's PLL_GetProc() function.

If you want to manage Planimate directly, you must follow the technique in pl5loader.cpp / planimate5.cpp, particularly the thread synchronisation.

Refer to planidll.hpp for function typedefs, comments and basic notes. Material here assumes familiarity with the planidll.hpp header.

PL_GetSystemInfo / PL_SetSystemInfo

These enable access to a number of attributes.

PLSI_CLOCK

This returns the model run clock during a simulation run. This is the number of seconds since the model's Run Start Date.

PLSI_ADVANCETOTIME

This returns the time (measured as for clock) that the model is advancing to. If set and the time is in the future to the current simulation clock, the simulation will advance to the time if running. If paused, the advance will occur when the run is continued.

The engine must be in MD_PAUSED or MD_SIMULATE when using this otherwise it returns 0 and any setting is ignored.

PLSI_CURRENTPENDING

This returns the number of events currently in the simulation Future Events List.

PLSI_ENGINESTATE

This returns the state of the simulation engine as defined in ePLMode enum:

enum ePLMode
{
MD_OBJECT - = 0, // editing objects or user mode with engine stopped
MD_FLOWEDIT,     // flow or interaction edit mode (flow editor has submode)
MD_PAINT,        // editing paint object layer
MD_SIMULATE,     // engine started and model is running
MD_PAUSED        // engine started and model is paused
};

PLSI_CURRENTFILEVERSION

This returns the version of MDL files that the Planimate DLL will save

PLSI_OLDESTFILEVERSION

This returns the oldest file ersion that the Planimate DLL can load and run.

PLSI_LOADEDFILEVERSION

This returns the version of the loaded model, as appears next to "V" near the beginning of the MDL file.

PLSI_DLLVERSION

This returns the version of the DLL API. As of December 2013 this is 4.

PLSI_PAUSEAFTERADVANCE

When this is set, the run engine will pause after an advance-to-time instead of continuing with on screen animation. The run engine must be in MD_SIMULATE or MD_PAUSED state otherwise this returns 0 and setting is ignored.

Broadcasts

You can broadcast an item into a Planimate model using PL_SendBroadcast(). You can include tuple data using PL_SendBroadcastTuple().

You can send a silent broadcast (useful when Planimate is paused) which does not visibly continue the model run using PL_SendBroadcastBG() and PL_SendBroadcastTupleBC().

Planimate must be suspended (PL_SuspendThread()) before sending any broadcasts.

Broadcast Callbacks

You can arrange for Planimate to call back a function you provide using PL_RegisterBroadcastCallback(). This requires a broadcast handle which you can retrieve using PL_GetBroadcastName().

The callback function must conform to Win32 CALLBACK linkage and be declared match tPL_BroadcastCallback.

When using the PL5Loader, it is useful to pass the PLHANDLE as the userdata. This is passed back in the callback. Or you can pass your own management structure to enable you to track which Planimate instance has sent the broadcast, if necessary.

Advance To Time Callback

typedef PLRESULT CALLBACK tPL_PauseCallback(double the_time,
int    stop_reason, // SIMUL_Run
void * userdata);

typedef PLDLLFN void tPL_RegisterPauseCallback(tPL_PauseCallback * fn,
void * userdata);

It is often useful tfor an application which embeds Planimate to be notified when a model becomes pause, eg: due to an advance-to-time completing. This can set up by calling PL_RegisterPauseCallback(). You need to call this with a pointer to a function matching tPL_PauseCallback. The userdata field is an optional pointer you can include if you need to track multiple callbacks.

The callback function also gets the current simulation clock value and the reason the model became paused, see the enum SIMUL_Run in planidll.hpp.

Data Access

The application using Planimate-as-a-DLL can access data objects (Attributes, LabelLists, Tables) which have been registered in the model's _Data_Objects label list. The PL_GetDataObjectName() function is useful if you know the name of the data object. This returns a PLDataObject handle.

Once you have a PLDataObject handle, you can use the appropriate functions for that object type, for example for an Attribute you would use PL_GetAttValue(). PL_SetAttValue() for numeric access and PL_GetAttText() and PL_SetAttText() for text formatted attributes.

Data formatting Services

Numerical data in Planimate including times is set/read as double values. You can access Planimate's data parsing/formatting routines using PL_StringToValue() and PL_ValueToString(). These take a format type parameter which are defined in the eTFUnit enum in file units.hpp.

You can query the display name of Planimate's different formats using PL_FormatName(). Note that these names could change in future versions of Planimate.