Planimate calling dotNET: Difference between revisions

From Planimate Knowledge Base
Jump to navigation Jump to search
(Created page with "__TOC__ == Note On Notation == Support for dotNet will be provided by a Planimate callable DLL. The existing PL call DLL/API will be used initially with only minor changes t...")
 
 
(14 intermediate revisions by the same user not shown)
Line 1: Line 1:
This describes Planimate's interface to the dotNET environment which gives access to a wide variety of modern UI elements, table editing and graphing libraries as well as libraries that easily interface to web APIs and databases.
__TOC__
__TOC__


== Note On Notation ==
== PLCLR ==


Support for dotNet will be provided by a Planimate callable DLL. The existing PL call DLL/API will be used initially with only minor changes to the API and the DLL calling notation within Planimate.
PLCLR.DLL is a Planimate callable DLL which provides the interface to dotNET's Common Language Runtime. You'll see its name in all the calls to the API. In future versions the calls will be integrated and appear as internal Planimate routines.


It has been proposed that DLL functions become as easy to use as regular routine operations (via enhancement of PL's routine editor and routine operation selection dialog). When this occurs the notation below will be simplified and further streamlining will be possible.
PLCLR expects you to point it to dotNET DLLs (called Assemblies). All DLLs involved must be in the same folder as the MDL during editing and the PBA EXE for a PBA. In addition, users will need Visual Studio 2013 runtime libraries and dotNET 4.5 installed. These can be obtained from Microsoft. When shipping a PBA, an installer should check for these and install as required.


== dotNET Interface DLL ==
=== PLEngine / PL.NET wrapper ===


A new DLL called PLCLR will give access to managed dotNET code. This DLL must be in Planimate's MDL or PBA directory.
Most of the functionality described here is about calling methods, passing parameters,  setting/getting properties and managing modal and modeless popups.
Unlike Planimate this DLL needs to be compiled with dynamic runtime libraries so it needs to remain separate.


Hence to use PLCLR, Visual Studio runtime distributables will need to be installed as well as dotNET. In the future compiling Planimate to use runtime distributables may become standard but it will impact PBA installation.
'''In addition''' to everything described on this page, PLCLR gives access to the many calls in the PL.NET wrapper which enable managed code to access data from Planimate, send broadcasts and more. This is the Planimate-As-A-DLL interface.


The DLL manages loading the dotNet Common Language Runtime (CLR) and the allocation of multiple objects during a Planimate session.
The WPFTest demo uses the PLEngineCore class to send broadcasts back to Planimate, very useful when a c# window is a modeless popup or embedded in a panel.


You start by initialising the DLL with a dotNet version ("" == latest installed).
PLEngineCore documentation can be found [https://interdynamics.github.io/PL.Net/class_planimate_1_1_engine_1_1_p_l_engine_core.html here].


  p.result = PLCLR:Init("4.5")
== Creating Instances Of Classes ==


You can explicitly unload the DLL and all loaded objects using PLCLR:Term().
You start by creating an instance of a class written in C# (Windows Forms) or c# / XAML (WPF).
The examples below assume you have access to the TestCLR model and WPFTest sample C# project.


== Class Instances ==
To create an instance you need the DLL name and the class name including namespace, eg:


The DLL can instantiate classes and will return a handle to them which is used to further work with them. The handle is an integer that can be stored in an attribute or table cell.
p.handle = PLCLR:New(“WPFTest.DLL”,”WPFTest.MainWindow”)


p.handle = PLCLR:New(dllname,classname,[...params])
p.handle is a number that you'll need to work with the object. You can also use a table cell to store it.
p.result = PLCLR:Delete(p.handle)


You can call a method of a class as follows:
For future maintenance, its good form to put the strings into portal text attributes, literal text is used here to make the example clearer.
p.result = PLCLR:Call(p.handle,"methodname",[...params])


You can call a getter/setter for a property directly:
You should test that p.handle is > 0 after a New. If its <= 0 an error occurred, in which case take away the negative (0 – p.handle) and you have the PLCLR error code.
p.value = PLCLR:get("propertyName");
 
p.result = PLCLR:set("propertyName",p.value);
When you've finished using an object you should dispose of it, though in some instances an object will persist the entire lifetime of the PBA so this is not necessary.
 
p.result = PLCLR:Del(p.handle)
 
PLCLR will recycle handle values for deleted objects.
 
== Calling Methods ==
 
Lets say you c# class has a method:
 
public void SayHello()
{
  MessageBox.Show(“Hello”);
}
 
You can call a method as follows:
 
p.result = PLCLR:Call(p.handle,”SayHello”)
 
Note the result of the call is always the result of PLCLR's operation, return values of methods are not used. Instead, you can pass parameters by value or reference, eg:
 
public void ComplexCalculation(int value_in,out double value_out)
{
  value_out = value_in + 0.5;
}
 
In Planimate you use
 
p.result = PLCLR:Call(p.handle,"ComplexCalculation(p.value1,p.value2)
 
Since value_out is declared as an "out", Planimate will set p.value p.value2 to the value calculated in the method.
 
== Properties ==
 
If in your c# class you have a public property with a getter/setter eg:
 
String username  {get; set;}
 
Then you can use PLCLR's Get and Set operations to access them. With p.name a portal attribute in text format, you can use:
 
p.name = PLCLR:get("username");
 
and
 
p.result = PLCLR:set("uername",p.name);
 
This also works for numbers, see the next section.


== Data Types ==
== Data Types ==


Translation between types will be handled similar to the SQLite DLL.
PLCLR has an extensible translation system to map between Planimate's simple data types and c#'s types. It matches the type of data passed in a parameter from Planimate with the type in a method's parameter or class property and chooses a translation as follows.
 
Conversions work for inputs to a method and outputs ("ref" or "out" parameters in C#) unless otherwise noted.
 


dotNET                 PL
{| class="wikitable"
int/uint/long etc    value (NearestInt)
|+ Parameter/Property Type Translation
float                value
! dotNET !! Planimate !! Notes
string               text
|-
single
| double || value (portal,routine,item/system attribute), table cell ||
array[]               t.table.Column()
|-
2D array              table (TBD)
| int,uint || value,cell || Nearest Integer used
|-
| bool || value,cell || Nearest Integer then 0=false, nonzero=true
|-
| string || Text attribute or cell || Not label but you can use string expression to convert any format to text
|-
| double[], int[], uint[], boolean[] || t.table.Column() || Conversions above applied per cell
|-
| double[], int[], uint[] || t.tablep[] || Just column one sent with less overhead than Column(). Conversions above applied per cell. Row number changes on return supported.
|-
| string[] || t.table.Column() || Column must be a text column, currently cannot return data.
|-
| double[][] || t.table[] || Entire table's raw numbers, as array of columns, ie: array of array of numbers
|-
| Dictionary<String,int> || l.labellist || Label list mapped so elements are index values, keyed by name
|-
| Dictionary<int,String> || l.labellist || Label list mapped so elements are strings, keyed by their index value
|-
| string[] || l.labellist || Label list names sent as a array of Strings. In order of index but the index numbers are lost. If returned, a new list is created indexed from one. The labels must not be repeated.
|
|}


All Planimate date times will be translated to seconds since 1/jan/1970 and writes to PL dates will be auto-translated to the active epoch in Planimate.
=== Dates And Times ===


Table data will be copied to arrays. A callback may enable access to data without copying it all (good for sparse access). I've not found out how to map unmanaged memory (PL table) so managed code can access it, yet.
Currently all Planimate date/times are sent as the number of seconds since the model run start date. A future Datetime interface may be added. The PLEngine module can assist with conversion.


== MODAL DIALOGS ==
== Modal Dialog ==


Planimate can call a method which displays a modal dialog, with the method returning when the dialog closes. For dialog z-ordering, the main Planimate window handle (owner) will be passable as a parameter eg s.MainWindowHandle.
A WPF Window can be made to "go modal" and show a modal dialog by calling ShowDialog(). Whilst "Call" could be used to call this directly, you cannot repeat use of a model dialog, you would need to create a New instance, set properties, Call ShowDialog (or a method you create which does so), read back any properties you want back from the dialog then Delete the instance.


A single function will make modal dialogs easier, avoiding the need for separate New/Handle/Delete calls, but advanced users (eg: mutlitple complex properties to set) might still do it the long way.
PLCLR provides a Dialog() operation which reduces this all to one line in Planimate.


p.result = PLCLR:ModalDialog("classname",params...)
p.result = PLCLR:Dialog(ddllname,classname,... up to 8 optional parameters...)
Like New, this loads an assembly and creates an instance of a class. It then calls a function that you must provide in the class called DoDialog(), passing the parameters which can be by value as well as "ref" or "out". It then disposes of the instance.


A C# handler function "ModalDialog" will be expected to exist in the class. The Planimate window handle will automatically be passed to the constructor of the class. Params (as present) will be passed to the handler function.
The DoDialog in the WPFTest sample (file MainWindow.xaml.cs) demonstrates using the parameters to set up fields in a simple dialog and to pass back information.


Any standard dotNET form should work with a very small subclass to handle the parent window (TBD do it all in the PLCLR DLL).
The Dialog() operation also calls another function in the class, if present, SetOwner(). This is used to ensure the model dialog knows Planimate is its owning window. You should include the sample SetOwner() you'll find in WPFTest in your class.


== Modeless Dialog ==
== Modeless Dialog ==


A new paint object "Window" will enable a class to be instantiated within a window in a panel in Planimate. The window will receive show/hide messages when the panel it is on is shown/hidden and size messages if the paint object is anchored. A paint object property will give access to its window handle and will typically be passed via "New()" so a managed class can set it as its parent window.
A WPF Window has a Show() method that causes it to be displayed without becoming modal. Planimate can call this to show the window and enable interaction with the controls on it. However its important that the popup know its owner and this can be done by calling SetOwner before Show().
 
p.result = PLCLR:SetOwner(p.handle)
p.result = PLCLR:Call(p.handle,"Show")
 
This requires the SetOwner() handler be present in the C# class, as described in modal dialog.
 
The window styles configured in the XAML and c# code control the title bar, resizing etc. The TestWPF sample demonstrates dynamically changing the style of the window to enable resizing and the titlebar.
 
=== Embedded Window ===
 
A modeless dialog can be embedded into a Planimate panel, becoming in effect a viewport on a dotNET class. Anchoring and resizing is fully supported, if the dotNET window includes a control that supports dynamic layout (eg: Grid) it will adapt to resizes.
 
In Planimate all that is required to put a popup into a Window paint object is to pass the paint object id to PLCLR's SetOwner:
 
p.result = PLCLR:SetOwner(p.handle,l._Paint_objects[MyWindow])
 
An additional function is required in the C# code, SetChild(). PLCLR calls this instead of SetOwner() when a paint object label is included. Refer to the WPFTest sample for its implementation. Please keep in mind this function may change with subsequent PLCLR releases.


== Exceptions ==
== Exceptions ==


PLCLR will have a flag that will enable it to catch all exceptions caused in managed code and display them in a dialog, passing the failure back to Planimate as an error so model development can continue instead of being closed by the managed code exception.
PLCLR wraps all calls in an exception handler which displays a popup with the exception details and returns an error code in p.result, the objective being to prevent faulting Planimate. C# code can catch exceptions instead if different behaviour is desired. Care will be needed particularly with modeless dialogs.
 
Node DEBUG builds of PLCLR are set up not to trap all exceptions, if you are working with a debug build you likely have the source and can change this in PLCLR.cpp


== Callbacks ==
== PLCLR Error Codes ==


Beyond passing parameters, PLCLR will provide an API to C# virtually identical to the Planimate-As-A-DLL API, enabling managed code to call back Planimate to get/set data.
The following error codes are returned in operations which return a "p.result". Note that "New" returns errors as negative numbers and succesful handles as positive numbers.


This will be particularly useful for modeless UIs.


TBD: merging PL Call DLL callbacks with PL as a DLL callbacks.
{| class="wikitable"
|+ PLCLR Ertor Codes
! Enum !! Value !! Meaning
|-
| PLDLLPROC_OK || 0 || Success. Note 0 never returned by New(), it returns > 0 for a handle or < 0 for an error code which is negative of the codes below.
|-
| PLDLLPROC_FILEERROR || 1 || Error opening DLL file (could be dotNET version not installed)
|-
| PLDLLPROC_CLASSERROR || 2 || Class not located in DLL - have you included the namespace?
|-
| PLDLLPROC_METHODERROR || 3 || Error locating named method in class
|-
| PLDLLPROC_PROPERTYERROR || 4 || Error locating named property in class
|-
| PLDLLPROC_HANDLEERROR || 5 || Error in passed instance handle (was it allocated with New() and subsequently Deleted()?
|-
| PLDLLPROC_OPIDERROR || 6 || PLDLL call failure - unrecognised operation (coding error in PLCLR)
|-
| PLDLLPROC_DATAERROR || 7 || Bad data/parameter (incompatible data type to what method expercts
|-
| PLDLLPROC_DLLERROR || 8 || Unexpected error in PLCLR
|-
| PLDLLPROC_EXCEPTION || 9 || Exception caught - see error popup for details
|}

Latest revision as of 14:33, 30 June 2016

This describes Planimate's interface to the dotNET environment which gives access to a wide variety of modern UI elements, table editing and graphing libraries as well as libraries that easily interface to web APIs and databases.

PLCLR

PLCLR.DLL is a Planimate callable DLL which provides the interface to dotNET's Common Language Runtime. You'll see its name in all the calls to the API. In future versions the calls will be integrated and appear as internal Planimate routines.

PLCLR expects you to point it to dotNET DLLs (called Assemblies). All DLLs involved must be in the same folder as the MDL during editing and the PBA EXE for a PBA. In addition, users will need Visual Studio 2013 runtime libraries and dotNET 4.5 installed. These can be obtained from Microsoft. When shipping a PBA, an installer should check for these and install as required.

PLEngine / PL.NET wrapper

Most of the functionality described here is about calling methods, passing parameters, setting/getting properties and managing modal and modeless popups.

In addition to everything described on this page, PLCLR gives access to the many calls in the PL.NET wrapper which enable managed code to access data from Planimate, send broadcasts and more. This is the Planimate-As-A-DLL interface.

The WPFTest demo uses the PLEngineCore class to send broadcasts back to Planimate, very useful when a c# window is a modeless popup or embedded in a panel.

PLEngineCore documentation can be found here.

Creating Instances Of Classes

You start by creating an instance of a class written in C# (Windows Forms) or c# / XAML (WPF). The examples below assume you have access to the TestCLR model and WPFTest sample C# project.

To create an instance you need the DLL name and the class name including namespace, eg:

p.handle = PLCLR:New(“WPFTest.DLL”,”WPFTest.MainWindow”)

p.handle is a number that you'll need to work with the object. You can also use a table cell to store it.

For future maintenance, its good form to put the strings into portal text attributes, literal text is used here to make the example clearer.

You should test that p.handle is > 0 after a New. If its <= 0 an error occurred, in which case take away the negative (0 – p.handle) and you have the PLCLR error code.

When you've finished using an object you should dispose of it, though in some instances an object will persist the entire lifetime of the PBA so this is not necessary.

p.result = PLCLR:Del(p.handle)

PLCLR will recycle handle values for deleted objects.

Calling Methods

Lets say you c# class has a method:

public void SayHello()
{
 MessageBox.Show(“Hello”);
}

You can call a method as follows:

p.result = PLCLR:Call(p.handle,”SayHello”)

Note the result of the call is always the result of PLCLR's operation, return values of methods are not used. Instead, you can pass parameters by value or reference, eg:

public void ComplexCalculation(int value_in,out double value_out)
{
 value_out = value_in + 0.5;
}

In Planimate you use

p.result = PLCLR:Call(p.handle,"ComplexCalculation(p.value1,p.value2)

Since value_out is declared as an "out", Planimate will set p.value p.value2 to the value calculated in the method.

Properties

If in your c# class you have a public property with a getter/setter eg:

String username  {get; set;}

Then you can use PLCLR's Get and Set operations to access them. With p.name a portal attribute in text format, you can use:

p.name = PLCLR:get("username");

and

p.result = PLCLR:set("uername",p.name);

This also works for numbers, see the next section.

Data Types

PLCLR has an extensible translation system to map between Planimate's simple data types and c#'s types. It matches the type of data passed in a parameter from Planimate with the type in a method's parameter or class property and chooses a translation as follows.

Conversions work for inputs to a method and outputs ("ref" or "out" parameters in C#) unless otherwise noted.


Parameter/Property Type Translation
dotNET Planimate Notes
double value (portal,routine,item/system attribute), table cell
int,uint value,cell Nearest Integer used
bool value,cell Nearest Integer then 0=false, nonzero=true
string Text attribute or cell Not label but you can use string expression to convert any format to text
double[], int[], uint[], boolean[] t.table.Column() Conversions above applied per cell
double[], int[], uint[] t.tablep[] Just column one sent with less overhead than Column(). Conversions above applied per cell. Row number changes on return supported.
string[] t.table.Column() Column must be a text column, currently cannot return data.
double[][] t.table[] Entire table's raw numbers, as array of columns, ie: array of array of numbers
Dictionary<String,int> l.labellist Label list mapped so elements are index values, keyed by name
Dictionary<int,String> l.labellist Label list mapped so elements are strings, keyed by their index value
string[] l.labellist Label list names sent as a array of Strings. In order of index but the index numbers are lost. If returned, a new list is created indexed from one. The labels must not be repeated.

Dates And Times

Currently all Planimate date/times are sent as the number of seconds since the model run start date. A future Datetime interface may be added. The PLEngine module can assist with conversion.

Modal Dialog

A WPF Window can be made to "go modal" and show a modal dialog by calling ShowDialog(). Whilst "Call" could be used to call this directly, you cannot repeat use of a model dialog, you would need to create a New instance, set properties, Call ShowDialog (or a method you create which does so), read back any properties you want back from the dialog then Delete the instance.

PLCLR provides a Dialog() operation which reduces this all to one line in Planimate.

p.result = PLCLR:Dialog(ddllname,classname,... up to 8 optional parameters...)

Like New, this loads an assembly and creates an instance of a class. It then calls a function that you must provide in the class called DoDialog(), passing the parameters which can be by value as well as "ref" or "out". It then disposes of the instance.

The DoDialog in the WPFTest sample (file MainWindow.xaml.cs) demonstrates using the parameters to set up fields in a simple dialog and to pass back information.

The Dialog() operation also calls another function in the class, if present, SetOwner(). This is used to ensure the model dialog knows Planimate is its owning window. You should include the sample SetOwner() you'll find in WPFTest in your class.

Modeless Dialog

A WPF Window has a Show() method that causes it to be displayed without becoming modal. Planimate can call this to show the window and enable interaction with the controls on it. However its important that the popup know its owner and this can be done by calling SetOwner before Show().

p.result = PLCLR:SetOwner(p.handle)
p.result = PLCLR:Call(p.handle,"Show")

This requires the SetOwner() handler be present in the C# class, as described in modal dialog.

The window styles configured in the XAML and c# code control the title bar, resizing etc. The TestWPF sample demonstrates dynamically changing the style of the window to enable resizing and the titlebar.

Embedded Window

A modeless dialog can be embedded into a Planimate panel, becoming in effect a viewport on a dotNET class. Anchoring and resizing is fully supported, if the dotNET window includes a control that supports dynamic layout (eg: Grid) it will adapt to resizes.

In Planimate all that is required to put a popup into a Window paint object is to pass the paint object id to PLCLR's SetOwner:

p.result = PLCLR:SetOwner(p.handle,l._Paint_objects[MyWindow])

An additional function is required in the C# code, SetChild(). PLCLR calls this instead of SetOwner() when a paint object label is included. Refer to the WPFTest sample for its implementation. Please keep in mind this function may change with subsequent PLCLR releases.

Exceptions

PLCLR wraps all calls in an exception handler which displays a popup with the exception details and returns an error code in p.result, the objective being to prevent faulting Planimate. C# code can catch exceptions instead if different behaviour is desired. Care will be needed particularly with modeless dialogs.

Node DEBUG builds of PLCLR are set up not to trap all exceptions, if you are working with a debug build you likely have the source and can change this in PLCLR.cpp

PLCLR Error Codes

The following error codes are returned in operations which return a "p.result". Note that "New" returns errors as negative numbers and succesful handles as positive numbers.


PLCLR Ertor Codes
Enum Value Meaning
PLDLLPROC_OK 0 Success. Note 0 never returned by New(), it returns > 0 for a handle or < 0 for an error code which is negative of the codes below.
PLDLLPROC_FILEERROR 1 Error opening DLL file (could be dotNET version not installed)
PLDLLPROC_CLASSERROR 2 Class not located in DLL - have you included the namespace?
PLDLLPROC_METHODERROR 3 Error locating named method in class
PLDLLPROC_PROPERTYERROR 4 Error locating named property in class
PLDLLPROC_HANDLEERROR 5 Error in passed instance handle (was it allocated with New() and subsequently Deleted()?
PLDLLPROC_OPIDERROR 6 PLDLL call failure - unrecognised operation (coding error in PLCLR)
PLDLLPROC_DATAERROR 7 Bad data/parameter (incompatible data type to what method expercts
PLDLLPROC_DLLERROR 8 Unexpected error in PLCLR
PLDLLPROC_EXCEPTION 9 Exception caught - see error popup for details