Chinaunix首页 | 论坛 | 博客
  • 博客访问: 167995
  • 博文数量: 24
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 399
  • 用 户 组: 普通用户
  • 注册时间: 2013-03-04 15:36
文章分类

全部博文(24)

文章存档

2017年(2)

2015年(5)

2014年(9)

2013年(8)

我的朋友

分类: 嵌入式

2013-03-05 11:23:08

      现在越来越多的软件项目都提供插件机制,这样使得软件的扩展性大大增强,那么到底插件机制的实现是怎么样的呢?在这里只谈论C语言的实现,其实C语言实现插件的例子也很多,像mjpg-streamer就是将输入输出做成插件,dm500机顶盒的主程序enigma也使用了插件机制,我就是从enigma当中学习的。好了,这里给一个简单的例子来看看如何实现。

      我的设想是这样的:有一个主程序,有一个插件(.so文件),主程序里面提供一些最基本的功能模块,而在插件中使用这些功能模块实现某些功能。简单点就是主程序要调用.so文件中的函数,而.so要调用主程序中的某些基础函数。

      为了既能使用插件a.so, 又能使用插件b.so, 还能使用插件c.so,这里要显式调用动态链接库,即通过dlopen, dlsym, dlclose来使用.so文件,还是直接来看代码。

     #mkdir soplugin

     #cd soplugin

     #vim main.h

//main.h

#ifndef MAIN_H
#define MAIN_H

class A {
public:
    A(){};
    ~A(){};

    void p(const char *s);
};

#endif

      #vim main.cpp

#include
#include    // dlopen/dlsym/dlclose头文件

#include "main.h"

void A::p(const char *s)
{
    printf("A::p()\n");
}

void plugin_show(const char *s)  // 这个函数会被.so文件调用
{
    printf("%s\n", s);
}

int main(int argc, char *argv[])
{
    int (*PluginExec)(int argc, char *argv);
    void *plugin;

    printf("loading...\n");
    plugin = dlopen("./tplugin.so", RTLD_GLOBAL|RTLD_NOW); // 显式打开.so文件
    if (plugin == NULL) {
        printf("ptr: %p\n", plugin);
        perror("Can not load tplugin.so");
        return -1;
    }
    
    PluginExec = (int (*)(int, char*))dlsym(plugin, "plugin_exec"); // 得到入口函数指针
    if (PluginExec) {
        PluginExec(0, NULL);  // 调用入口函数
    }
    
    dlclose(plugin);

    return 0;
}

       #vim plugin.cpp

#include "main.h"

extern void plugin_show(const char *s);

extern "C"
int plugin_exec(int argc, char *argv[])  // 这里要用extern "C"声明,否则C++编译器会给函数名加上一些乱七八糟的东西,不信你可以试试,然后用objdump去查查看
{
    A a;

    a.p("in plugin_exec"); // 这个在主程序中实现

    plugin_show("in plugin.");  // 这个也在主程序中实现

    return 0;
}


     好了,代码就这些,很简单,但能说明问题就行了,再写个Makefile。

     # vim Makefile
all:
    g++ -shared -fPIC -DPIC -c plugin.cpp -I.
    ld -shared -o tplugin.so plugin.o
    g++ -Wl,-E -o mm main.cpp -ldl

     前面两行是将plugin.cpp做成一个.so文件,后面一行是编绎main.cpp,这里要特别注意参数-Wl,-E,这个参数意思是将-E参数传递给链接器ld,最终的目的是将main.cpp中的函数输出成全局符号,以方便.so文件调用,如果没有此参数,那么编绎也不会有问题,但在运行时dlopen总是会失败,原因是无法解决符号依赖问题。关于这个参数你可以用objdump对比一下加与不加的结果差别。

     好了,接下来编译然后运行。

     #make
g++ -shared -fPIC -DPIC -c plugin.cpp -I.
ld -shared -o tplugin.so plugin.o
g++ -Wl,-E -o mm main.cpp -ldl
      #./mm
loading...
A::p()
in plugin.


      这个例子只是简单的实现打印,但至少已经可以看到主程序和.so文件之间可以调用了,那我再实现a.so, b.so当然也不成问题了。可能有人会产生疑问,为什么不隐式调用呢?原因是:如果隐式调用就必须在编译阶段确定好.so文件,这样就谈不上可扩展插件了,它们之间就存在了编译上的依赖关系。而显式调用是在运行期间确定他们的依赖关系的。

      如果有兴趣可以参数mjpg-streamer去学习,但是mjpg-streamer中的插件没有调用主程序的函数,最好的学习例子还是enigma,他里面实现了大量的插件。以后嵌入式软件项目的扩展性要求会越来越高,插件扩展也大受欢迎,插件扩展的机制很多,需要我们去收集学习,这里讲的是最简单的一种。我为什么要学习,原因是我目前参与的一个项目主程序尽然大到30多MB,编译链接时间太长,要扩展功能就更痛苦了,一次一次的编译/链接,一次一次的等待,真是折磨。

附:

enigma是dm500机顶盒的主程序,enigma本身通过c++实现gui, gdi, dvb等一堆基础库,并实现了插件管理器,外围的功能基本全是c/c++插件实现。

enigma2是dm800的机顶盒主程序,enigma2就比enigma更高级了,他通过c++实现gui, gdi, dvb等一些基础库,其余的界面功能,机顶盒功能全部是用动态语言python实现,中间使用swig胶合在一起。也是不错的学习例子,但项目太大,学习不太容易。

阅读(5819) | 评论(0) | 转发(8) |
给主人留下些什么吧!~~