Linux下C++动态库的生成和使用

1. 导出函数的动态库

//DllTest.h

#ifndef _DLLTEST_H
#define _DLLTEST_H

extern "C" int add(int a,int b);
typedef int (*add_t)(int a,int b);

#endif


//DllTest.cc

#include "DllTest.h"

int add(int a,int b)
{
	return a+b;
}
上述动态库需要导出函数add。编译生成动态库:g++ -fPIC -shared -o libDllTest.so DllTest.cc

动态库加载方式分为静态和动态加载。

静态方式加载动态库示例:

//main.cc

#include <iostream>
using namespace std;

#include "DllTest.h"

int main(int argc,char** argv)
{
	cout << "------------static call-------------" << endl;
	cout << "add(5,3) = " << add(5,3) << endl;
}
编译可执行程序:g++ -o main main.cc  -lDllTest -L. /

动态方式加载动态库示例:

//main.cc

#include <iostream>
using namespace std;

#include <dlfcn.h> 
#include "DllTest.h"

int main(int argc,char** argv)
{
	cout << "------------dynamic call-------------" << endl;

	void *so_handle = dlopen("libDllTest.so", RTLD_LAZY); // 载入.so文件   
	if (!so_handle) {  
		cout << "Error: load so `failed." << endl;  
		return -1;  
	}  

	add_t fn = (add_t)dlsym(so_handle, "add"); // 载入函数   
	if (NULL == fn) {  
		cout << "get function address failed" <<  endl;  
		return -1;   
	}  

	cout << "add 57 + 3 = " << fn(57, 3) << endl; // 调用函数   

	dlclose(so_handle); // 关闭so句柄   
}
编译可执行程序:g++ -o main main.cc -ldl

extern “C”的作用不用多说了,但是需要说明的一点是:静态加载方式下可以不需要这个,但是动态加载方式下是必需的。

即使两种加载方式编译时都顺利通过,在运行时仍然可能出现找不到库的情况,此时静态方式会提示类似“./main: error while loading shared libraries: libDllTest.so: cannot open shared object file: No such file or directory”这样的错误,动态方式会导致so_handle为空。此时可通过两种方法解决这个问题:

  • export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./,假设动态库文件与可执行文件在同一目录下
  • 修改/etc/ld.so.conf文件,增加动态库所在目录,然后执行/sbin/ldconfig即可。

2. 导出类的动态库

Linux下动态库中导出类比较麻烦,可参考下例进行。说明一下,这个例子参考了http://blog.chinaunix.net/uid-26000296-id-3778641.html,代码略作了修改。

//Base.h

#ifndef BASE_CLASS_H
#define BASE_CLASS_H

class BaseClass {
protected:
	int member_var;
public:
	BaseClass(): member_var(0) {}

	virtual ~BaseClass() {}

	void set_member_var(int param) {
		member_var = param;
	}

	virtual int get_member_var() const = 0;
};

// the types of the class factories
typedef BaseClass* create_t();
typedef void destroy_t(BaseClass*);

#endif


//SubClass.cc

#include "Base.h"

class SubClass : public BaseClass {
public:
	virtual int get_member_var() const {
		return member_var;
	}
};

// the class factories
extern "C" BaseClass* create() {
	return new SubClass;
}

extern "C" void destroy(BaseClass* p) {
	delete p;
}
编译生成动态库:g++ -fPIC -shared -o libSubClass.so SubClass.cc

加载动态库示例代码:

//main.cc

#include <iostream>
using namespace std;

#include <dlfcn.h> 
#include "Base.h"

int main(int argc,char** argv)
{
	void *so_handle = dlopen("libSubClass.so", RTLD_LAZY); // 载入.so文件   
	if (!so_handle) {  
		cout << "Error: load so failed." << endl;  
		return -1;  
	}  

	create_t* create = (create_t*)dlsym(so_handle, "create"); // 载入函数   
	if (NULL == create) {  
		cout << "get function address failed" <<  endl;  
		return -1;   
	}  

	destroy_t* destroy = (destroy_t*) dlsym(so_handle, "destroy");
	if (NULL == destroy) {  
		cout << "get function address failed" <<  endl;  
		return -1;   
	} 

	BaseClass* pObj = create();
	pObj->set_member_var(10);
	cout << "pObj->get_member_var():" << pObj->get_member_var() << endl;
	destroy(pObj);

	dlclose(so_handle); // 关闭so句柄   
}
编译可执行程序:g++ -o main main.cc -ldl。很明显,上述代码采用了动态加载方式。其实导出类的动态库也可以采用静态加载方式,示例代码如下:

//Base.h

#ifndef BASE_CLASS_H
#define BASE_CLASS_H

class BaseClass {
protected:
	int member_var;
public:
	BaseClass(): member_var(0) {}

	virtual ~BaseClass() {}

	void set_member_var(int param) {
		member_var = param;
	}

	virtual int get_member_var() const = 0;
};

// the types of the class factories
typedef BaseClass* create_t();
typedef void destroy_t(BaseClass*);

#endif

//SubClass.h
#include "Base.h"

