Planimate as a DLL: Difference between revisions

From Planimate Knowledge Base
Jump to navigation Jump to search
mNo edit summary
No edit summary
 
(4 intermediate revisions by the same user not shown)
Line 1: Line 1:
Planimate can convert itself into a DLL which can be embedded in other applications, includding dotNET applications using the Planimate dotNET wrapper.  
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.  


This page documents the use of the Planimate DLL and the win32 sample application.  
Planimate has been successfully incorporated into dotNET applications using the Planimate dotNET wrapper.  


=== Creating A DLL  ===
This page documents the use of the Planimate DLL and the win32 sample application loadpl.It is current as of Planimate 6.4.0.<br>
 
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.<br>
 
== Creating A DLL  ==


A Planimate DLL can be created in various ways.  
A Planimate DLL can be created in various ways.  


==== Converted Developmen EXE  ====
=== 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.  
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  ====
=== 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.  
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  ====
=== Directly Compiled By InterDynamics<br> ===


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 is not normally distributed as a DLL but this is a build option in its source project and may be useful for debugging.  


=== LoadPL Demo ===
== Planimate API ==


LoadPL is an application which demonstrates loading 2 Planimate instances (running in their own threads) into the one Win32 application, then commanding them independently from the menu bar, including run/psuse/stop, advance to time/animate and reading data, sending broadcasts and getting callbacks from Planimate generated broadcasts.  
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.  


LoadPL contains two targets  
Complete source to the two targets is provided.


==== pl5loader DLL  ====
=== 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.<br>
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
=== LoadPL EXE ===


This is a simple Win32 C++ application which demonstrates using pl5loader to load/run two instances of Planimate.  
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:
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.


*Start/stop/pause/close PL using the PL_Run() function
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.
*Re-loading a model in a given PL 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<br>


=== API File Summary  ===
== API File Summary  ==


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


{| width="314" height="563" cellspacing="1" cellpadding="1" border="1"
{| width="314" border="1" cellspacing="1" cellpadding="1"
|-
|-
|  
|  
units.hpp<br>planidll.hpp<br>
units.hpp<br>planidll.hpp<br>  


| These define data types, export function typedefs and enums used when calling Planimate.<br>
| 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:&nbsp;tPL_LoadModel()&nbsp;is exported as PL_LoadModel().<br>
|-
|-
|  
|  
pl5loader.cpp<br>pl5loader.hpp<br>planimate5.hpp<br>planimate5.cpp<br>
pl5loader.cpp<br>pl5loader.hpp<br>planimate5.hpp<br>planimate5.cpp<br>  


| These build DLL pl5loader.dll which implements the multi-threaded management of multiple planimate dll instances.<br>
| These build DLL pl5loader.dll which implements the multi-threaded management of multiple planimate dll instances.<br>
|-
|-
| loadpl.cpp<br>resource.h<br>loadpl.rc<br>stdafx.cpp<br><br>
| loadpl.cpp<br>resource.h<br>loadpl.rc<br>stdafx.cpp<br><br>  
| These build EXE loadpl.exe which demonstrates loading two Planimate instances using the pl5loader.dll.<br>
| These build EXE loadpl.exe which demonstrates loading two Planimate instances using the pl5loader.dll.<br>
|-
|-
| demo.dll<br>demo2.dll<br><br>
| demo.dll<br>demo2.dll<br><br>  
| These are Planimate developer EXEs converted into DLLs using the /makedll command line option and are loaded by loadpl.<br>
| These are Planimate developer EXEs converted into DLLs using the /makedll command line option and are loaded by loadpl.<br>
|-
|-
| demo.mdl<br>demo.db<br><br>
| demo.mdl<br>demo.db<br><br>  
| The test model loaded by the loadpl sample application.<br>
| The test model loaded by the loadpl sample application.<br>
|-
|-
| matmult.dll<br>matmult64.dll<br><br>
| matmult.dll<br>matmult64.dll<br><br>  
| A planimate callable DLL&nbsp;which the demo.mdl test model uses to demonstrate how Planimate-as-a-DLL&nbsp;can call other DLLs.<br>
| A planimate callable DLL&nbsp;which the demo.mdl test model uses to demonstrate how Planimate-as-a-DLL&nbsp;can call other DLLs.<br>
|-
|-
| loadpl.vcproj<br>
| loadpl.vcproj<br>  
| Visual Studio 2008 project which builds both pl5loader.dll and loadpl.exe targets. 32 and 64 bit targets are supported.<br>
| Visual Studio 2008 project which builds both pl5loader.dll and loadpl.exe targets. 32 and 64 bit targets are supported.<br>
|-
|-
| <br>
| <br>  
| <br>
| <br>
|}
|}


