我们知道库一般有静态库和动态库2种:
- 静态库是编译时就链接到可执行文件中的;
- 动态库是在程序运行时再进行加载的。
故本文讨论的链接与加载方式是指对动态库而言的。
一、动态库的加载方式
1、隐式加载
就是我们需要准备好.h、.lib或者.so,对头文件进行包含,并添加对lib的链接命令,来完成对库函数的调用。这种链接方式,称之为隐式链接。
在程序从开始运行时,就会按照系统中一定的搜索路径,寻找动态库,找到就自动加载它,才能成功运行程序,这些步骤,是系统自动完成的。
一般来说,隐式链接的动态库,在程序加载时,无需用户干涉,便称之为隐式加载。
一般通常使用隐式加载的方式,比显示加载,代码编写更简单。
2、显式加载
我们对动态库的调用,是在代码中直接使用LoadLibrary,或其他加载函数,直接对dll或so进行加载,然后解析文件中的函数符号,并调用该函数。这种链接方式,称之为显式链接。
这种加载方式依赖于用户的加载代码,所以是程序运行起来后,属于延迟加载。需要用户明确知道库文件名称,故称之为显式加载。
这种方式,解析函数符号的代码较多,编写不方便。
二、Windows下程序如何查找dll
1、搜索顺序
在win系统下,应用程序搜索其依赖的dll时,按如下顺序进行查找:
- 应用程序所在的路径;
- Windows的SYSTEM目录,如C:\Windows\System,通过调用GetSystemDirectory函数可以获取这个目录的路径;
- Windows目录,如C:\Windows,通过调用GetWindowsDirectory函数可以获取这个目录的路径;
- PATH环境变量指定的路径。
上述查找顺序,对隐式加载、显式加载方式的dll均有效。
隐式加载dll时,没什么好说的,必定通过上述顺序查找dll。
显式加载dll时,由于使用了LoadLibrary函数,如下:
HMODULE WINAPI LoadLibrary(
_In_ LPCTSTR lpFileName
);
- 如果lpFileName字符串指定完整路径,则该函数仅搜索该dll的路径。
- 如果lpFileName字符串指定一个没有路径的dll名称或者相对路径(如lpFileName=“Hello.dll”),则该函数使用标准搜索策略来查找模块,也就是使用上述顺序进行查找"Hello.dll"。
2、指定隐式加载dll的路径
对于需要隐式加载的dll,由于其加载过程由系统接管。即隐式加载的dll是在exe主体模块之前加载进来的,执行顺序上是先通过PE文件的导入表加载隐式的dll,然后再加载EXE本体。
所以在程序exe中无法通过代码,来指定隐式加载dll的路径。
只能通过修改PATH环境变量来指定加载dll的路径,或者直接将dll移入可以被搜索到的路径中来实现。
3、指定显式加载dll的路径
对于需要显式加载的dll,由于其LoadLibrary函数参数,即可指定dll的路径;若只提供了文件名,则也可以将dll的路径设置到PATH环境变量中,或者直接将dll移入可以被搜索到的路径中。
4、小结
dll的搜索顺序,对于无论是显式、隐式加载dll,都是有效的,因为都需要查找dll。只不过显式加载时,它通过传递参数直接定位到具体dll时,不需要额外搜索。
牢记:使用隐式加载时,程序中无法通过代码,来指定该dll搜索路径。
无论显式、隐式加载,通常最简单的办法,就是将dll与exe放于同一目录下。
三、Linux下程序如何查找so
1、搜索顺序
在linux系统下,应用程序搜索其依赖的so时,按如下顺序进行查找:
- gcc编译时指定的运行时库路径 -Wl,-rpath
- 环境变量LD_LIBRARY_PATH指定的路径
- 从cache文件/etc/ld.so.cache中查找,该文件包含了先前在库路径中找到的编译好的候选库列表。
- 系统默认库位置/lib和/usr/lib路径
上述查找顺序,对隐式加载、显式加载方式的so均有效。
隐式加载so时,没什么好说的,必定通过上述顺序查找so。
显式加载so时,由于使用了dlopen函数,如下:
void* dlopen(const char* filename, int flags);
- 如果filename字符串指定完整路径,则该函数仅搜索该dll的路径。
- 如果filename字符串指定一个没有路径的dll名称或者相对路径(如lpFileName=“Hello.so”),则该函数使用标准搜索策略来查找模块,也就是使用上述顺序进行查找"Hello.so"。
关于linux下显式加载so的例子,可参考《Linux 动态加载并调用动态库(.so)方法介绍》。
2、指定隐式加载so的路径
对于需要隐式加载的so,由于其加载过程由系统接管。即隐式加载的so是在程序主体模块之前加载进来的,执行顺序上是先加载隐式的so,然后再加载程序本体。
所以在程序中无法通过代码,来指定隐式加载so的路径。
但是可以通过修改上述顺序中任一环节,来指定隐式加载的so路径。
linux与win有区别,win下直接将dll和exe放于同一目录就可以找到。但是在linux下不行,这显得有些智障。。。主要是linux的理念不一样,系统设计者希望大家把so库都放到系统指定的目录下,但是个人并不喜欢这样做,很多的应用so糅合在一起很乱。
推荐使用第一种,即指定gcc编译选项来实现。
假设,我们使用Qt开发的程序,希望在程序所在目录下lib/中去寻找so,那么可以在程序工程的.pro文件中,添加如下编译选项:
QMAKE_LFLAGS += -Wl,-rpath=./lib
则该程序启动时,会在./lib/去找so。当然也可以改成./当前目录下,这就和win下时很像了。
另外,如果程序显示加载1.so,1.so隐式加载调用2.so,出现找不到2.so的问题。那么,可以在1.so代码所属工程.pro中,添加上述的编译选项,就可以解决这个问题。
3、指定显式加载so的路径
对于需要显式加载的so,由于其dlopen函数参数,即可指定so的路径;若只提供了文件名,则可以通过修改上述顺序中任一环节,来指定so路径。
4、小结
so的搜索顺序,对于无论是显式、隐式加载,都是有效的,因为都需要查找so。只不过显式加载时,它通过传递参数直接定位到具体so时,不需要额外搜索。
牢记:使用隐式加载时,程序中无法通过代码,来指定该so搜索路径。但是可以通过编译选项来指定。
四、总结
win、linux下动态库的加载和搜索机制很相似,但是有一些不同,主要不同之处体现在,隐式加载动态库时:
- win下,只能通过修改PATH指定库路径,但是默认支持搜索程序当前目录。
- linux下,支持gcc编译选项指定库路径,但是默认不支持搜索程序当前目录。
另外,在win下,使用隐式加载时,程序中无法通过代码,来指定该dll搜索路径。
在linux下,虽然依然不能通过程序中代码来,指定该so搜索路径;但是可以使用gcc编译选项的方式,提前指定该程序搜索so的路径,也算是一种很好的补充方式。
若对你有帮助,欢迎点赞、收藏、评论,你的支持就是我的最大动力!!!
同时,阿超为大家准备了丰富的学习资料,欢迎关注公众号“超哥学编程”,即可领取。