动态链接库在 Windows(dll) 以及 Linux(so)下编程中的应用



一、Windows 中的动态链接库
    1、动态链接库的形式
        Windows 中的动态链接库为 *.dll文件,在编译器生成*.dll的同时,会产生一个*.lib文件,该文件体积较小,保存了*.dll的链接信息。
    2、使用方式
        使用dll文件的方式有两种:显示调用和隐式加载。
        1)隐式加载需要上面提到的的lib文件,在工程中包含相应的*.h头文件,以及*.lib文件,即可正常使用dll提供的导出方法以及导出类(不提倡使用导出类)。
        2)显示调用使用LoadLibrary加载相应的dll文件,使用GetProcAddress获取dll中的导出方法。
        隐式加载通常是在同一个解决方案(Workspace)中,某些工程输出为dll文件,而某些工程为可执行的程序,在程序中引用dll以解除耦合性,便于后续在不影响程序的正常使用的情况下对dll单独升级,只要dll导出的方法没有发生改变,dll就可以直接替换。
        显示调用通常可以对dll模块进行独立开发,开发完成后,提供dll文件以及一个描述导出方法的头文件,使用时,添加头文件即可。
        隐式加载,dll的存放路径有三种:系统目录比如 Windows\System32\;执行程序所在目录;环境变量PATH 所包含的目录。显示调用则没有这些限制,因为LoadLibrary参数就是dll文件的路径。
    3、dll依赖
        如果我们调用的dll依赖于其他的dll,那我们需要手动处理这些依赖关系
        1)显示调用依赖关系没什么好说的,根据路径查找。
        2)对于隐式加载的依赖关系,如果dll都放在系统目录或者当前目录下,那没什么问题。但如果因为某些需求dll不在系统目录或者当前目录下,那就比较棘手了, 会经常遇到明明dll路径正确,却在LoadLibrary时报找不到该模块的错误,这往往就是dll依赖的dll没有被加载。下面给出一个案例以说明
       现有一个文件夹DirAAA,里边放了三个dll : A、B、C。A引用了B,B又引用了C(这里的引用都是隐式加载)。在外边的一个文件夹DirBBB中有一exe程序,用LoadLibrary调用A,不成功。按说B、C和A放在一起,为什么会无法加载呢?(看的头晕?那就跳过看总结吧)问题来了,这是因为虽然A、B、C放在一起,但是对于exe程序来说,当前目录是DirBBB,而不是DirAAA!所以根本找不到B和C。这里解决办法有这样几个,把B和C放进系统目录、在PATH环境变量中增加DirBBB,但这里显然不想这么做,不然就不会来讨论依赖问题了。解决方法是:同样用LoadLibrary把B和C载入进来。当然,载入是有顺序的,必须先加载不依赖其它dll的C,然后载入B,最后加载A时,就可以了。
        分析认为,当B和C被加载以后,加载A时,会发现B和C已被加载到内存中,于是就可以正常加载了。这点和接下来要讨论的Linux下不同!
        B和C就LoadLibrary就可以了,不用做别的。保存下获取到的句柄,以便在程序结束时调用FreeLibrary将其释放。这里释放是否有顺序问题,我没有验证。
        总结:以上话说了那么多,本质上就是,把依赖的dll挨个用LoadLibrary加载进来就可以了,注意顺序以及依赖嵌套依赖。
        

