使用IEC61499 开发应用的人都会有一个感触-“功能块到用时方显少”。所以用户开发功能块的必需的。理论上,IEC61499 标准提供了定义功能块类型的方式。比如在4DIAC 中,可以定义功能块类型。它包括可以定义基本功能块,服务接口功能块和复合功能块。而功能块内部算法可以使用ST 语言来编写,或者Lua 语言来。实际上,设计完成了功能块类型后,要将该功能块Export 成为运行时的C++ 代码,和运行时一起从新编译之后,运行时才真正支持定义的功能块(在4DIAC Forte 中称为外部模块(external module))。这种方法是有缺陷的。首先它并不是动态功能块,需要和运行时一起编译。真正实现动态功能块的方式只能使用解释性语言来编写算法(比如lua ,或者java,JavaScript)而解释性语言的弊端是明显的,执行效率不高,不适合实时控制的要求。另外,如果涉及硬件底层的控制,解释性语言也是无能为力。在linux OS 下,还有另外一种方式实现动态远程部署,那就是容器技术。然而,docker 是一个庞然大物,在小型嵌入式系统中并不合适。
为了寻求更加合适的方式实现动态功能块。我们做了多种努力,最终选择了动态库的技术来实现IEC61499动态功能块。
我们设计了一个CALL 功能块,它的主要功能是调用一个动态库中的一个子程序。
例如:下面是一个带两个参数的库函数调用功能块
其中param1,和param2 是函数的输入值,result 的函数的结果
name 由库名称和函数名称组合而成,例如 libmath.add 表示是动态库libmath 中的add 函数。
函数的内部变量如何考虑
我们知道,函数无法访问外部变量,也不能保留内部变量。我们使用类来保留内部变量。
例子:
下面这个例子是动态库中定义了一个类。然后使用外部函数来调用这个类。
fifo.cpp
#include
#include "fifo.h"
FB::FB(){
}
FIFO::FIFO(){
inPos=0;
outPos=0;
counter=0;
}
void FIFO::pushData(int value){
printf("pushData\n");
buffer[inPos++]=value;
if (inPos==32)
inPos=0;
counter++;
}
int FIFO::popData() {
int value;
printf("popData\n");
value=buffer[outPos++];
if (outPos==32) outPos=0;
counter--;
return value;
}
extern "C" {
FB * init(){
printf("init\n");
return (new FIFO());
}
void pushData(FB * handle,int val){
FIFO * h;
h=(FIFO *)handle;
h->pushData(val);
}
int popData(FB * handle){
FIFO * h;
h=(FIFO *)handle;
return h->popData();
}
}
注意:extern "C" 很重要。否则运行./dlopen_test会报undefined symbol dlsym的错误,因为c++编译后的文件会把函数名改名(为了实现重载功能),用extern "C"声明后,就会使用c的方式进行编译,编译后的文件中仍然是定义的函数名,可以通过nm来看so中函数的名称。
冠以extern "C"限定符后,并不意味着函数中无法使用C++代码了,相反,它仍然是一个完全的C++函数,可以使用任何C++特性和各种类型的参数。
fifo.h
#include
class FB {
public:
FB();
};
class FIFO :public FB {
public:
FIFO();
void pushData(int value);
int popData();
private:
int buffer[32];
int counter;
int inPos,outPos;
};
FB * init();
void pushData(FB * handle,int val);
int popData(FB * handle);
编译
g++ fifo.cpp -fPIC -shared -o libfifo.so
主程序
#include
#include
#include "fifo.h"
typedef FB *(*PFUNC_INIT)();
typedef void (*PFUNC_PUSH)(FB* handle,int);
typedef int(*PFUNC_POP)(FB* handle); //给函数指针定义一个别名 PFUNC_SHOW = int(*)(int)
int main()
{
printf("Dynamic lib test\n");
void *handle = dlopen("./libfifo.so",RTLD_LAZY| RTLD_DEEPBIND |RTLD_GLOBAL); //加载动态库
if(handle==NULL)
{
printf("dlopen fail:%s\n",dlerror());
return -1;
}
PFUNC_INIT init = (PFUNC_INIT)dlsym(handle,"init");
if(init==NULL)
{
printf("dlsym fail:%s\n",dlerror());
return -1;
}
PFUNC_PUSH push = (PFUNC_PUSH)dlsym(handle,"pushData");
if(push==NULL)
{
printf("dlsym fail:%s\n",dlerror());
return -1;
}
PFUNC_POP pop = (PFUNC_POP)dlsym(handle,"popData");
if(pop==NULL)
{
printf("dlsym fail:%s\n",dlerror());
return -1;
}
FB * h=init();
push(h,1);
push(h,2);
int val=pop(h);
printf("result=%d\n",val);
val=pop(h);
printf("result=%d\n",val);
dlclose(handle);
return 0;
}
编译
g++ fifoTest.cpp -ldl -o fifoTest
运行结果
yao@DESKTOP-U0S0G7O:/mnt/f/yao2020/c++2020/dynamiclib$ ./fifoTest
Dynamic lib test
init
pushData
pushData
popData
result=1
popData
result=2
在这里我们看到,在main.中通过函数参数来传递 类的指针(FB * h)。而且FB 只是一个空的类,FIFO 使用FB 作为基类。在主程序中只是使用FB 指针类传递FIFO类的实例的地址。也就是说,如果动态库中的所有类,都使用FB 作为基类,那么就可以使用FB指针来作为动态库中类的指针。在C++中可以使用基类指针来访问派生类。
同样地,在动态库的函数中,也可以运行一个线程。
使用动态库实现动态功能块的开发是可型的方法。你说呢?