IEC61499 功能块调用C++ 动态程序库的实现(3)

前面几篇博文已经大致描述了IEC61499 功能块调用动态程序库的好处。本篇博文介绍我的实现方法以及遇到的问题。

实现的结果

 为了验证我的想法,实现了一个简单的功能块,称为FB_CALL_X2 .这个功能块有一个函数名称,两个参数,一个结果构成。

name是被调用函数的名称,由两部分组成。格式为:程序库名称.函数名称,例如在本例子中为OpenLibaray .add和OpenLibaray.sub两个函数。

在实验项目中,我编写了一个简单的动态程序库OpenLibaray.它内部有三个函数,分别是add,sub,multiply和divide。 实现加减乘除。它们都是两个参数构成,所以功能块称为FB_CALL_X2.

实现了FB_X2功能块之后,我编写了下面的IEC61499 应用。

从图上看,这个程序运行是正确的。

实现的细节

动态功能块调用的过程

  1. 当CALL 功能块收到INIT 事件时,从数据输入NAME 中提取出动态库的名称(OpenLibaray)和函数的名称(add 或者sub)。装入动态功能块库(./OpenLibaray.so)。显然,你需要将编译好的动态库放置在运行时forte.exe 同一个目录中。
  2. 当CALL 功能块收到REQ 事件时,读取数据输入param1和 param2 的值,调用动态库中的函数.
  3. 函数的返回值输出到RESULT 数据输出端。

遇到的问题

     遇到的问题是函数的输入输出的类型如何处理。在linux 调用动态程序库的机制中先通过dlsym 获取函数的调用入口地址和参数的地址,然后调用这个函数。比如

 typedef int (*PFUNC_PUSH)(int,int);

  PFUNC_ADD  add = (PFUNC_PUSH)dlsym(handle,"add"); 

result=add(param1,param2)

      这样问题就来了,带调用函数的时候我们需要确定函数参数以及返回值的类型,定义函数的模板。而功能块程序并不知道所有函数的参数以及返回值的类型。如果一定要这样的话,需要指定功能块param1,param2和result 的类型。这样就不够灵活了。

   于是,我们想到了ASN.1数据编码。事实上,我们在实现功能块之间数据传递的方式就是采用了ASN.1 编码方式。

   我们将动态库函数的参数也采取了ASN.1 编码方式进行。于是动态库函数的调用方式变成了如下方式:

typedef int (*PFUNC)(char *,char *,char *);

 PFUNC sub = (PFUNC)dlsym(handle,Name);

sub(param1,param2,result);

下面是Dynamic.c 程序

#include<stdio.h>
#include <iostream>
#include <list>
#include <string>
#include <dlfcn.h>
#include <cstring>
using namespace std;
typedef int (*PFUNC)(char *,char *,char *);
struct lib {
    string Name;
    void * handle;
};
std::list <lib> libList;
void *  openlibaray(string libName){
      cout<<"OpenLibaray:"<<libName<<endl;
     for (std::list<lib>::iterator it = libList.begin(); it != libList.end(); it++){
         if ((*it).Name==libName) {
             return (*it).handle;
         }
     }
   //Open Dynmaic Lib
   string Path="./"+libName+".so";
   cout<<"Open Libaray:"<<Path<<endl;
  void *handle = dlopen(Path.c_str(),RTLD_LAZY| RTLD_DEEPBIND |RTLD_GLOBAL);  //加载动态库
        if(handle==NULL)
        {
            printf("dlopen fail:%s\n",dlerror());
            return handle;
        } 
    libList.push_back({libName,handle});
    return handle;    
}
bool callLibaray(void * handle,const char * Name, char * a, char * b,char * r){
    cout<<"call Libaray:"<<Name<<endl;
  PFUNC sub = (PFUNC)dlsym(handle,Name);  
       if(sub==NULL)
    {
        printf("dlsym fail:%s\n",dlerror());
        return false;
    } 
 
    sub(a,b,r);
   return true;    
}

当然,动态库中函数中需要增加从ASN.1 编码中提取实际数据的处理程序。比如:

 

#include <cstring>
#include<stdio.h>
#include <iostream>
#include <string>
using namespace std;
extern "C" { 
void add(char * a,char * b,char * r){
      int a_val=(a[1]<<8)|a[2];
      int b_val=(b[1]<<8)|b[2];
      int r_val= a_val+b_val;
      r[1]=(r_val>>8)&0xff;
      r[2]=r_val &0xff;
}
int sub(char * a,char * b,char * r){
    int a_val=(a[1]<<8)|a[2];
    int b_val=(b[1]<<8)|b[2];
    int r_val= a_val-b_val;
    r[1]=(r_val>>8)&0xff;
    r[2]=r_val &0xff;
}
int multiply(char * a,char * b,char * r){
    int a_val=(a[1]<<8)|a[2];
    int b_val=(b[1]<<8)|b[2];
    int r_val= a_val*b_val;
    r[1]=(r_val>>8)&0xff;
    r[2]=r_val &0xff;
} 
int divide(char * a,char * b,char * r){
    int a_val=(a[1]<<8)|a[2];
    int b_val=(b[1]<<8)|b[2];
    int r_val= a_val/b_val;
    r[1]=(r_val>>8)&0xff;
    r[2]=r_val &0xff;
}
}

 结论 

实践证明,通过FB_CALL 功能块调用C++ 动态库的方式具有如下好处

  1. 将运行时和扩展功能块分离,简化了开发,部署和更新功能块。
  2. 可以通过提供动态库的方式,提供功能库。保护了第三方的知识产权
  3. 提供了动态更新的方式

进一步的研究方向

CALL 功能块中是否要添加一个error 输出?

在动态程序库中包含类和线程的方式。

将功能块程序封装到动态程序库的可能性。

猜你喜欢

转载自blog.csdn.net/yaojiawan/article/details/108897528