Open Core Interface - MLPI
MLPI-MotionLogicProgrammingInterface(mlpi4LabVIEW)  1.26.2
ContainerLib
Collaboration diagram for ContainerLib:

Modules

 Common function
 
 Auxiliary function
 
 Version and Permission
 
 Structs, Types, ...
 

Detailed Description

Use the ContainerLib when you need to access a larger set of data repetitively and with maximum update speed. For example, input data you want to read every machine cycle. Using the function mlpiContainerCreate, you first have to create a container by naming all the elements you want to read with the container. This also defines the memory layout of the container. After that, you can read/write your data using the mlpiContainerUpdate function. When the container is no longer needed, delete it using mlpiContainerDestroy.

Attention
The addresses of the variables of the container content might change on every download, reset origin or online change of the PLC application, so you have to stop and destroy each regarding container before you load or change the PLC application!
Please ensure you don't destroy a container during an access on it (update, read information e.g.)!

A container is a list of items which are described as so called 'tags'. Each 'tag' identifies a data element which can be read or written. For example, a symbolic variable from the PLC or data from the input area of the I/O mapping.

The first argument of a tag always defines the type of data to read or write. The following data sources are available:

LOGICLIB_MEMORY_AREA
  • argument 1: application name. (e.g. "Application")
  • argument 2: area. (Either "INPUT", "OUTPUT" or "MARKER")
  • argument 3: bit offset. (e.g. "0" to read from start offset. On bit access any offset value is valid, on byte access the offset must be a multiple of 8.)
  • argument 4: bit length. (Note: Single bit access (1) or byte access (8*n) supported. e.g. "1", "8", "16", "24", ... bit.)
LOGICLIB_SYMBOL
  • argument 1: symbol name. (e.g. "Application.PlcProg.boDummy")
IOLIB_FIELDBUS_IO
  • argument 1: master name. (e.g. "Onboard_I_O"). The name of the fieldbus master is the name of the regarding master node in your IndraWorks project. You can also retrieve the list of configured master names by using the function mlpiIoReadFieldbusMasterList.
  • argument 2: slave address. (e.g. "1");
  • argument 3: area. (Either "INPUT" or "OUTPUT")
  • argument 4: bit offset. (e.g. "0" to read from start offset. On bit access any offset value is valid, on byte access the offset must be a multiple of 8.)
  • argument 5: bit length. (Note: Single bit access (1) or byte access (8*n) supported. e.g. "1", "8", "16", "24", ... bit.)
ALIGNMENT_DUMMY
  • argument 1: byte length (Note: Only "1", "2", "4" or "8" byte supported.)
Example:
const WCHAR16 tagList[] =
{
L"LOGICLIB_MEMORY_AREA,Application,MARKER,0,32;" // first 4 bytes of the marker address space (%MX0.0 to %MX3.7)
L"LOGICLIB_MEMORY_AREA,Application,MARKER,32,128;" // 16 bytes of the marker address space starting at offset 4 (%MX4.0 to %MX19.7)
L"LOGICLIB_MEMORY_AREA,Application,INPUT,32,32;" // 4 bytes of the input address space starting at offset 4 (%IX4.0 to %IX7.7)
L"IOLIB_FIELBUS_IO,Onboard_I_O,0,INPUT,0,8;" // first 1 byte of the input area of the first slave on the OnboardIO.
L"ALIGNMENT_DUMMY,1;" // 1 byte alignment dummy value.
L"ALIGNMENT_DUMMY,2;" // 2 byte alignment dummy value.
L"LOGICLIB_SYMBOL,Application.USERVARGLOBAL.varUint;" // symbolic variable 'Application.USERVARGLOBAL.varUint'
L"ALIGNMENT_DUMMY,2;" // 2 byte alignment dummy value.
L"LOGICLIB_SYMBOL,Application.USERVARGLOBAL.varReal;" // symbolic variable 'Application.USERVARGLOBAL.varReal'
};

Let's say we have the following data in the 'UserVarGlobal' variable list of our application project.

{attribute 'symbol' := 'readwrite'}
{attribute 'linkalways'}
VAR_GLOBAL
varBoolean : BOOL := FALSE;
varString : STRING[31] := 'MLPI Test String';
varUdint : UDINT := 23;
varUdintArray : ARRAY[0..5] OF UDINT := [4, 8, 15, 16, 23, 42];
varReal : REAL := 42.0;
varRealArray : ARRAY[0..5] OF REAL := [4, 8, 15, 16, 23, 42];
END_VAR

Now let's imagine, we want to read all of the data and also the first 4 bytes of the input area (%IX0.0 to %IX3.7). The corresponding tagList for your C/C++ application would look like this:

const WCHAR16 tagList[] =
{
L"LOGICLIB_SYMBOL,Application.USERVARGLOBAL.varString;" // 0x00: the 31 byte string + 1 byte null termination.
L"LOGICLIB_SYMBOL,Application.USERVARGLOBAL.varUdint;" // 0x20: 4 byte unsigned integer.
L"LOGICLIB_SYMBOL,Application.USERVARGLOBAL.varUdintArray;" // 0x24: 6*4 byte array of unsigned integer.
L"LOGICLIB_SYMBOL,Application.USERVARGLOBAL.varBoolean;" // 0x3C: 1 byte boolean value.
L"ALIGNMENT_DUMMY,1;" // 0x3D: we add 1 bytes of dummy value for better alignment.
L"ALIGNMENT_DUMMY,2;" // 0x3E: we add 2 bytes of dummy value for better alignment. Next value is again aligned to 4 byte.
L"LOGICLIB_SYMBOL,Application.USERVARGLOBAL.varReal;" // 0x40: 4 byte float value.
L"LOGICLIB_SYMBOL,Application.USERVARGLOBAL.varRealArray;" // 0x44: 6*4 byte array of floats.
L"LOGICLIB_MEMORY_AREA,Application,INPUT,0,32;" // 0x9C: first 4 bytes of the input address space (%IX0.0 to %IX3.7)
};
Note
The layout of the different elements and your container is free. You don't need to have the same variable order as in the PLC. Use the alignment elements to align the elements to have better and faster access from within your C/C++ application.

Having defined our tagList, the next step is to create a container for reading by using the function mlpiContainerCreate. A container can either be used for reading or for writing. Not for both! You need to create two containers if you want to read and write the same set of data. In this example, we only create a read container using the following piece of code.

// this is our handle which is a reference to the container
memset(&hContainer, 0, sizeof(hContainer));
ULONG containerSize = 0;
MLPIRESULT result = mlpiContainerCreate(connection, tagList, MLPI_CONTAINER_ACCESS_READ, &hContainer, &containerSize);
if (MLPI_FAILED(result)) {
printf("\ncall of MLPI function failed with 0x%08x!", (unsigned)result);
return result;
}
printf("\nContainer created. Size in bytes: %d", containerSize);

After we have created the container, we can use the function mlpiContainerUpdate to read the current data of the container elements. This should be really fast now, as the MLPI has cached some information to provide a really fast mechanism here. Call the update function whenever you need to refresh the data.

// create some memory first
UCHAR *containerData = new UCHAR[containerSize];
memset(containerData, 0, containerSize);
// update the container!
result = mlpiContainerUpdate(connection, hContainer, containerData, containerSize);
if (MLPI_FAILED(result)) {
printf("\ncall of MLPI function failed with 0x%08x!", (unsigned)result);
delete [] containerData;
return result;
}

Now let's dump the content of the container data we've just read:

// dump the data
printf("\ndumping container data:\n");
utilHexdump(containerData, containerSize); // #include "util/mlpiGlobalHelper.h"

This results in the following output:

containerdump.png

We can see the requested data elements as described in the tagList.

For easier access in your application, you can build a struct with memory maps to your requested data. In the example above, a struct would look like this. You may want to place this in a header file.

// activate structure packing to match container alignment
#if defined(_MSC_VER)
#pragma pack(push,1)
#define PACKED
#elif defined(__GNUC__)
#define PACKED __attribute__ ((__packed__))
#endif
// define structure, which maps exactly to our requested container layout
// given by the taglist.
struct ContainerMap
{
CHAR varString[32];
ULONG varUdint;
ULONG varUdintArray[6];
BOOL8 varBoolean;
UCHAR varDummy1;
USHORT varDummy2;
FLOAT varReal;
FLOAT varRealArray[6];
UCHAR memoryAreaInput[4];
} PACKED;
// reset structure packing back to default
#if defined(_MSC_VER)
#pragma pack(pop)
#undef PACKED
#elif defined(__GNUC__)
#undef PACKED
#endif

Now you can cast your buffer with the container data to the structure and have easy access to the data:

// check if structure really matches our container size
if (sizeof(ContainerMap) != containerSize) {
printf("\ncontainerSize doesn't match structure! Incorrect structure of container layout?!");
return -1;
}
// cast memory to structure pointer
struct ContainerMap *containerMap = (struct ContainerMap*) containerData;
// print the data
printf("\nvarString: %s", containerMap->varString);
printf("\nvarUdint: %u", containerMap->varUdint);
printf("\nvarUdintArray: %u %u %u %u %u %u"
, containerMap->varUdintArray[0], containerMap->varUdintArray[1], containerMap->varUdintArray[2]
, containerMap->varUdintArray[3], containerMap->varUdintArray[4], containerMap->varUdintArray[5]);
printf("\nvarBoolean: %s", containerMap->varBoolean ? "TRUE" : "FALSE");
printf("\nvarReal: %f", containerMap->varReal);
printf("\nvarRealArray: %f %f %f %f %f %f"
, containerMap->varRealArray[0], containerMap->varRealArray[1], containerMap->varRealArray[2]
, containerMap->varRealArray[3], containerMap->varRealArray[4], containerMap->varRealArray[5]);

This also enables debugger support in your development environment.

containerwatch.png
Note
The ContainerLib functions trace their debug information mainly into the module MLPI_CONTAINER_LIB and in additional into the modules MLPI_ACCESSOR and MLPI_BASE_MODULES. For further information see also the detailed description of the library TraceLib and the notes about Using the Trace for debugging.