I'm stuck while designing a basic FB for our machine using OOP principles.
A machine uses different modules like this:
module1 : BaseModuleFB;
module2 : BaseModuleFB;
A module (some part of the machine) needs data to function (like positions, delays, etc.) This data will be stored in a recipe so grouping the data in a struct makes sence.
FUNCTION_BLOCK BaseModuleFB
VAR
Data : BaseModuleDataStruct;
END_VAR
Now I have a different machine with some extra requirement. And also some extra data is needed. So I extend the module:
FUNCTION_BLOCK ModuleWithExtraFunctionFB EXTENDS BaseModuleFB
VAR
Data : ModuleWithExtraFunctionDataStruct;
END_VAR
The code above will not compile because the variable Data is already in use in the base class.
The Data structs look like this by the way:
TYPE BaseModuleDataStruct:
STRUCT
position1:INT;
END_STRUCT
END_TYPE
TYPE ModuleWithExtraFunctionDataStruct EXTENDS BaseModuleDataStruct:
STRUCT
position2:INT;
END_STRUCT
END_TYPE
Another option I thought of was creating a property called Data
.
This property can be overritten by the derived class.
But this approach also failed because you cannot change the type of the overridden property.
Maybe someone has some nice ideas about this? Thanks.
CodePudding user response:
In general, you can think of classes (function blocks) as callable structures with methods (functions). So, unless you need to expose the Data structure (have a method that returns a structure), then why not just move the contents of the struct into the function block:
FUNCTION_BLOCK BaseModuleFB
VAR
// instead of "Data : BaseModuleDataStruct;"
position1: INT;
END_VAR
FUNCTION_BLOCK ModuleWithExtraFunctionFB EXTENDS BaseModuleFB
VAR
// instead of "Data : ModuleWithExtraFunctionDataStruct;"
position2: INT;
// you can access both "position1" and position2" here
END_VAR
If you absolutely must access a structure inside your function block, there is no simple way to do it in a completely enclosed way, however, you could pass the responsibility of defining the struct to the user of the function block either by using interfaces or pointers:
Fir the option with interfaces, we will use the __QUERYINTERFACE operator.
// An interface with a Method/Property that gives the Data structure
// It needs to extend "__System.IQueryInterface" for us to be bale to use the "__QUERYINTERFACE" operator on it later
INTERFACE IDataProvider EXTENDS __System.IQueryInterface
METHOD GetModuleAData : BaseModuleDataStruct
// An interface extension that adds a Method/Property that gives the extended Data structure
INTERFACE IDataProviderB EXTENDS IDataProvider
METHOD GetModuleBData : ModuleWithExtraFunctionDataStruct
FUNCTION_BLOCK BaseModuleFB
VAR
_dataProvider: IDataProvider;
END_VAR
METHOD FB_Init: BOOL
VAR_INPUT
bInitRetains: BOOL; // Internal built-in hidden argument. Don't touch!
bInCopyCode: BOOL; // Internal built-in hidden argument. Don't touch!
dataProvider: IDataProvider;
END_VAR
// Inside the FB_Init method: "THIS^._dataProvider := dataProvider;"
FUNCTION_BLOCK ModuleWithExtraFunctionFB EXTENDS BaseModuleFB
VAR
_dataProviderB: IDataProviderB;
END_VAR
METHOD FB_Init: BOOL
VAR_INPUT
bInitRetains: BOOL; // Internal built-in hidden argument. Don't touch!
bInCopyCode: BOOL; // Internal built-in hidden argument. Don't touch!
dataProvider: IDataProvider;
// We will use the __QUERYINTERFACE operator to "cast" IDataProvider to IDataProviderB
END_VAR
VAR
success: BOOL;
END_VAR
// Inside the FB_Init method:
// success := __QUERYINTERFACE(_dataProvider, _dataProviderB);
Then in the base methods you should be able to use _dataProvider.GetModuleAData
and in the extended methods _dataProviderB.GetModuleBData
.
As for pointers:
FUNCTION_BLOCK BaseModuleFB
VAR
pdata: POINTER TO BaseModuleDataStruct;
END_VAR
METHOD SetData: BOOL
VAR_IN_OUT
data: BaseModuleDataStruct;
END_VAR
// Inside the SetDatamethod: "THIS^.pdata:= ADR(data);"
FUNCTION_BLOCK ModuleWithExtraFunctionFB EXTENDS BaseModuleFB
VAR
pdata2: POINTER TO ModuleWithExtraFunctionDataStruct;
END_VAR
METHOD SetData: BOOL
VAR_IN_OUT
data: BaseModuleDataStruct;
END_VAR
// Inside the SetDatamethod: "THIS^.pdata:= ADR(data);"
// "THIS^.pdata2:= ADR(data);"
// We are assuming that the user passed data of type "ModuleWithExtraFunctionDataStruct" here!!!
// Care needs to be taken here, otherwise we may get access violations!
In your Program on the first run call the SetData
methods for both Function Blocks. Make sure you are passing the correct data struct to avoid any access violations!
Then in the base methods you should be able to use pdata^.position1
and in the extended methods pdata2^position2
.
I uploaded an example PLCOpenXML file on GDrive, you can try importing it and play around with it.