C++动态库生成与调用

本文记录在Linux环境下C++动态库生成和调用的方法。

下面先稍微解释一下静态库(.a)和动态库(.so)的区别

  • 静态库:在编译时进行编译链接,库的内容与位置需要提前给定
  • 动态库:在运行时才进行加载链接,库的内容不需要提前给定,通过文件形式读入

由于我目前在设计可面向不同用户/应用的领域特定系统,因此如果能够将系统框架做成长期运行的服务,用户编写的函数以动态库形式加载进来,显然是一种不错的选择。(另外一种方式是通过进程间交互IPC,但是这会涉及到大量通信开销,而且程序也会较难编写。)

Example

下面给出一个简单的例子,实现动态加载继承类。

首先创建基类,并构建纯虚函数。

#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

Reference