=== Usage walkthrough<br> ===
== 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&nbsp;(editing) DLL, you will want to load a model.<br>
 
Interacting with a given Planimate instance (there may be multiple loaded)&nbsp;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"&nbsp;returned from PLL_Init().<br>
 
&nbsp;((tPL_LoadModel*)PLL_GetProc(pl,ePL_LoadModel))(modelname,NULL);
 
Notice the distinction between the Planimate Loader (PLL_xxxx)&nbsp;and Planimate (PL_xxxx)&nbsp;functions. PLL_GetProc() will be explained in a section below.
 
=== Suspend/Resume of Planimate<br>  ===
 
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()<br> ===
 
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.<br>
 
==== Calling functions the long way<br> ====
 
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<br> ====
 
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:
 
#define GETPLPROC(pl,procname) ((t##procname *)PLL_GetProc(pl,e##procname))<br>
 
Using this macro you can retrieve the run clock using:<br>
 
double clock_time = GETPLPROC(pl,PL_GetSystemInfo)(PLSI_CLOCK);<br>


This documents loading Planimate-as-a-DLL&nbsp;using the pl5loader DLL. If you choose to load planimate-as-a-DLL&nbsp;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.<br>
The macro expands the call to get the enum and corresponding typecast, in this case:<br>


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


One instance of the pl5loader DLL manages loading and accessing multiple planimate instances. Once the pl5loader.dll is loaded and functions are bound, loading a PL is a matter of  
This works in a C++&nbsp;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.<br>


  PLHANDLE pl = PLL_Init("PLDLL.dll","",parent_hwnd);
=== Run Control ===
// wait for it to be loaded and running
PLL_WaitRunning(pl);


To call a PL function as defined in planidll.hpp you need to retrieve its pointer and cast it to its typdef. PLL_GetProc() returns a pointer for a given instance. For C, the casting takes the form:
The PL_Run() function enables control of the Planimate run engine including starting, pausing, stopping and closing the Planimate instance.  


// ((tFunctionType*)PLL_GetProc(plhandle,eFunctionEnum))(function-parameters);
To get a newly loaded model running, call:


tFunctionType and eFunctionEnum are declared in planidll.hpp and must match.
((tPL_Run*)PLL_GetProc(pl,ePL_Run))(PLRUNCMD_Run);


For example, calling Run to get the model going:
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.


((tPL_Run*)PLL_GetProc(PL,ePL_Run))(PLRUNCMD_Run);
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.


As noted in planidll.hpp, data access functions need to be protected by calls to PLL_SuspendThread() / PLL_ResumeThread().
=== Terminating Planimate  ===


=== <br>pl5loader exports ===
The host application can use the loader to terminate and unload a Planimate instance:


These are functions exported in pl5loader.hpp for the pl5loaderd dll which encapsulates much of the complexity in managing Planimate in its own thread.
PLL_Term(pl);


=== planidll exports<br> ===
This gracefully stops the model, deletes it from memory, stops the thread and unloads the DLL from memory.


These are functions exported in planidll.hpp for a Planimate EXE&nbsp;converted to a DLL. The PL5loader takes care of the loading/unloading of the DLL but you directly call other functions here to read and write to model data structures.
== 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.<br>


=== Loading the DLL<br> ===
== Planimate DLL Exported Functions ==


Typically you will load the DLL&nbsp;using LoadLibrary()&nbsp;followed by GetProcAddress to bind to the member functions. This is demontrated in the LoadPL SDK&nbsp;demo application and will not be covered in detail here.<br>
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.  


Since an EXE&nbsp;converted to a DLL&nbsp;does not have a regular DllMain(), you need to invoke initialisation functions explicitly as follows. This example is for a simplistic single threaded use of the DLL.<br>
If you want to manage Planimate directly, you must follow the technique in pl5loader.cpp / planimate5.cpp, particularly the thread synchronisation.  


*LoadLibrary() and GetProcAddress()&nbsp;for the functions<br>
Refer to planidll.hpp for function typedefs, comments and basic notes. Material here assumes familiarity with the planidll.hpp header.
*PL_SetInstance() to set the instance handle<br>
*PL_Init() to initialise planimate. This can be passed command line arguments to load a model. It also needs a window handle for a window into which Planimate will create a child window for its output.<br>
*PL_GetSystemInfo() / PL_SetSystemInfo() to configure special options<br>
*PL_GetBroadcastName() to lookup a broadcast handle<br>
*PL_SendBroadcast() to send a broadcast (via its handle) to the model<br>
*PL_Term()&nbsp;when you are finished with the model to deallocate it<br>
*FreeLibrary() the DLL instance<br>


