Linux的.a、.so和.o文件 windows下obj,lib,dll,exe的关系 动态库内存管理 动态链接库搜索顺序 符号解析和绑定 strlen函数的汇编实现分析

https://docs.microsoft.com/zh-cn/windows/win32/dlls/about-dynamic-link-libraries

关于Dynamic-Link库

动态链接允许模块仅包含加载时或运行时查找导出的 DLL 函数所需的信息。 动态链接不同于更熟悉的静态链接,其中链接器将库函数的代码复制到调用它的每个模块中。

动态链接的类型

在 DLL 中调用函数有两种方法:

  • 在 加载时动态链接中,模块对导出的 DLL 函数进行显式调用,就像它们是本地函数一样。 这要求将模块与包含函数的 DLL 的导入库链接。 导入库向系统提供加载 DLL 所需的信息,并在加载应用程序时找到导出的 DLL 函数。
  • 运行时动态链接中,模块使用 LoadLibrary 或 LoadLibraryEx 函数在运行时加载 DLL。 加载 DLL 后,模块调用 GetProcAddress 函数以获取导出的 DLL 函数的地址。 该模块使用 GetProcAddress 返回的函数指针调用导出的 DLL 函数。 这样就无需导入库。

DLL 和内存管理

加载 DLL 的每个进程都会将其映射到其虚拟地址空间。 进程将 DLL 加载到其虚拟地址后,可以调用导出的 DLL 函数。

系统为每个 DLL 维护每个进程引用计数。 当线程加载 DLL 时,引用计数将递增一个。 当进程终止或引用计数变为零 (运行时动态链接仅) 时,将从进程的虚拟地址空间卸载 DLL。

与任何其他函数一样,导出的 DLL 函数在调用它的线程的上下文中运行。 因此,以下条件适用:

  • 调用 DLL 的进程线程可以使用 DLL 函数打开的句柄。 同样,调用进程的任何线程打开的句柄可以在 DLL 函数中使用。
  • DLL 使用调用线程的堆栈和调用进程的虚拟地址空间。
  • DLL 从调用进程的虚拟地址空间分配内存。

 

https://docs.microsoft.com/zh-cn/windows/win32/dlls/advantages-of-dynamic-linking

动态链接的优点