二、Linux中的动态链接库(共享库)
    1、动态链接库的形式
    Linux中的动态链接库为*.so文件,一般命名为 libxxx.so或者类似于libxxx.1.2.3.so或者libxxx.so.1.2.3。开头的lib表示共享库,xxx是库名字,so是扩展名,这里的1.2.3是版本号。与Windows不同的是,Linux中没有对应的lib文件,只有一个.so文件,而Windows中如果没有lib文件会报“符号未定义”的链接错误的。
    2、使用方式
    使用so文件同样有两种:显示调用和隐式加载。
    1)隐式加载是在链接时,将其链接进目标文件。链接有两种形式:
        ①是指明其具体路径,比如:./../Utils.so”就是引用的动态共享库的具体路径。(-o 表示指定生成名称,-ldl 表示需要使用动态共享库)
            g++ -shared -o libDemo.so Demo.o ./../libUtils.so(这里是生成so文件的样例,生成执行文件的样例如  g++  -o run  -ldl  ./../libUtils.so)
        ②是使用系统路径,这里的 系统路径指 /lib、/usr/lib、/usr/local/lib,以及LD_LIBRARY_PATH等环境变量包含的路径,格式如下:
            g++ -shared -o libDemo.so -lUtils -L./../
            这里 -l后边接库名,不要前缀lib和扩展名so,后边的-L指明共享库文件的路径,如果位于刚指出的 系统路径中,则不需要加-L参数了
    2)显示调用同Windows,是在执行时,调用dlopen加载so文件,调用dlsym从其中导出方法(函数)。
    3)为什么这里出来了第三种?这种方式只是讨论 编译so的时候引用so的情况下。so引用其他so,也适用于1)和2) 两种情况,但是还有第三种情况,那就是不引用。什么,不引用?那能通过链接么?答案肯定是能,不然就不用在这里讨论了。假设 Linux下编译A so文件引用了B so文件,可以直接引入B的头文件(假设引入方法FunB),或者写上 extern语句表明 FunB方法来自外部,就可以使用 FunB。这种方式可以正常链接成功,但可想而知,这种方式编译出来的so文件是找不到 FunB的具体位置的,因为 FunB存在于外部!所以①隐式加载该A so文件时,会报“对‘ FunB()’未定义的引用”的错误。需要将B so同时引入到工程中才可以通过。②但是显示加载就不会报错了,因为显式加载是在运行时加载A so,因此运行时会报刚才那个错误,解决办法当然是先把 B so显式加载进来。这里又涉及到了依赖,将在接下来讨论。这里还有个问题,如果我这里加载了B so以后,又加载了 C so,里边也包含了 FunB,那A so调用时会调用哪个?答案是根据顺序来,调用首先加载的B so中的FunB。

    无论是Windows还是Linux,当前目录都是指可执行程序所在的目录。隐式加载时指定具体路径,链接生成的目标程序执行时,都是从具体路径中查找so文件;若使用系统路径,则需要从上边提到的系统路径中查找。显式加载则没有这些限制,但是同样会碰到依赖问题。

    3、so依赖
    如果我们使用的so文件又依赖其他so文件,我们同样要处理这种依赖关系。
    1)隐式加载的依赖关系没得商量,只能按照链接时的方式来。指明具体路径的,到相应的具体路径中查找so;使用系统路径的,到上边提到的系统路径中查找so。值得注意的是,如果是系统路径,默认情况下,当前目录是不包含在内的,这一点跟Windows不同,这具有一定的可靠性,但也造成了一些不便。这种情况下,可以修改环境变量来解决,具体方法请百度一下。(没加载依赖时报找不到某个so文件的错)
   2) 显示加载依赖关系,根据路径查找,没什么好说的。
   3)对于第三种关系,把所有的依赖项挨个用dlopen加载进来就是了。


三、对比

    Windows与Linux加载动态库非常明显的区别在于对依赖项的处理上。在Windows中,在查找依赖的dll时,并不考虑其路径问题,只要能找到(在内存中或者在系统路径或者在当前路径)就可以了,即使这些路径里都找不到,那显示地用LoadLibrary加载进来即可。但在Linux中,依赖项只能按照当初链接时设定的规则,即使A so已经被加载进内存,如果B so使用具体路径方式引用了A so,加载B so时,依然会使用具体路径方式查找依赖的A so,尽管A so已经被加载进了内存。

四、抛砖引玉

    在什么情况下如何组织动态库以及动态库之间的依赖关系,希望比较有经验的朋友能够点拨一二,将在后边更新在这里。


本文为最近学习的整理,如有不对的地方,还希望大家给予指正,共同提高!

因为不能保证正确,转载请尽量使用链接转载,以便大家能够看到及时更新的内容。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值