跟我学c++中级篇——动态库

一、基本概念和介绍

在人们的实际生活中,经常会遇到这样一些工作,比如大街上的小商贩经常通过喊话来吸引人们的注意,但是这些话基本的内容都是一致的,如果日复一日,年复一年的不断的喊此类东西,累不用说,对喊话者来说,也是一种无意义的负担。那么怎么解决这个问题呢?小喇叭,带录音功能的小喇叭,有多少个地方就发多少录好音的小喇叭。就节省了好多的人力、物力。但是如果地方越来越多,而且播放的声音经常改呢?这又出来新的问题,于是,是不是可以用网络联通,各个小喇叭只是访问一下服务器,只在服务器上有一个播放的声音源就可以了呢?这个很容易理解。
而同样,在编写程序的过程,也经常会遇到类似的问题。比如,经常写一些操作文件的模块或者写一些显示打印的东西等等。另外随着计算机的发展,共享设备也越来越多,这样,就导致大量的硬件设备的代码需要共用。比如装了几十个应用软件,都需要打印,那么就写好一个模块,让这几十个应用软件调用,每一个自己保持一个模块的副本就可以了。
可是,如果软件越来越多,程序中要保存相同的N个副本,其中代码完全一样,只是数据不一样,怎么办呢?可不可以共用一个副本呢,答案也是肯定的。看看,这两者是多么的类似啊。当开发者把一个个可复用的模块编写成一个通用的可动态调用(不需要重复编译相同的源码,不需要链接重复的目标文件)的模块文件时,就形成了动态库。
而上面的介绍其实就是动态库发展的过程。在Windows系统中,在System32目录下,可以看到很多以dll结尾的文件,在Linux的lib文件夹下,可以看到很多以so结尾的文件,这都是动态库,只是在不同的平台上,表现的方式不一样罢了。在早期的动态库技术中,对每份共用的代码,都在内存中形成一份单独的副本,和相关的其它代码模块共同形成一个可执行程序运行。但是正如上面所言,这样的话会浪费大量的资源。随着计算机技术的发展,特别是到了X64的平台下,PIC(position independent code:位置无关)的提出,通过修改动态库的代码访问符号,只需要加载一份动态库的副本。这样,动态库其实也可以称作是共享库了。

二、特点

在Linux下创建动态库的编译选项有一个-fPIC,它的意思是与位置无关(position-independet code),那么它有什么意义呢?在没有出现这个选项前,是不是就不能创建动态库呢。答案肯定是否定的。其实这就是上面提到的发展过程中的,如果没有这个选项,只能完全加载副本,而如果使用了这个选项就可以进程共享这个副本了。没有这个选项前的动态库是一种运行时加载,比较原始。而使用其以后,就多进程共享此副本,在X64平台上基本已经默认使用了。
可以这样认为,在X32平台上,这个选项,可以忽略,在X64上不使用这个选项,在链接时,就会发生链接错误。那么是不是必须使用这个选项才可以呢?也不是,可以向编译器指定 -mcmodel=large来防止这个错误。
多说几句,编译静态库,是否使用这个选项,要看这个静态库的应用方式,如果这个静态库需要被动态库使用,就要加上这个选项,否则,可以认为加不加没有什么影响。
动态库的特点,本质就是二进制接口的兼容。也就是在c++编程中经常提到的ABI,在很多c++牛人的文章中经常可以看到,一定要保持二进制的接口的稳定性,不要使用纯虚函数做为接口,就是这个原因。因为动态库在这方面的体现的更为明显。
c++的重载和编译更名机制,使得c++的二进制接口变得更为复杂,链接器就需要在这种复杂的情况下,保证在链接时能找到相关的接口。链接器使用一种叫做“名称修饰”的技术来进行符号的定位。不过可惜的是,这种技术并不是标准的,这意味着不同编译平台处理可能有所不同,甚至在某方面有很大不同,这也是为什么有的代码可以在某些编译器正常编译,但是有的不行的一个外在表现。
动态库有两种加载方式 ,一种是加载时动态链接,一种是运行时动态链接。这个很好理解,一种是在编译时就把动态库编译进来,一种是程序在启动后,通过通用的API,比如dlopen,来打开相关动态库。

三、 应用

动态库的应用现在已经非常广泛,举一个例子,在Windows的system32路径下和LINUX的lib路径下,可以看到大片的动态库文件(windows是以.dll为后缀, linux是以.so为后缀)。日常接触到的最常用的应用场景就是,公司有一项技术不想为其它公司知道,但又要为其它公司提供服务,就可以把相关的代码打成动态库,再提供一个头文件。这样,代码的安全性就得到了很大的保障。下面看一个LINUX下的简单的动态库的例子:

#include <iostream>

int Compare(int a,int b)
{
        if (a > b)
        {
                return a;
        }
        else
        {
                return b;
        }

        return 0;
}

用下面的命令进行编译:

g++ -fPIC -shared -o libcompare.so compare.cpp

这里需要说明的是,LINUX下的动态库,要求必须以lib开头,加自定义的名字再加后缀.so。否则编译可能找不到相关的库。
为了省事,就没有单独建立一个头文件,直接把函数的声明放到了应用的文件中。

#include <iostream>

int Compare(int a,int b);

int main()
{
        int a = 100;
        int b = 200;

        int max = Compare(a,b);
        std::cout<<"max is :"<< max<< std::endl;

        return 0;
}

用下面的命令进行编译:

g++ -o dtest testdll.cpp -L.  -lcompare

编译完成后,看下目录下,会发现有两个新生成的文件:libcompare.so 和 dtest。这时个执行一下这个文件:

./dtest
./dtest: error while loading shared libraries: libcompare.so: cannot open shared object file: No such file or directory

发生这个的原因是在于LINUX下的编译和运行时的机制的问题,在运行时没有找到这个库文件,解决的方法有以下两种方法:
1、临时的最简单的方法:
LD_LIBRARY_PATH=. ./dtest 或者export LD_LIBRARY_PATH=pwd:$LD_LIBRARY_PATH
将库的路径临时指到当前路径。

2、将库拷贝到/usr/lib路径下(或相关目录),然后在/etc/ld.so.conf(其实更多的我们自己单独创建一个文件,把这个文件挂到这个文件中),再ldconfig即可。这种方法在实际工程中经常使用。
打开/etc/ld.so.conf,发现其内容为:

include /etc/ld.so.conf.d/*.conf

其它不同平台可能还有不同的解决方法,这里就不一一列举。
通过上面这个简单的一个小例子,可以了解动态库的一个初步开发流程,在后面会相继展开在两种平台下的动态库编程的详细介绍,先抛出一块小砖,再请同学们慢慢品尝后面的美食。

四、总结

动态库技术是一门相对来说的复杂的技术,但也不是说有多难。整体上融会贯通之后,让你可以从底层的角度了解内存的管理和寻址的方式,更好的和OS原理中的一些阐述相结合。知易行难,很多东西需要亲手来实践一下,才能够明白个中的滋味。简单的看看资料,走马观花一样的浏览一下,是不会让同学们成为高手。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值