linux下动态链接库(.so)的显式调用和隐式调用

进入主题前,先看看两点预备知识。

一、显式调用和隐式调用的区别

我们知道,动态库相比静态库的区别是:静态库是编译时就加载到可执行文件中的,而动态库是在程序运行时完成加载的,所以使用动态库的程序的体积要比使用静态库程序的体积小,并且使用动态库的程序在运行时必须依赖所使用的动态库文件(.so文件),而使用静态库的程序一旦编译好,就不再需要依赖的静态库文件了(.a文件)。

动态库的调用又分为显示和隐式两种方式,区别如下:

1、 隐式调用需要调用者写的代码量少,调用起来和使用当前项目下的函数一样直接;而显式调用则要求程序员在调用时,指明要加载的动态库的名称和要调用的函数名称。

2、隐式调用由系统加载完成,对程序员透明;显式调用由程序员在需要使用时自己加载,不再使用时,自己负责卸载。

3、由于显式调用由程序员负责加载和卸载,好比动态申请内存空间,需要时就申请,不用时立即释放,因此显式调用对内存的使用更加合理, 大型项目中应使用显示调用。

4、当动态链接库中只提供函数接口,而该函数没有封装到类里面时,如果使用显式调用的方式,调用方甚至不许要包含动态链接库的头文件(需要调用的函数名是通过dlsym函数的参数指明的),而使用隐式调用时,则调用方不可避免要加上动态库中的头文件,g++编译时还需要要用参数-I指明包含的头文件的位置。需要注意的是,当动态链接库中的接口函数是作为成员函数封装在类里面时,即使使用显式调用的方式,调用方也必须包含动态库中的相应头文件(详见五、显示调用动态链接中的类成员函数)。

5、显式调用更加灵活,可以模拟多态效果(具体见后文)。

二、extern "C"的作用

C++程序(或库、目标文件)中,所有非静态(non-static)函数在二进制文件中都是以“符号(symbol)”形式出现的。这些符号都是唯一的字符串,从而把各个函数在程序、库、目标文件中区分开来。在C中,符号名正是函数名,两者完全一样。而C++允许重载(不同的函数有相同的名字但不同的参数,甚至const重载),并且有很多C所没有的特性──比如类、成员函数、异常说明──几乎不可能直接用函数名作符号名。为了解决这个问题,C++采用了所谓的name
mangling。它把函数名和一些信息(如参数数量和大小)杂糅在一起,改造成奇形怪状,只有编译器才懂的符号名。例如,被mangle后的foo可能看起来像[email protected]%6^,或者,符号名里头甚至不包括“foo”。

其中一个问题是,C++标准并没有定义名字必须如何被mangle,所以每个编译器都按自己的方式来进行name mangling。有些编译器甚至在不同版本间更换mangling算法(尤其是g++ 2.x和3.x)。前文说过,在显示调用动态库中的函数时,需要指明调用的函数名,即使您搞清楚了您的编译器到底怎么进行mangling的,从而知道调用的函数名被C++编译器转换为了什么形式,,但可能仅仅限于您手头的这个编译器而已,而无法在下一版编译器下工作。

extern "C"即可以解决这个问题。用 extern "C"声明的函数将使用函数名作符号名,就像C函数一样。因此,只有非成员函数才能被声明为extern "C",并且不能被重载。尽管限制多多,extern "C"函数还是非常有用,因为它们可以象C函数一样被dlopen动态加载。冠以extern
"C"限定符后,并不意味着函数中无法使用C++代码了,相反,它仍然是一个完全的C++函数,可以使用任何C++特性和各种类型的参数。所以extern "C" 只是告诉编译器编和链接的时候都用c的方式的函数名字,函数里的内容可以为c的代码也可以为c++的。

有了上面两个预备知识后,下面以实际例子来演示两种不同的动态库调用方式。例子的结构组织为如下:

so1.h和so1.cc是第一个动态库中的文件,会编译链接为libso1.so;so2.h和so2.cc是第一个动态库中的文件,会编译链接为libso2.so;test.cc是调用两个动态库的程序。

三、显式调用

so1.h:

extern "C" void fcn();

so1.cc:

#include <iostream>
#include "so1.h"

void fcn() {
	std::cout << "this is fcn in so1" << std::endl;
}

so1的makefile:

libso1.so:so1.o
    g++ so1.o -shared -o libso1.so
so1.o:so1.cc so1.h
    g++ -c so1.cc -fPIC -o so1.o

.PHONY:clean
clean:
    rm so1.o libso1.so

make之后,将生成的libso1.so拷贝到test.cc所在目录下。

so2.h:

extern "C" void fcn();

so2.cc:

#include <iostream>
#include "so2.h"

void fcn() {
	std::cout << "this is fcn in so2" << std::endl;
}

so2的makefile:

libso2.so:so2.o
	g++ so2.o -shared -o libso2.so
so2.o:so2.cc so2.h
	g++ -c so2.cc -fPIC -o so2.o

.PHONY:clean
clean:
	rm so2.o libso2.so

make之后,将生成的libso2.so拷贝到test.cc所在目录下。

test.cc:

#include <iostream>
#include <cstdlib>
#include <dlfcn.h>

using namespace std;

int main(int argc, char **argv) {
	if(argc != 2) {
		cout << "argument error!" << endl;
		exit(1);
	}

	//pointer to function
	typedef void (*pf_t)();

	char *err = NULL;
	//open the lib
	void *handle = dlopen(argv[1], RTLD_NOW);

	if(!handle) {
		cout << "load " << argv[1] << "failed! " << dlerror() << endl;
		exit(1);
	}

	//clear error info
	dlerror();

	pf_t pf  = (pf_t)dlsym(handle, "fcn");
	err = dlerror();
	if(err) {
		cout << "can't find symbol fcn! " << err << endl;
		exit(1);
	}

	//call function by pointer
	pf();

	dlclose(handle);

	return 0;
}

test的makefile:

test:test.o
	g++ test.o -lso1 -L. -lso2 -L. -ldl -Wl,-rpath=. -o test
test.o:test.cc
	g++ -c test.cc -o test.o

make之后,终端运行结果如下:

可以看到这里,通过输入不同的参数,调用了不同的共享库中的fcn函数,是一种多态的表现,许多软件的不同插件就是这样实现的。

需要注意的是,要使用显式调用的方式,必须加入头文件dlfcn.h,makefile中的链接命令中要加入参数-ldl,否则报错。

dlfcn.h中提供的API说明如下:

1)        dlopen

函数原型:void *dlopen(const char *libname,int flag);

功能描述:dlopen必须在dlerror,dlsym和dlclose之前调用,表示要将库装载到内存,准备使用。如果要装载的库依赖于其它库,必须首先装载依赖库。如果dlopen操作失败,返回NULL值;如果库已经被装载过,则dlopen会返回同样的句柄。

参数中的libname一般是库的全路径,这样dlopen会直接装载该文件;如果只是指定了库名称,在dlopen会按照下面的机制去搜寻:

a.根据环境变量LD_LIBRARY_PATH查找

b.根据/etc/ld.so.cache查找

c.查找依次在/lib和/usr/lib目录查找。

flag参数表示处理未定义函数的方式,可以使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暂时不去处理未定义函数,先把库装载到内存,等用到没定义的函数再说;RTLD_NOW表示马上检查是否存在未定义的函数,若存在,则dlopen以失败告终。

2)        dlerror

函数原型:char *dlerror(void);

功能描述:dlerror可以获得最近一次dlopen,dlsym或dlclose操作的错误信息,返回NULL表示无错误。dlerror在返回错误信息的同时,也会清除错误信息。

3)        dlsym

函数原型:void *dlsym(void *handle,const char *symbol);

功能描述:在dlopen之后,库被装载到内存。dlsym可以获得指定函数(symbol)在内存中的位置(指针)。如果找不到指定函数,则dlsym会返回NULL值。但判断函数是否存在最好的方法是使用dlerror函数,

4)        dlclose

函数原型:int dlclose(void *);

功能描述:将已经装载的库句柄减一,如果句柄减至零,则该库会被卸载。如果存在析构函数,则在dlclose之后,析构函数会被调用。

四、隐式调用

隐式调用不需要包含头文件dlfcn.h,只需要包含动态链接库中的头文件,使用动态库中的函数也不需要像显示调用那么复杂。

五、显式调用动态链接中的类成员函数

显示调用动态链接库的类成员函数,有单独的写法,但比较少用。推荐的写法是为每个要被外部调用的类成员函数设计一个普通的借口函数,在接口函数内部使用类的成员函数。当然这就需要将类设计为单例模式,因为不可能在每个接口函数中都构造一个类的对象。

时间: 2024-09-26 21:56:06

linux下动态链接库(.so)的显式调用和隐式调用的相关文章

linux 下动态链接库的制作与使用