class SubClass : public BaseClass {
public:
	SubClass(){};
	virtual ~SubClass(){};
	virtual int get_member_var() const;
};

// the class factories
extern "C" BaseClass* create() {
	return new SubClass;
}

extern "C" void destroy(BaseClass* p) {
	delete p;
}

//SubClass.cc
#include "SubClass.h"

int SubClass::get_member_var() const {
	return member_var;
}
编译生成动态库:g++ -fPIC -shared -o libSubClass.so SubClass.cc

静态加载示例代码如下:

//main.cc

#include <iostream>
using namespace std;

#include <dlfcn.h> 
#include "SubClass.h"

int main(int argc,char** argv)
{
	BaseClass* pObj = create();
	pObj->set_member_var(10);
	cout << "pObj->get_member_var():" << pObj->get_member_var() << endl;
	destroy(pObj);
}
编译可执行程序:g++ mainClass.cc -o mainClass -L./ -lSubClass

个人觉得静态加载方式明显的比动态加载方式麻烦,而且通用性不好,所以不建议使用。

上述代码中需要注意一个细节问题:如果SubClass.h中不定义、只声明SubClass的析构函数,虽然生成动态库时不会出错,但是生成可执行文件时,会报下列类似错误:

In function `SubClass::SubClass()':
mainClass.cc:(.text._ZN8SubClassC2Ev[_ZN8SubClassC5Ev]+0x1f): undefined reference to `vtable for SubClass'

这个错误是一个比较常见的错误,一般原因都是由于在继承层次的类中,virtual函数没有定义、只有声明。比如上例中的Base.h中,如果BaseClass的析构函数如果缺失定义,也会出现类似错误。








文中是linuxC++动态库 实现接口提供类导出的一个例子 注意其中使用函数返回基类指针的用法,因为Linux的动态链接库不能像MFC中那样直接导出类 一、介绍 如何使用dlopen API动态地加载C++函数和类,是Unix C++程序员经常碰到的问题。 事实上,情况偶尔有些复杂,需要一些解释。这正是写这篇mini HOWTO的缘由。 理解这篇文档的前提是对C/C++语言中dlopen API有基本的了解。 这篇HOWTO的维护链接是: http://www.isotton.com/howtos/C++-dlopen-mini-HOWTO/ 二、问题所在 有时你想在运行时加载一个库(并使用其中的函数),这在你为你的程序写一些插件或模块架构的时候经常发生。 在C语言中,加载一个库轻而易举(调用dlopen、dlsym和dlclose就够了),但对C++来说,情况稍微复杂。 动态加载一个C++库的困难一部分是因为C++的name mangling (译者注:也有人把它翻译为“名字毁坏”,我觉得还是不翻译好), 另一部分是因为dlopen API是用C语言实现的,因而没有提供一个合适的方式来装载类。 在解释如何装载C++库之前,最好再详细了解一下name mangling。 我推荐您了解一下它,即使您对它不感兴趣。因为这有助于您理解问题是如何产生的,如何才能解决它们。 1. Name Mangling 在每个C++程序(或库、目标文件)中, 所有非静态(non-static)函数在二进制文件中都是以“符号(symbol)”形式出现的。 这些符号都是唯一的字符串,从而把各个函数在程序、库、目标文件中区分开来。 在C中,符号名正是函数名:strcpy函数的符号名就是“strcpy”,等等。 这可能是因为两个非静态函数的名字一定各不相同的缘故。 而C++允许重载(不同的函数有相同的名字但不同的参数), 并且有很多C所没有的特性──比如类、成员函数、异常说明──几乎不可能直接用函数名作符号名。 为了解决这个问题,C++采用了所谓的name mangling。它把函数名和一些信息(如参数数量和大小)杂糅在一起, 改造成奇形怪状,只有编译器才懂的符号名。 例如,被mangle后的foo可能看起来像foo@4%6^,或者,符号名里头甚至不包括“foo”。 其中一个问题是,C++标准(目前是[ISO14882])并没有定义名字必须如何被mangle, 所以每个编译器都按自己的方式来进行name mangling。 有些编译器甚至在不同版本间更换mangling算法(尤其是g++ 2.x和3.x)。 即使您搞清楚了您的编译器到底怎么进行mangling的,从而可以用dlsym调用函数了, 但可能仅仅限于您手头的这个编译器而已,而无法在下一版编译器下工作。 三、类 使用dlopen API的另一个问题是,它只支持加载函数。 但在C++中,您可能要用到库中的一个类,而这需要创建该类的一个实例,这不容易做到。 四、解决方案 1. extern "C" C++有个特定的关键字用来声明采用C binding的函数: extern "C" 。 用 extern "C"声明的函数将使用函数名作符号名,就像C函数一样。 因此,只有非成员函数才能被声明为extern "C",并且不能被重载。 尽管限制多多,extern "C"函数还是非常有用,因为它们可以象C函数一样被dlopen动态加载。 冠以extern "C"限定符后,并不意味着函数中无法使用C++代码了, 相反,它仍然是一个完全的C++函数,可以使用任何C++特性和各种类型的参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值