本文记录在Linux环境下C++动态库生成和调用的方法。
下面先稍微解释一下静态库(.a
)和动态库(.so
)的区别
由于我目前在设计可面向不同用户/应用的领域特定系统,因此如果能够将系统框架做成长期运行的服务,用户编写的函数以动态库形式加载进来,显然是一种不错的选择。(另外一种方式是通过进程间交互IPC,但是这会涉及到大量通信开销,而且程序也会较难编写。)
下面给出一个简单的例子,实现动态加载继承类。
首先创建基类,并构建纯虚函数。
#ifndef BASE_H
#define BASE_H
class BaseClass {
public:
virtual void foo() = 0;
};
#endif // BASE_H
继承类中对虚函数进行实现,同时在该文件中需要以工厂模式(Factory Method)构建创建和销毁实例的接口,并导出符号(extern "C"
)。
这个文件可以单独和头文件编译为动态库derived.so
,之后便可以由主程序在运行时动态加载。
#include <iostream>
#include "base.h"
using namespace std;
class DerivedClass : public BaseClass {
public:
void foo() final {
cout << "Derived class!" << endl;
}
};
extern "C" BaseClass* create_object() {
return new DerivedClass;
}
extern "C" void destroy_object(BaseClass* object) {
delete object;
}
主程序中用dlopen
加载动态库,然后通过dlsym
读入符号到函数指针。之后便可以通过创建基类指针,加载继承类进来并调用类函数了。这里还添加了一些报错信息,方便定位问题。
// main.cpp
#include <dlfcn.h>
#include <iostream>
#include "base.h"
using namespace std;
#define CHECK() \
{ \
const char* dlsym_error = dlerror(); \
if (dlsym_error) { \
cerr << "Cannot load libray or symbol: " \
<< dlsym_error << '\n'; \
exit(1); \
} \
} \
int main() {
cout << "Start program..." << endl;
void* handle = dlopen("derived.so", RTLD_LAZY);
CHECK();
// "create" is a function pointer
// argv: ()
// return: BaseClass*
BaseClass* (*create)();
// "destroy" is a function pointer
// argv: BaseClass*
// return: void
void (*destroy)(BaseClass*);
// explicitly change void pointer to function pointer
create = (BaseClass* (*)())dlsym(handle, "create_object");
CHECK();
destroy = (void (*)(BaseClass*))dlsym(handle, "destroy_object");
CHECK();
BaseClass* derived_ptr = (BaseClass*)create();
derived_ptr->foo();
destroy(derived_ptr);
}
下面是Makefile文件,对于动态库需要添加-fPIC
和-shared
指令,而主函数则需添加-ldl
来允许加载动态库。
运行时注意将当前路径加入LD_LIBRARY_PATH
中,否则将搜索不到编译出来的动态库。
CC = g++
all: derived.so main
derived.so: derived.cpp
$(CC) -fPIC -shared $^ -o $@
main: main.cpp
$(CC) main.cpp -ldl -o main
run:
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. ./main
clean:
-rm derived.so main