linux 下动态链接库的制作与使用 linux 下动态链接库的制作与使用动态链接库*.so的编译与使用- -动态库*.so在linux下用c和c++编程时经常会碰到,最近在网站找了几篇文章介绍动态库的编译和链接,总算搞懂了这个之前一直不太了解得东东,这里做个笔记,也为其它正为动态库链接库而苦恼的兄弟们提供一点帮助.1.动态库的编译下面通过一个例子来介绍如何生成一个动态库.这里有一个头文件:so_test.h,三个.c文件:test_a.c.test_b.c.test_c.c,我们将这几个文件编

Linux下动态链接库的使用

Linux下的工具和程序模块很多都是通过动态链接库实现的. 在使用的时候分为两个过程: (1)编译时需要知道动态链接库文件的位置 (2)运行时需要打开动态链接库 对于(1)的情况,在makefile文件中指定,对于(2)中的情况需要在环境变量LD_LIBRARY_PATH中指定:当然如果程序是安装make install进来的,安装位置如果在系统默认的打开动态链接库路径内,则不再需要在LD_LIBRARY_PATH变量中说明,否则就需要添加到LD_LIBRARY_PATH中. Linux下动态链

linux下的shell命令的编写,以及java如何调用linux的shell命令(java如何获取linux上的网卡的ip信息)

程序员都很懒,你懂的! 最近在开发中,需要用到服务器的ip和mac信息.但是服务器是架设在linux系统上的,对于多网口,在获取ip时就产生了很大的问题.下面是在windows系统上,java获取本地ip的方法.贴代码: package com.herman.test; import java.net.InetAddress; /** * @see 获取计算机ip * @author Herman.Xiong * @date 2014年5月16日 09:35:38 */ public class

Dll的显式和隐式调用

隐式调用: 需要把生成动态链接库时生成的.lib文件加入到工程中去,在使用DLL时,只需说明一下就可以直接调用DLL中的函数,像调用程序内部的函数一样 显示调用: 需要使用LoadLibrary方式将自己生成的DLL文件先加载进来,在通过LoadLibrary返回的HINSTANCE,调用GetProcAddress获取想要的函数,然后就可以正常调用DLL中的函数 注意:在应用程序退出前,应该FreeLibrary来释放动态链接库 VC++中有两种方式来导出函数: 1. 通过_declspec

Linux 下动态链接库 *.so(生成、调用)

[摘要]动态库*.so在linux下用c和c++编程时经常会碰到,最近在网站找了几篇文章介绍动态库的编译和链接,这里做个笔记. 1.动态库的编译 下面通过一个例子来介绍如何生成一个动态库.这里有一个头文件:so_test.h,三个.c文件:test_a.c.test_b.c.test_c.c,我们将这几个文件编译成一个动态库:libtest.so. //so_test.h: #include "stdio.h" void test_a(); void test_b(); void te

Linux下动态链接库和静态链接库问题

第一部分:编译过程 先了解一下linux下C代码的编译过程,C代码的编译,一般分成四个阶段,包括:预编译,编译,汇编和链接,这四个阶段的分工是 预编译阶段: 编译阶段: 汇编阶段: 链接阶段: 第二部分:动态链接库和静态链接库的定义 第三部分:动态链接库和静态链接库的区别 第四部分:动态链接库和静态链接库的使用办法

linux下动态链接库.so文件 静态链接库.a文件创建及使用

转摘网址为:http://www.cnblogs.com/fengyv/archive/2012/08/10/2631313.html Linux下文件的类型是不依赖于其后缀名的,但一般来讲:    .o,是目标文件,相当于windows中的.obj文件    .so 为共享库,是shared object,用于动态连接的,和dll差不多    .a为静态库,是好多个.o合在一起,用于静态连接    .la为libtool自动生成的一些共享库,vi编辑查看,主要记录了一些配置信息.可以用如下命令

LINUX下动态链接库的使用-dlopen dlsym dlclose dlerror(转)

dlopen 基本定义 功能:打开一个动态链接库  包含头文件:  #include <dlfcn.h>  函数定义:  void * dlopen( const char * pathname, int mode );  函数描述:  在dlopen的()函数以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程.使用dlclose()来卸载打开的库.  mode:分为这两种  RTLD_LAZY 暂缓决定,等有需要时再解出符号  RTLD_NOW 立即决定,返回前解除所有未决定的符号

Linux下动态链接库 与gcc 选项

-L 编译时查找动态链接库的路径 -lxxx(小写)  e.g -lcudart   = link libcudart.so  , -I(大写) 头文件的路径 -rpath (-R), 编译时指定链接动态库的路径 可以用ldd <your_execute>看看是不有 ‘not found’在你链接的库后面, 解决方法是通过-Wl,rpath=<your_lib_dir>,使得execute记住链接库的位置 LD_LIBRARY_PATH  程序运行时查找动态库的路径 C_INCLU