使用 CODESYS v3 中的方法进行回调

问题描述 投票:0回答:2

** TLDR;是否可以创建指向方法的指针,存储它们然后调用它们? **

这个问题与这个问题相关:如何在codesys v3中创建指向函数的指针,但我的问题是关于方法的,而且,由于引用的问题已经有5年历史了,其他解决方案可能就在眼前。

下面的代码不起作用,但它给人留下了我想要做什么以及我期望它如何工作的印象。

假设我有一个 FB/类:

Alarm
,其中我想将回调注入到
Alarm.ActivateCallback
中,该回调将在警报激活时使用
Alarm.Activate()
执行。 PLC_PRG 有一个
Alarm
的实例,并希望注入一个方法
PRG.OnAlarm1Activated()
作为回调,目前只需将 PRG 的布尔值设置为 TRUE:

FB闹钟

FUNCTION_BLOCK Alarm
VAR_INPUT
    ActivateCallback : POINTER TO CallbackDelegate;
END_VAR

Alarm.Activate()

IF (ActivateCallback <> 0) THEN
    ActivateCallback^();
END_IF;

PLC_PRG:

PROGRAM PLC_PRG
VAR
    _init : BOOL := TRUE;   
    _activate : BOOL := FALSE;
    _alarm1 : Alarm;
    _alarm1Activated : BOOL;
END_VAR
IF (_init) THEN
    _alarm1.ActivateCallback :=ADR(OnAlarm1Activated);
    _init := FALSE;
END_IF


IF (_activate) THEN
    _alarm1.Activate();
END_IF

PLC_PRG.OnAlarm1Activated():

Alarm1Activated := TRUE;

对于上面的代码,我收到以下错误:

  • ‘CallbackDelegate’是FUNCTION类型,无法实例化
  • “OnAlarm1Activated”上无法执行“Adr”操作

可以修改上面的代码来启用所需的功能吗?

作为替代方案,我可以使用接口并注入它们。但我也想知道上面是否也可能。

旁注:CODESYS 帮助说明了以下内容,但也许有解决方法:

... CODESYS 无法在编程系统的应用程序中调用函数指针! ...

编辑以详细说明界面替代方案(请原谅我的 C#/CODESYS 混合半代码)

public enum AlarmEvent
{
    Activated,
    Cleared,
}

public FB Alarm 
{
    public AlarmHandler : REFERENCE TO IAlarmEventHandler;

    public Activate() {
        AlarmHandler.HandleAlarmEvent(this, AlarmEvent.Activate);
    }
} 

public interface IAlarmEventHandler
{
    public void HandleAlarmEvent(ref Alarm alarm, AlarmEvent event);
}

public FB PLC_PRG : IAlarmEventHandler 
{ 
    Alarm1 : Alarm;

    public FB_Init() {
        Alarm1.AlarmHandler := this;
    }

    public void HandleAlarmEvent(ref Alarm alarm, AlarmEvent event) {
        if (alarm == Alarm1 && event == AlarmEvent.Activated)
            _alarm1Activated = true;
    }
}
pointers callback twincat codesys
2个回答
3
投票

如果您想注入一个 FunctionBlock(本质上是一个类),您可以使用 Interface

CODESYS 始终将使用接口类型声明的变量视为引用。

如果您想使用指向普通函数的指针

CODESYS 不提供任何从开发系统中的应用程序内调用函数指针的方法。

因此从对象内部执行回调函数是不可能的。

您必须将函数调用包装在 FunctionBlock 内并将其作为接口传递:

INTERFACE ICallback
    METHOD Invoke;
END_INTERFACE;

FUNCTION_BLOCK CallbackExecutor1 IMPLEMENTS ICallback
    METHOD Invoke
        MyCallbackFunction(); // the function execution is hardcoded
    END_METHOD;
END_FUNCTION_BLOCK;

FUNCTION_BLOCK CallbackExecutor2 IMPLEMENTS ICallback
    METHOD Invoke
        MyProgram.MyCallbackMethod(); // the method execution is hardcoded
    END_METHOD;
END_FUNCTION_BLOCK;

FUNCTION_BLOCK CallbackExecutor3 IMPLEMENTS ICallback
VAR
    _fbRef: REFERENCE TO MyFB;
END_VAR

    METHOD FB_Init: BOOL // constructor
    VAR_INPUT
        bInitRetains: BOOL;
        bInCopyCode: BOOL;
        
        fb_reference: REFERENCE TO MyFB; // to call a method of a FunctionBlock, you need a reference/pointer to an instance of the said FunctionBlock
    END_VAR
    
        _fbRef REF= fb_reference;
    END_METHOD;

    METHOD Invoke
        _fbRef.MyCallbackMethod(); // the method execution is hardcoded
    END_METHOD;
END_FUNCTION_BLOCK;

FUNCTION_BLOCK FB
VAR
    _callback1: ICallback;
    _callback2: ICallback;
    _callback3: ICallback;
END_VAR

    _callback1.Invoke(); // invoke/activate/call where needed
    
    IF TRUE THEN
        _callback2.Invoke(); // invoke/activate/call where needed
    END_IF;
    
    IF FALSE THEN
        _callback3.Invoke(); // invoke/activate/call where needed
    END_IF;

    METHOD FB_Init: BOOL // constructor
    VAR_INPUT
        bInitRetains: BOOL;
        bInCopyCode: BOOL;
        
        callback1: ICallback; // pass callbacks in the constructor
        callback2: ICallback; // pass callbacks in the constructor
        callback3: ICallback; // pass callbacks in the constructor
    END_VAR
    
        _callback1 := callback1;
        _callback2 := callback2;
        _callback3 := callback2;
    END_METHOD;
END_FUNCTION_BLOCK;

PROGRAM Main_07
VAR
    _myfb: MyFB;
    callback1: CallbackExecutor1;
    callback2: CallbackExecutor2;
    callback3: CallbackExecutor3(fb_reference := _myfb);
    _fb: FB(callback1 := callback1, callback2 := callback2, callback3 := callback3);
END_VAR
END_PROGRAM

注意:仅仅为了传递回调而创建“一次性”FunctionBlocks 并不是免费的,如果您在资源有限的环境中工作,我建议避免使用此方法。此外,将参数传递给这些回调甚至更加困难,即使不是不可能(至少以通用方式)。

或者为每个回调返回一个布尔值并在调用方检查它们。对于第二个选项,我们可以查看 STANDARD 库中的 TON FunctionBlock:

VAR
    timer: TON;
END_VAR

timer(IN := TRUE; PT := T#5S); // 5 second timer

IF (timer.Q) THEN
    // executed when the timer counts 5 seconds
END_IF

就你而言,也许是这样的:

VAR
    obj: MyPOU;
END_VAR

obj( (* inputs *) );

IF (obj.callback1) THEN
    Func1();
END_IF

IF (obj.callback2) THEN
    Func2();
END_IF

1
投票

您要使用的是 CODESYS 3 中的接口,它们基本上是方法指针。您只需确保添加对要调用的接口的引用即可:

  1. 当您创建 FB_Alarm 实例时(通过 FB_InitOR
  2. 作为功能块的输入。
© www.soinside.com 2019 - 2024. All rights reserved.