您的位置:首页 > 运维架构 > Linux

Linux动态库(一)

2016-05-08 00:00 465 查看
摘要: 写了一个C++的运行时加载动态库的例子。

起因

博主在以Linux下做开发。在软件需求中,需要动态库带来的灵活性。

比如说博主主导的智能主机的开发。它需要支持很多种类的设备控制,如普通的开关灯、RGB灯、窗帘、百叶窗等等。我们将这些设备抽象成Device类,具体的设备就从这个类上派生出来。每支持一个新设备就派生一个Device的子类进行具体能力的实现。如下:



如果我们在代码里将其写死。那么,将来我们每新添一个设备,我们都得更换整个应用程序。升级程序有风险,而且在升级过程中主机是会停止服务的。

那么,能不能在不停止应用程序的情况下直接支持新的设备的呢?我想是能的,用动态库实现的插件思想来做。当主机发现一个种类的设备,比如:门锁。主机之前并不认识,它便会拿着设备的model_id去向服务器请求其“驱动”。而这个驱动,则是该设备从Device派生的类DoorlockDevice的动态库文件 doorlock_device.so。主机将该动态库加载进来,并实例化其DoorlockDevice对象。于是,主机就可以正常控制门锁了。

这样做除了灵活以外,还有另一个优点:通常用户的智能家居系统中只有两三种设备,如果将死在代码里或者静态链接。主机程序在加载的时候会将100多个设备代码加载到内存中运行。这对于内存匮乏的嵌入式系统而言,这是莫大的浪费。如果采用动态库加载方式,主机程序不需要在启动的时候将所有的设备驱动加载到内存运行,而是按需加载。如此可以大大地节省内存开销。

于是,我一定要好好研究一下动态库加载与插件模式。

正题

好了,回归正题。下面是一个非常简单的例子,看看是如何实现的:

文件组织结构:

.
├── main.cpp
├── Makefile
└── plugins
├── device.cpp
├── device.h
├── devices
│   ├── rgb_device.cpp
│   ├── rgb_device.h
│   ├── switch_device.cpp
│   └── switch_device.h
└── Makefile


plugins/device.{h,cpp} 定义了设备的接口:

plugins/device.h

#ifndef DEVICE_H_20160508
#define DEVICE_H_20160508

class Device {
public:
Device();
virtual ~Device();

public:
virtual void work() = 0;
};

typedef Device* create_func_t();

#endif //DEVICE_H_20160508


plugins/device.cpp

#include "device.h"
#include <iostream>
using namespace std;

Device::Device() {
cout << "Device construct" << endl;
}

Device::~Device() {
cout << "Device destruct" << endl;
}


定义的比较简单,就一个方法:work()

plugins/devices/ 目录下为Device的两个派生类:

RGBDevice类

#ifndef RGB_DEVICE_H_20160508
#define RGB_DEVICE_H_20160508

#include "../device.h"

class RGBDevice: public Device {
public:
virtual void work();
};

#endif //RGB_DEVICE_H_20160508


#include "rgb_device.h"

#include <iostream>
using namespace std;

extern "C" Device* create() {
return new RGBDevice;
}

void RGBDevice::work() {
cout << "RGB work" << endl;
}


SwitchDevice类

#ifndef SWITCH_DEVICE_H_20160508
#define SWITCH_DEVICE_H_20160508

#include "../device.h"

class SwitchDevice: public Device {
public:
virtual void work();
};

#endif //SWITCH_DEVICE_H_20160508


#include "switch_device.h"

#include <iostream>
using namespace std;

extern "C" Device* create() {
return new SwitchDevice;
}

void SwitchDevice::work() {
cout << "SwitchDevice work" << endl;
}


上面两个类的cpp里,我们都定义了一个create() 函数。它是用来创建一个对应的对象的。对于动态库里的对象,我们不能用new来创建。因为在编译main程序时,编译器根本就不知道如果new RGBDevice。

我们也不能提前在编译main程序时包含rgb_device.h(我们那时可能就不知道会有这个一个类)。综上,我们需要一个动态库内部指定的函数来创建。

plugins/Makefile的内容如下:

CFLAGS+=-shared -fPIC
CXXFLAGS+=$(CFLAGS)

all: librgb_device.so libswitch_device.so

librgb_device.so: devices/rgb_device.cpp device.cpp
$(CXX) $(CXXFLAGS) -o $@ $^

libswitch_device.so: devices/switch_device.cpp device.cpp
$(CXX) $(CXXFLAGS) -o $@ $^

clean:
-rm -f lib*.so


博主不太精于写Makefile,只能写成这样了。见笑了~

大致是这样的,每个派生类都要单独编译成一个libxxxx.so文件。如rgb_device,编译命令是:g++ -shared -o librgb_device.so -fPIC device/rgb_device.cpp device.cpp

为什么要加device.cpp ?因为RGBDevice继承于Device,而Device有自己的方法。如果编译时不加device.cpp,那么RGBDevice在链接时,不知道该怎么执行继承于Device的成员函数了。

好了,在plugins/下make一下,正常情况下,两个动态库就生成了。

下面看看main中如何加载动态库:

main.cpp

#include <dlfcn.h>
#include <iostream>
#include "device.h"

using namespace std;

int main() {
cout << "start" << endl;

void *handle = dlopen("./plugins/libswitch_device.so", RTLD_NOW);
if (handle == NULL) {
cerr << dlerror() << endl;
return 0;
}

create_func_t *func = (create_func_t*)dlsym(handle, "create");
if (func != NULL) {
Device *d = (*func)();
if (d != NULL) {
d->work();
delete d;
} else
cerr << "can't find [create] func" << endl;
}

dlclose(handle);
handle = NULL;

cout << "done" << endl;
return 0;
}


Makefile

all: test
make -C plugins all

test: main.cpp
$(CXX) -o $@ $^ -I./plugins/ -ldl

clean:
make -C plugins clean
-rm test


注意:在链接main时,一定要添加 -ldl,否则不能支持dlopen, dlsym, dlclose 函数。

make 之后,运行结果:

start
Device construct
SwitchDevice work
Device destruct
done




dlopen("./plugins/libswitch_device.so", RTLD_NOW)


换成:

dlopen("./plugins/librgb_device.so", RTLD_NOW)


后再运行,结果是:

start
Device construct
RGB work
Device destruct
done


可见动态的效果了。如果我们将动态库名写在配置文件里。那么,只要修改一下配置文件,那么运行的效果就不同了。

这只是实现上面我的设想的第一步。后面再研究动态库的插件。

下次见!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux c++ dynamic so