Home > Back-end >  How to override a struct in a derived FB
How to override a struct in a derived FB

Time:06-21

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.

  • Related