动态链接在静态链接上具有以下优势:

  • 在同一基址上加载同一 DLL 的多个进程共享物理内存中 DLL 的单个副本。 执行此操作可节省系统内存并减少交换。
  • DLL 中的函数发生更改时,只要函数参数、调用约定和返回值不更改,就不需要重新编译或重新链接使用这些函数的应用程序。 相比之下,静态链接的对象代码要求在函数更改时重新链接应用程序。
  • DLL 可以提供市场后支持。 例如,可以修改显示驱动程序 DLL 以支持最初交付应用程序时不可用的显示器。
  • 使用不同编程语言编写的程序可以调用相同的 DLL 函数,只要程序遵循函数使用的同一调用约定。 调用约定 ((如 C、Pascal 或标准调用) 控制调用函数必须将参数推送到堆栈的顺序,无论函数还是调用函数负责清理堆栈,以及是否在寄存器中传递任何参数。 有关详细信息,请参阅编译器附带的文档。

使用 DLL 的潜在缺点是应用程序不是自包含的;这取决于单独的 DLL 模块是否存在。 如果在进程启动时找不到 DLL,系统将使用加载时动态链接终止进程,并向用户发出错误消息。 在这种情况下,系统不会使用运行时动态链接终止进程,但缺少 DLL 导出的函数对程序不可用。

 

 

静态库 (C++/CX) | Microsoft Learn https://learn.microsoft.com/zh-cn/cpp/cppcx/static-libraries-c-cx?view=msvc-170

演练:创建并使用静态库 (C++) | Microsoft Learn https://learn.microsoft.com/zh-cn/cpp/build/walkthrough-creating-and-using-a-static-library-cpp?view=msvc-170

使用静态库是重用代码的一种绝佳方式。 你不必在要求功能的每个应用中重新实现同一例程,而只需将其写入静态数据库一次,然后从应用引用它们即可。 从静态库链接的代码成为了应用的一部分,这样你就不必安装另一个文件来使用代码。

 

 

 

 

 

Linux的.a、.so和.o文件 - chlele0105的专栏 - CSDN博客 https://blog.csdn.net/chlele0105/article/details/23691147

在说明Linux的.a、.so和.o文件关系之前,先来看看windows下obj,lib,dll,exe的关系

https://zh.wikipedia.org/wiki/动态链接库 

动态链接库(英语:Dynamic-link library,缩写为DLL)是微软公司微软视窗操作系统中实现共享函数库概念的一种实现方式。这些库函数的扩展名.DLL.OCX(包含ActiveX控制的库)或者.DRV(旧式的系统驱动程序)。

https://zh.wikipedia.org/wiki/静态库

 在计算机科学中,静态库(英语:Static library, Statically-linked library),或称静态库,是一个外部函数与变量的集合体。静态库的文件内容,通常包含一堆程序员自定的变量与函数,其内容不像动态链接库那么复杂,在编译期间由编译器链接器将它集成至应用程序内,并制作成目标文件以及可以独立运作的可执行文件。而这个可执行文件与编译可执行文件的程序,都是一种程序的静态创建(static build)。以过去的观点来说,库只能算是静态(static)类型。

 

windows下obj,lib,dll,exe的关系

    lib是和dll对应的。lib是静态链接库的库文件,dll是动态链接库的库文件。
    所谓静态就是link的时候把里面需要的东西抽取出来安排到你的exe文件中,以后运行你的exe的时候不再需要lib。
    所谓动态就是exe运行的时候依赖于dll里面提供的功能,没有这个dll,你的exe无法运行。
   
    lib,dll,exe都算是最终的目标文件,是最终产物。而c/c++属于源代码。源代码和最终目标文件中过渡的就是中间代码obj,实际上之所以需要中间代码,是你不可能一次得到目标文件。比如说一个exe需要很多的cpp文件生成。而编译器一次只能编译一个cpp文件。这样编译器编译好一个cpp以后会将其编译成obj,当所有必须要的cpp都编译成obj以后,再统一link成所需要的exe,应该说缺少任意一个obj都会导致exe的链接失败。
   
    1.obj里存的是编译后的代码跟数据,并且有名称,所以在连接时有时会出现未解决的外部符号的问题。当连成exe后便不存在名称的概念了,只有地址。lib就是一堆obj的组合。
    2.理论上可以连接obj文件来引用其他工程(可以认为一个obj文件等价于编译生成它的cpp文件,可以引用obj来替换cpp,也可以添加cpp来替换obj ),但实际中通常用lib来实现工程间相互引用。
    3.编译器会默认链接一些常用的库,其它的需要你自己指定。
   
lib和DLL的区别

    (1)lib是编译时需要的,dll是运行时需要的。如果要完成源代码的编译,有lib就够了。如果也使动态连接的程序运行起来,有dll就够了。在开发和调试阶段,当然最好都有。
    (2) 一般的动态库程序有lib文件和dll文件。lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的。如果有dll文件,那么对应的lib文件一般是一些索引信息,具体的实现在dll文件中。如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。 静态编译的lib文件有好处:给用户安装时就不需要再挂动态库了。但也有缺点,就是导致应用程序比较大,而且失去了动态库的灵活性,在版本升级时,同时要发布新的应用程序才行。
    (3)在动态库的情况下,有两个文件,一个是引入库(.LIB)文件(实际上也算是一个静态库,只是在链接时只能把函数在DLL的入口链接到exe中,而不像真正静态链接库那样将函数体真正链接到exe中 ,通过lib进行的动态链接实际上也使用了静态链接来实现 ),一个是DLL文件,引入库文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。从上面的说明可以看出,DLL和.LIB文件必须随应用程序一起发行,否则应用程序将会产生错误。

DLL内的函数分为两种:
    (1)DLL导出函数,可供应用程序调用;
    (2)DLL内部函数,只能在DLL程序使用,应用程序无法调用它们

创建静态链接库和创建动态链接库

    VC6中创建[Win32 Dynamic-Link Library]工程便可以创建出一个空的DLL工程.

    VC6中创建[Win32 Static Library]工程便可以创建出一个空的LIB工程(静态链接库工程,仅生成一个lib文件).

添加lib文件的常用办法有二个:
    1、把*.lib放在VC的Lib目录中
    2、修改project setting的Link->Input中的Addtional library path,加入你的目录dll:是可实际运行的二进制代码,有定位代码的!

    3、也可以在object/library中直接写上lib文件路径.(这里实际上是可以写上任意obj文件或者lib文件的).

 

linux .o,.a,.so

        .o,是目标文件,相当于windows中的.obj文件 

  .so 为共享库,是shared object,用于动态连接的,相当于windows下的dll 

  .a为静态库,是好多个.o合在一起,用于静态连接 

 

静态函数库
特点:实际上是简单的普通目标文件的集合,在程序执行前就加入到目标程序中。
优点:可以用以前某些程序兼容;描述简单;允许程序员把程序link起来而不用重新编译代码,节省了重新编译代码的时间(该优势目前已不明显);开发者可以对源代码保密;理论上使用ELF格式的静态库函数生成的代码可以比使用共享或动态函数库的程序运行速度快(大概1%-5%)
生成:使用ar程序(archiver的缩写)。ar rcs my_lib.a f1.o f2.o是把目标代码f1.o和f2.o加入到my_lib.a这个函数库文件中(如果my_lib.a不存在则创建)
使用:用gcc生成可执行代码时,使用-l参数指定要加入的库函数。也可以用ld命令的-l和-L参数。
 
共享函数库
    共享函数库在可执行程序启动的时候加载,所有程序重新运行时都可自动加载共享函数库中的函数。.so文件感觉很复杂,光是命名规则就已经看得我很晕了~整理一下,共享库需要:soname、real name,另外编译的时候名字也有说法。依次解释下:
soname:必须的格式:lib+函数库名+.so+版本号信息(但是记住,非常底层的C库函数都不是以lib开头命名的)。例子:/usr/lib/libreadline.so.3
real name:顾名思义是真正的名字啦,有主版本号和发行版本号。但是没找到实例……
编译器编译的时候需要的函数库的名字就是不包含版本号信息的soname,例如上面的例子把最后的.3去掉就可以了。
位置:共享函数库文件必须放在特定目录,对于开放源码来说,GNU标准建议所有的函数库文件都放在/usr/local/lib目录下,而且建议命令、可执行程序都放在/usr/local/bin目录下。不过这个只是习惯啦,可以改变,具体的位置信息可以看/etc/ld.so.conf里面的配置信息。当然,也可以修改这个文件,加入自己的一些特殊的路径要求。
创建:在网上找到了gcc方式和easyeclipse环境下两种创建方式。
gcc方式:
    首先创建object文件,这个文件将加入通过gcc –fPIC 参数命令加入到共享函数库里面,标准格式:gcc -shared -Wl,-soname,your_soname -o library_name file_list library_list(说实话这个标准格式看起来好复杂,我找了个实例,但是好像和那个标准格式稍有不同:gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so)
在easyeclipse环境下生成.so文件:
        1.选择新建工程,建立一个c++工程
        2.在工程类型选项里选择 Shared Library,然后填入工程名字PXXX点击完成即可。
        3.编写程序,然后编译就会在debug或者release里生成一个libPXXX.so文件,如果不要lib的起头标记点击project菜单的Properties选项,然后在弹出的界面的右边点击Build artifact页面,将Output prefix选项的内容清空即可。
        4.如果是C++程序,注意在接口函数的前面加上extern "C"标记,在头文件加上如下标记:
#ifdef   __cplusplus 
#extern   "C"{ 
#endif 
  
头文件主体 
  
#ifdef   __cplusplus 

#endif 
     如果不加以上标记,经过编译后,so里的函数名并非你编写程序时设定的函数名,在开发环境左侧的工程文件列表中点开debug项里的PXXX.o可以看到so文件里的函数名都是在你设定的函数名后面加了一个__Fi标记,比如你用的设定的函数名称是Func(), 而so里的函数名则为Func__Fi()或者其他的名称。
安装:拷贝共享库文件到指定的标准的目录,然后运行ldconfig。如果没有权限这样做,那么就只好通过修改环境变量来实现这些函数库的使用了。方法不再说了,很复杂。
查看:可以通过运行ldd来看某个程序使用的共享函数库。例如ldd /bin/ls。查看.so文件使用nm命令,如nm libXXX.so。(注意,nm对于静态的函数库和共享的函数库都起作用)
关于覆盖:如果想用自己的函数覆盖某个库中的一些函数,同时保留该库中其他的函数的话,可以在/etc/ld.so.preload中加入要替换的库(.o结尾的文件),这些preloading的库函数将有优先加载的权利。
关于更新:每次新增加动态加载的函数库、删除某个函数库或者修改某个函数库的路径时,都要重新运行ldconfig来更新缓存文件/etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表

(在Linux下,共享库的加载是由/lib/ld.so完成的,ld.so加载共享库时,会从ld.so.cache查找)

 

内存管理

Win32中,DLL文件按照片段(sections)进行组织。每个片段有它自己的属性,如可写或是只读、可执行(代码)或者不可执行(数据)等等。这些section可分为两种,一个是与绝对地址寻址无关的,所以能被多进程公用;另一个是与绝对地址寻址有关的,这个就必须由每个进程有自己的副本专用。sections的这种二分类,在编译DLL时就已经由编译器、链接器给标注好了。所以在装入DLL时,装入器知道哪些sections在内存物理地址空间只需要有一份,供多个进程共享(映射到各个进程的内存逻辑地址空间,所以逻辑地址可以不同); 哪些sections必须是进程使用自己的专用副本。

具体说,DLL装入时需考虑下述情形:

  1. 局部变量——每个线程都有自己的,DLL内部的局部变量随所在函数被执行而在各自线程的调用栈上开辟存储空间。
  2. 全局变量
    1. DLL内部定义的全局变量
      1. const全局变量——放入const节中,多进程共享;
      2. 非const全局变量——放入各个进程各自专用的data节中。即DLL装入时各个进程复制一份自己专用的DLL的data节。但是,对于一个进程内的多个线程并发访问这种进程空间全局变量,仍然存在线程安全问题。例如,在一个COM的DLL加载入一个进程的空间后,该进程的多个线程可能会并发访问该COM库的COM对象。为此,Windows与COM引入了线程“套间”(apartment)技术。一个进程内,应用程序与加载的各个DLL分属于不同的Module,如果DLL使用所在Module的全局变量,例如动态链接MFC的regular dll在访问自己的MFC全局变量时,应该明确声明。
    2. 访问DLL以外定义的全局变量——使用间址技术,在DLL的data节中用一个指针数据类型的内存空间来保存一个外部全局变量的地址。
  3. 函数调用
    1. 调用DLL内部定义的函数。这不是问题。
    2. 调用DLL外部定义的函数。例如,DLL内部调用一个外部函数foo()。这个foo函数在进程1中可能实现为“四舍五入”,在进程2中实现为“下取整”。所以调用外部函数是各个进程私用的事情。解决办法是使用间址技术,在data节中用一个“函数指针”数据类型的内存空间来保存这种外部函数的入口地址。
  4. 跳转指令
    1. DLL内部跳转,不是问题
    2. 跳转到DLL外部,解决同上述3.2

DLL代码段通常被使用这个DLL的所有进程所共享。如果代码段所占据的物理内存被收回,它的内容就会被放弃,后面如果需要的话就直接从DLL文件重新加载。

与代码段不同,DLL的数据段通常是私有的;也就是说,每个使用DLL的进程都有自己的DLL数据副本。作为选择,数据段可以设置为共享,允许通过这个共享内存区域进行进程间通信。但是,因为用户权限不能应用到这个共享DLL内存,这将产生一个安全漏洞;也就是一个进程能够破坏共享数据,这将导致其它的共享进程异常。例如,一个使用访客账号的进程将可能通过这种方式破坏其它运行在特权账号的进程。这是在DLL中避免使用共享片段的一个重要原因。

当DLL被如UPX这样一个可执行的packer压缩时,它的所有代码段都标记为可以读写并且是非共享的。可以读写的代码段,类似于私有数据段,是每个进程私有的并且被页面文件备份。这样,压缩DLL将同时增加内存和磁盘空间消耗,所以共享DLL应当避免使用压缩DLL。

符号解析和绑定 . 

Symbol resolution and binding

 

DLL输出的每个函数都由一个数字序号唯一标识,也可以由可选的名字标识。同样,DLL引入的函数也可以由序号或者名字标识。对于内部函数来说,只输出序号的情形很常见。对于大多数视窗API函数来说名字是不同视窗版本之间保留不变的;序号有可能会发生变化。这样,我们不能根据序号引用视窗API函数。

按照序号引用函数并不一定比按照名字引用函数性能更好:DLL输出表是按照名字排列的,所以对半查找可以用来在在这个表中根据名字查找这个函数。另外一方面,只有线性查找才可以用于根据序号查找函数。

将一个可执行文件绑定到一个特定版本的DLL也是可能的,这也就是说,可以在编译时解析输入函数(imported functions)的地址。对于绑定的输入函数,链接工具保存了输入函数绑定的DLL的时间戳和校验和。在运行时Windows检查是否正在使用同样版本的库,如果是的话,Windows将绕过处理输入函数;否则如果库与绑定的库不同,Windows将按照正常的方式处理输入函数。

绑定的可执行文件如果运行在与它们编译所用的环境一样,函数调用将会较快,如果是在一个不同的环境它们就等同于正常的调用,所以绑定输入函数没有任何的缺点。例如,所有的标准Windows应用程序都绑定到它们各自的Windows发布版本的系统DLL。将一个应用程序输入函数绑定到它的目的环境的好机会是在应用程序安装的过程。

动态链接库搜索顺序

对于Windows,加载动态链接库时:

  • 如果内存中已经有同module名的DLL,除非是DLL redirection或manifest,否则直接就用内存中这个DLL而不再搜索。
  • 如果DLL名字属于当前Windows版本的Known DLL,则必须用Known DLL。清单见 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs.
  • 如果DLL有依赖DLL,操作系统按缺省标准规则根据module名字搜索依赖DLL。即使第一个DLL指定了全路径。

Windows Desktop应用程序的DLL标准搜索序:

  1. 应用程序所在目录;
  2. 系统目录。GetSystemDirectory函数返回该目录。
  3. 16比特系统目录;
  4. Windows目录。使用GetWindowsDirectory函数返回该目录。
  5. 当前(工作)目录;
  6. 环境变量PATH中列出的目录。

如果SafeDllSearchMode被禁止,则当前目录成为第二个被搜索的目录。

Windows Desktop应用程序的DLL替代搜索序。 这包括两种情况:

  • LoadLibraryEx函数使用参数LOAD_WITH_ALTERED_SEARCH_PATH:LoadLibraryEx函数不是从应用程序所在目录开始,而是从参数lpFileName(即要加载的dll)所在目录开始搜索所有依赖DLL。
  • 用SetDllDirectory函数修改搜素序。

当所有DLL都完成搜索,要开始执行DLL的初始化时,替代搜索序结束,恢复标准搜索序。

 

 

微软Visual C++(MSVC)提供了许多标准C++的扩展,它允许直接在C++代码中将函数(类、数据变量)声明为输入或输出;这种做法已经被Windows平台上其他的C和C++编译器采纳,包括Windows平台上的GCC

创建DLL输出函数

C 或 C++

 #include <windows.h>
 
 // Export this function
 extern "C" __declspec(dllexport) double AddNumbers(double a, double b);
 
 // DLL initialization function
 BOOL APIENTRY DllMain(HANDLE hModule, [[DWORD]] dwReason, LPVOID lpReserved)
 {
 	return TRUE;
 }
 
 // Function that adds two numbers
 double AddNumbers(double a, double b)
 {
 	return a + b;
 }

 

使用DLL输入

C 或 C++

 #include <windows.h>
 #include <stdio.h>
 
 // Import function that adds two numbers
 extern "C" __declspec(dllimport) double AddNumbers(double a, double b);
 
 int main(int argc, char **argv)
 {
 	double result = AddNumbers(1, 2);
 	printf("The result was: %f\n", result);
 	return 0;
 }

运行时使用显式调用

Using explicit run-time linking

下面的例子展示了如何使用不同语言特有的WIN32 API绑定进行运行时的调用和链接。

Microsoft Visual Basic

 Option Explicit
 Declare Function AddNumbers Lib "Example.dll" (ByVal a As Double, ByVal b As Double) As Double
 
 Sub Main()
     Dim Result As Double
     Result = AddNumbers(1, 2)
     Debug.Print "The result was: " & Result
 End Sub

C 或 C++

 #include <windows.h>
 #include <stdio.h>
 
 // DLL function signature
 typedef double (*importFunction)(double, double); 

 int main(int argc, char **argv)
 {
     importFunction addNumbers;
     double result;
 
     // Load DLL file
     HINSTANCE hinstLib = LoadLibrary("Example.dll");
     if (hinstLib == NULL) {
         printf("ERROR: unable to load DLL\n");
         return 1;
     }
 
     // Get function pointer
     addNumbers = (importFunction)GetProcAddress(hinstLib, "AddNumbers");
     if (addNumbers == NULL) {
         printf("ERROR: unable to find DLL function\n");
         return 1;
     }
 
     // Call function.
     result = addNumbers(1, 2);
 
     // Unload DLL file
     FreeLibrary(hinstLib);
 
     // Display result
     printf("The result was: %f\n", result);
 
     return 0;
 }

 

 

 

 

 

 

 

 

 

 

 

 

我们通常把一些公用函数制作成函数库,供其它程序使用。函数库分为静态库和动态库两
种。静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。动态
库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运
行时还需要动态库存在。本文主要通过举例来说明在Linux中如何创建静态库和动态库,以
及使用它们。

在创建函数库前,我们先来准备举例用的源程序,并将函数库的源程序编译成.o文件。


第1步:编辑得到举例的程序--hello.h、hello.c和main.c;

hello.c(见程序2)是函数库的源程序,其中包含公用函数hello,该函数将在屏幕上输出"
Hello XXX!"。hello.h(见程序1)为该函数库的头文件。main.c(见程序3)为测试库文件的
主程序,在主程序中调用了公用函数hello。

程序1: hello.h

#ifndef HELLO_H
#define HELLO_H

void hello(const char *name);

#endif //HELLO_H

 

程序2: hello.c

#include <stdio.h>

void hello(const char *name)
{
printf("Hello %s!\n", name);
}

程序3: main.c

#include "hello.h"

int main()
{
hello("everyone");
return 0;
}

第2步:将hello.c编译成.o文件;

无论静态库,还是动态库,都是由.o文件创建的。因此,我们必须将源程序hello.c通过g
cc先编译成.o文件。

在系统提示符下键入以下命令得到hello.o文件。

# gcc -c hello.c

#

我们运行ls命令看看是否生存了hello.o文件。

# ls

hello.c hello.h hello.o main.c

#


在ls命令结果中,我们看到了hello.o文件,本步操作完成。

下面我们先来看看如何创建静态库,以及使用它。

第3步:由.o文件创建静态库;

静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a。例如:我们将
创建的静态库名为myhello,则静态库文件名就是libmyhello.a。在创建和使用静态库时,
需要注意这点。创建静态库用ar命令。

在系统提示符下键入以下命令将创建静态库文件libmyhello.a。

# ar -cr libmyhello.a hello.o

#

我们同样运行ls命令查看结果:

# ls

hello.c hello.h hello.o libmyhello.a main.c

#

ls命令结果中有libmyhello.a。

第4步:在程序中使用静态库;

静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包
含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明静态库名,gcc将会从
静态库中将公用函数连接到目标文件中。注意,gcc会在静态库名前加上前缀lib,然后追
加扩展名.a得到的静态库文件名来查找静态库文件。

在程序3:main.c中,我们包含了静态库的头文件hello.h,然后在主程序main中直接调用公
用函数hello。下面先生成目标程序hello,然后运行hello程序看看结果如何。

法一 # gcc -o hello main.c -L. –lmyhello,或gcc  main.c -L. –lmyhello -o hello自定义的库时,main.c还可放在-L.和 –lmyhello之间,但是不能放在它俩之后,否则会提示myhello没定义,但是是系统的库时,如g++ -o main(-L/usr/lib) -lpthread main.cpp就不出错。

法二 #gcc main.c libmyhello.a -o hello或gcc  -o hello main.c libmyhello.a

法三:先生成main.o:gcc -c main.c ,再生成可执行文件:gcc -o hello main.o libmyhello.a或gccmain.o libmyhello.a -o hello ,动态库连接时也可以这样做。

 

# ./hello

Hello everyone!

#

我们删除静态库文件试试公用函数hello是否真的连接到目标文件 hello中了。

# rm libmyhello.a

rm: remove regular file `libmyhello.a'? y

# ./hello

Hello everyone!

#

程序照常运行,静态库中的公用函数已经连接到目标文件中了。

我们继续看看如何在Linux中创建动态库。我们还是从.o文件开始。

第5步:由.o文件创建动态库文件;

动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀lib,但其
文件扩展名为.so。例如:我们将创建的动态库名为myhello,则动态库文件名就是libmyh
ello.so。用gcc来创建动态库。

在系统提示符下键入以下命令得到动态库文件libmyhello.so。

 

# gcc -shared -fPIC -o libmyhello.so hello.o (-o不可少)

#

我们照样使用ls命令看看动态库文件是否生成。

# ls

hello.c hello.h hello.o libmyhello.so main.c

#

第6步:在程序中使用动态库;

在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含
这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明动态库名进行编译。我们
先运行gcc命令生成目标文件,再运行它看看结果。

# gcc -o hello main.c -L. -lmyhello

 

(或 #gcc main.c libmyhello.so -o hello 不会出错(没有libmyhello.so的话,会出错),但是接下来./hello 会提示出错,因为虽然连接时用的是当前目录的动态库,但是运行时,是到/usr/lib中找库文件的,将文件libmyhello.so复制到目录/usr/lib中就OK了)

# ./hello

./hello: error while loading shared libraries: libmyhello.so: cannot open shar
ed object file: No such file or directory

#

哦!出错了。快看看错误提示,原来是找不到动态库文件libmyhello.so。程序在运行时,
会在/usr/lib和/lib等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提
示类似上述错误而终止程序运行。我们将文件libmyhello.so复制到目录/usr/lib中,再试
试。

# mv libmyhello.so /usr/lib

# ./hello

Hello everyone!

#

成功了。这也进一步说明了动态库在程序运行时是需要的。

我们回过头看看,发现使用静态库和使用动态库编译成目标程序使用的gcc命令完全一样,
那当静态库和动态库同名时,gcc命令会使用哪个库文件呢?抱着对问题必究到底的心情,
来试试看。

先删除除.c和.h外的所有文件,恢复成我们刚刚编辑完举例程序状态。

# rm -f hello hello.o /usr/lib/libmyhello.so

# ls

hello.c hello.h main.c

#

在来创建静态库文件libmyhello.a和动态库文件libmyhello.so。

在生成动态库时,需要使用-fPIC,这样才能生成位置无关的代码,达到代码段和数据段共享的目的

# gcc -c -fpic hello.c  //编译hello.c时也需要加上-fpic选项,否则rodata' can not be used when making a shared object; recompile with -fPIC

# ar -cr libmyhello.a hello.o (或-cvr )

# gcc -shared -fPIC -o libmyhello.so hello.o

# ls

hello.c hello.h hello.o libmyhello.a libmyhello.so main.c

#

通过上述最后一条ls命令,可以发现静态库文件libmyhello.a和动态库文件libmyhello.s
o都已经生成,并都在当前目录中。然后,我们运行gcc命令来使用函数库myhello生成目标
文件hello,并运行程序 hello。

# gcc -o hello main.c -L. –lmyhello (动态库和静态库同时存在时,优先使用动态库, 当然,直接#gcc main.c libmyhello.a -o hello的话,就是指定为静态库了)

# ./hello

./hello: error while loading shared libraries: libmyhello.so: cannot open shar
ed object file: No such file or directory

#

从程序hello运行的结果中很容易知道,当静态库和动态库同名时,gcc命令将优先使用动态库,默认去连/usr/lib和/lib等目录中的动态库,将文件libmyhello.so复制到目录/usr/lib中即可。

Note:
编译参数解析
最主要的是GCC命令行的一个选项:
-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件


-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code)。那么在产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。

如果不加fPIC,则编译出来的代码在加载时需要根据加载到的位置进行重定位(因为它里面的代码并不是位置无关代码),如果被多个应用程序共同使用,那么它们必须每个程序维护一份so的代码副本了.(因为so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)。

不用此选项的话编译后的代码是位置相关的,所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。

-L. 表示要连接的库在当前目录中;(多个库:在编译命令行中,将使用的静态库文件放在源文件后面就可以了。比如:gcc -L/usr/lib myprop.c libtest.a libX11.a libpthread.a -o myprop
其中-L/usr/lib指定库文件的查找路径。编译器默认在当前目录下先查找指定的库文件,如前面的“法二 #gccmain.c libmyhello.a-o hello”)


-lmyhello 编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so或.a来确定库的名称libmyhello.so或libmyhello.a。
LD_LIBRARY_PATH这个环境变量指示动态连接器可以装载动态库的路径。
当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用 /sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。

调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。

 

 

静态库链接时搜索路径顺序:

1. ld(GNU linker)会去找GCC命令中的参数-L

   编译过程是分为四个阶段:预处理(也称预编译,Preprocessing)、编译(Compilation)、汇编 (Assembly)和连接(link)  【链接】
2. 再找gcc的环境变量LIBRARY_PATH
3. 再找内定目录 /lib /usr/lib /usr/local/lib 这是当初compile gcc时写在程序内的

动态链接时、执行时搜索路径顺序:

1. 编译目标代码时指定的动态库搜索路径
2. 环境变量LD_LIBRARY_PATH指定的动态库搜索路径
3. 配置文件/etc/ld.so.conf中指定的动态库搜索路径
4. 默认的动态库搜索路径/lib
5. 默认的动态库搜索路径/usr/lib

有关环境变量:
LIBRARY_PATH环境变量:指定程序静态链接库文件搜索路径
LD_LIBRARY_PATH环境变量:指定程序动态链接库文件搜索路径

 


另:

从上述可知,如何找到生成的动态库有3种方式:

(1)把库拷贝到/usr/lib和/lib目录下。

(2)在LD_LIBRARY_PATH环境变量中加上库所在路径。

例如动态库libhello.so在/home/example/lib目录下:

export LD_LIBRARY_PATH=LD_LIBRARY_PATH:/home/example/lib

(3) 修改/etc/ld.so.conf文件,把库所在的路径加到文件末尾(直接写在文件末尾,不要在路径前加include),并执行ldconfig刷新(ldconfig 命令的用途,主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库(格式如前介绍,lib*.so*),进而创建出动态装入程序(ld.so)所需的连接和缓存文件.缓存文件默认为/etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表.)。这样,加入的目录下的所有库文件都可见。

附:像下面这样指定路径去连接系统的静态库,会报错说要连接的库找不到:

g++ -o main main.cpp -L/usr/lib libpthread.a 

必须这样g++ -o main main.cpp -L/usr/lib -lpthread才正确 。

自定义的库考到/usr/lib 下时,

g++ -o main main.cpp -L/usr/lib libpthread.a libthread.a libclass.a会出错,但是这样g++ -o main main.cpp -L/usr/lib -lpthread -lthread -lclass就正确了。 

 

posted @ 2018-10-21 19:14  papering  阅读(868)  评论(0编辑  收藏  举报