=== PL_GetSystemInfo / PL_SetSystemInfo<br> ===
=== PL_GetSystemInfo / PL_SetSystemInfo  ===


These enable access to a number of attributes.<br>  
These enable access to a number of attributes.<br>  


==== PLSI_CLOCK = 0<br> ====
==== PLSI_CLOCK  ====


This read only attribute returns the model run clock during a simulation run. This is the number of seconds since the model's Run Start Date.<br>  
This returns the model run clock during a simulation run. This is the number of seconds since the model's Run Start Date.<br>  


==== PLSI_ADVANCETOTIME = 1<br> ====
==== PLSI_ADVANCETOTIME  ====


This read only attribute returns the time (measured as for clock)&nbsp;that the model is advancing to. Valid if an advance to time is active.<br>  
This returns the time (measured as for clock)&nbsp;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.<br>  


==== PLSI_CURRENTPENDING = 2<br> ====
The engine must be in MD_PAUSED or MD_SIMULATE when using this otherwise it returns 0 and any setting is ignored.<br>
 
==== PLSI_CURRENTPENDING  ====


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


==== PLSI_ENGINESTATE = 3<br> ====
==== PLSI_ENGINESTATE  ====
 
This returns the state of the simulation engine<br>


==== PLSI_CURRENTFILEVERSION<br> ====
This returns the state of the simulation engine as defined in ePLMode enum:<br>
<pre>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
};
</pre>
==== PLSI_CURRENTFILEVERSION  ====


This returns the version of MDL&nbsp;files that the Planimate DLL&nbsp;will save<br>  
This returns the version of MDL&nbsp;files that the Planimate DLL&nbsp;will save<br>  


==== PLSI_OLDESTFILEVERSION = 5<br> ====
==== PLSI_OLDESTFILEVERSION  ====


This returns the oldest file ersion that the Planimate DLL&nbsp;can load and run.<br>  
This returns the oldest file ersion that the Planimate DLL&nbsp;can load and run.<br>  


==== PLSI_LOADEDFILEVERSION = 6<br> ====
==== PLSI_LOADEDFILEVERSION  ====


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


==== PLSI_DLLVERSION = 7<br>  ====
==== PLSI_DLLVERSION ====
 
This returns the version of the DLL&nbsp;API. As of December 2013 this is 4.<br>
 
==== PLSI_PAUSEAFTERADVANCE<br>  ====
 
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&nbsp;or MD_PAUSED&nbsp;state otherwise this returns 0 and setting is ignored.<br>  
 
=== 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().


This returns the version of the DLL&nbsp;API. As of January 2013 this is 2.<br>
The callback function must conform to Win32 CALLBACK linkage and be declared match tPL_BroadcastCallback.  


==== PLSI_BATCH = 8  ====
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.<br>


This is a read/write attribute. When 1 it indicates that Planimate is running in "batch"&nbsp;mode where no screen updates or animation delays are performed and no windows event dispatching occurs.
=== Advance To Time Callback<br>  ===
<pre>typedef PLRESULT CALLBACK tPL_PauseCallback(double the_time,
int    stop_reason, // SIMUL_Run
void * userdata);


This property can be set on and off however If the "/BATCH"&nbsp;command line option was used to initialise Planimate, Planimate will remain in batch mode.  
typedef PLDLLFN void tPL_RegisterPauseCallback(tPL_PauseCallback * fn,
void * userdata);
</pre>
It is often useful tfor an application which embeds Planimate to be notified when a model becomes pause, eg:&nbsp;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.<br>


A model in batch mode must not pop up dialogs or panels. The s.Batch system attribute can be used by the modeller to test this state.  
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.  


It will be desireable to hide the parent window used to initialise Planimate in batch mode as it will not be updated or redrawn.
=== Data Access  ===


==== PLSI_MESSAGELOCK = 9  ====
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.


As a standalone application, when Planimate is running a model, the main thread is in a loop in which Planimate processes events, performs display updates, performs timing delays (to keep animation at a consistent speed)&nbsp;and also dispatches windows events to keep the process responsive to paint messages and user interaction with the running model.  
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.  


When embedded as a DLL, it is possible to run the model in its own thread whilst the main thread continues independently. In such cases it is important that the Planimate thread does not dispatch windows messages as well as the main thread.
=== Data formatting Services  ===


Setting this value to 1 will prevent the simulation loop from dispatching windows events.  
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.  


<br>
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.


[[Category:DLL]]
[[Category:DLL]]
[[Category:Runtime Engine]]
[[Category:Runtime Engine]]
[[Category:Batch]]
[[Category:Batch]]

Latest revision as of 13:39, 2 April 2014

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.