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可能看起来像foo@4%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

#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

#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

#include

#include

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,只需要包含动态链接库中的头文件,使用动态库中的函数也不需要像显示调用那么复杂。

 

 

 

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

 

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

---------------------

作者:hujingLiu

来源:CSDN

原文:https://blog.csdn.net/lc_910927/article/details/42393121

版权声明:本文为博主原创文章,转载请附上博文链接!

 

 

 

 

 

 

 

 

1、介绍

动态库是程序设计常用的技术,采用动态库可以有效的减少程序大小,节省空间,提高效率,增加程序的可扩展性,便于模块化管理。在Windows和Linux操作系统中都有动态库的概念。Windows将其称为动态链接库(Dynamic Link Library,DLL),其文件扩展名为.dll,Linux称其为共享库技术(Shared Library),相应的共享库文件扩展名为.so。

故名思义,动态库在程序运行的时候被动态链接。但是在具体使用动态库的时候却有两种不同的方式:隐式链接和显式链接。隐式链接在编译/链接阶段完成,由编译系统根据动态库的头文件和库文件进行编译和链接,从而确定待调用的函数原形和地址。显式链接则是利用API函数实现加载和卸载共享库,获取带调用函数地址,获取错误信息等功能。

2、隐式链接举例

(1)动态库文件代码:dl_func.c

#include

extern char name[];

int add(int a, int b)

{

        printf("calling add\n");

        printf("Hello, %s!\n", name);

        return a + b;

}

该文件中的add()函数计算两个整数之和,并且打印外部变量的值,该外部变量由调用共享库的事例程序定义。

(2)客户端事例代码:dl_demo1.c

#include

#include

int add(int a, int b);

char name[100];

int main(int argc, char *argv[]) {

        int a = 10, b = 20;

        int c = 0;

        strcpy(name, "NHN XDBMS");

        c = add(a, b);

        printf("%d + %d = %d\n", a, b, c);

        return 0;

}

该事例程序调用共享库的中的add()函数计算两数之后并打印,同时在事例程序中,给变量name赋值,以便在add()函数中打印。

(3)编译与运行

编译共享库:

gcc -o libdl_func.so -fPIC -rdynamic -shared dl_func.c

选项-fPIC指示编译器将代码编译成位置独立的代码,一般需要以程序文件共享其函数或变量给其他程序文件的代码都应该以此选项进行编译,选项-rdynamic指示编译器所编译/链接的为共享库程序文件。由于要使用外部变量,因此需要-shared选项,否则编译器会抛出错误信息:undefined reference to `name',表示不能找到name变量。

编译事例程序:

gcc -o dl_demo1 -L./ -ldl_func dl_demo1.c

选项-L./ 指示编译器在当前目录下寻找共享库文件,-ldl_func指示需要的共享库文件名为libdl_func.so。

运行:

./dl_demo1

输出:

calling add

Hello, NHN XDBMS!

10 + 20 = 30

3、显式链接API函数

显式链接主要涉及到4个API函数( dlopen , dlerror , dlsym 和 dlclose ),这些函数原形定义包含在dlfcn.h头文件中。

(1)void *dlopen(const char *file, int mode);

该函数用来按照指定模式打开指定的共享库,将其影射到内存中,并且返回句柄。

第一个参数:指定共享库的名称,将会在下面位置查找指定的共享库。

-环境变量LD_LIBRARY_PATH列出的用分号间隔的所有目录。

-文件/etc/ld.so.cache中找到的库的列表,用ldconfig维护。

-目录usr/lib。

-目录/lib。

-当前目录。

第二个参数:指定如何打开共享库。

-RTLD_NOW:将共享库中的所有函数加载到内存

-RTLD_LAZY:会推后共享库中的函数的加载操作,直到调用dlsym()时方加载某函数

(2)void *dlsym(void *restrict handle, const char *restrict name);

该函数返回一个指向由name所确定的请求入口点的指针。调用dlsym时,利用dlopen()返回的共享库的phandle以及函数/变量名称作为参数,返回要加载函数/变量的入口地址。

(3)char *dlerror(void);

dlerror 返回 NULL 或者一个指向描述最近错误的 ASCII 字符串的指针

(4)int dlclose(void *handle);

关闭句柄并且取消共享目标文件的映射 

4、显式链接举例

(1)动态库文件代码:dl_func.c

与隐式链接的代码相同。

(2)客户端事例代码:dl_demo.c

#include

#include

char name[100];

int main(int argc, char *argv[]) {

        int a = 10, b = 20;

        int c = 0;

        void *dlh = NULL;

        int (*add)();

        strcpy(name, "NHN XDBMS");

        if((dlh = dlopen("libdl_func.so", RTLD_LAZY)) == NULL) {

                fprintf (stderr, "***DL ERROR: %s.\n", dlerror ());

                return 1;

        }

        if((add = (int (*)())dlsym(dlh, "add")) == NULL) {

                fprintf (stderr, "***DL ERROR: %s.\n", dlerror ());

                return 1;

        }

        c = add(a, b);

        printf("%d + %d = %d\n", a, b, c);

        dlclose(dlh);

        return 0;

}

该事例程序给变量name赋值,以便在add()函数中打印。程序利用dlopen函数加载共享库libdl_func.so,利用dlclose关闭句柄,利用dlerror获取错误信息,利用dlsym定位共享库中的add函数,然后调用该函数执行加法计算。

(3)编译与运行

编译共享库:

与前述共享库编译方法相同。

编译事例程序:

gcc -o dl_demo -fPIC -ldl dl_demo.c

由于变量name需要被共享库中的add()函数使用,因此必须使用选项-fPIC。选项-ldl指示编译器需要用来到libdl.so库文件。

运行:

./dl_demo

输出:

与隐式链接事例的输出相同。

5、其他

(1)如事例中所给出的,除了共享库可以给别人使用外,共享库也可以使用调用程序中的变量,如在共享库中打印事例程序中的name。不过由于name在外部定义和声明因此在链接共享库时需要使用-shared选项。

(2)除了可以共享函数外,还可以共享变量,如果在dl_func.c中定义个变量:

int num = 100;

那么可以在事例程序中这样调用:

int *d;

d = (int *)dlsym(dlh, "num");

printf("num = %d\n", *d);

 

 

 

 

******************************************************************

选项 -rdynamic 用来通知链接器将所有符号添加到动态符号表中

(目的是能够通过使用 dlopen 来实现向后跟踪)

-rdynamic

Pass the flag ‘-export-dynamic’ to the ELF linker, on targets that support

it. This instructs the linker to add all symbols, not only used ones, to the

dynamic symbol table. This option is needed for some uses of dlopen or to

allow obtaining backtraces from within a program.

 

比如日志系统,主程序里使用一套日志系统,dlopen方式打开的libso里无法使用。编译时加上这个参数,不需要增加任何代码就可以使代码通用。

**********************************************************************************************************

 

http://blog.csdn.net/zzxzzy/article/details/6013185

你可能感兴趣的:(c++)