第十八章-C语言进阶(二)

C语言的库

  1. 库的基本概念
    1. 库是已经写好的、成熟的、可复用的代码。每个程序都需要依赖很多底层库,不可能每个人的代码从零开始编写代码,因此库的存在具有非常重要的意义。
    2. 在我们的开发的应用中经常有一些公共代码是需要反复使用的,就把这些代码编译为库文件。
    3. 库可以简单看成一组目标文件的集合,将这些目标文件经过压缩打包之后形成的一个文件。像在Windows这样的平台上,最常用的c语言库是由集成按开发环境所附带的运行库,这些库一般由编译厂商提供。

静态库

静态库的创建

  1. 创建一个新项目,在已安装的模板中选择“常规”,在右边的类型下选择“空项目”,在名称和解决方案名称中输入staticlib。点击确定。
  2. 在解决方案资源管理器的头文件中添加,mylib.h文件,在源文件添加mylib.c文件(即实现文件)。
  3. 在mylib.h文件中添加如下代码:

     #pragma once
     #ifdef __cplusplus
     extern "C" {
     #endif // __cplusplus
        
     	int myadd(int a,int b);
        
     #ifdef __cplusplus
     }
     #endif // __cplusplus
    
  4. 在mylib.c文件中添加如下代码:

     #include"test.h"
     int myadd(int a, int b){
         return a + b;
     }
    
  5. 配置项目属性。
    1. 右击当前项目名称->属性->常规->配置类型->”静态库.lib”
    2. 因为这是一个静态链接库,所以应在项目属性的“配置属性”下选择“常规”,在其下的配置类型中选择“静态库(.lib)“。
  6. 编译生成新的解决方案
    1. 编译一下,在项目目录的Debug文件夹中找到staticlib.lib复制出来,同时也把mylib.h文件复制出来
    2. 在Debug文件夹下会得到mylib.lib (对象文件库),将该.lib文件和相应头文件给用户,用户就可以使用该库里的函数了。

静态库的使用:

  1. 方法一:(最简单、最常用
    1. 将上面打的静态库:staticlib.lib、mylib.h手动复制到当前项目的根目录
    2. 右击项目->添加->现有项->选中mylib.lib与mylib.h,添加
    3. 在程序中导入mylib.h头文件即可
  2. 方法二:
    1. 使用编译语句: #pragma comment(lib,"./staticlib.lib")
  3. 方法三: 配置项目属性
    1. 添加工程的头文件目录:工程—属性—配置属性—c/c++—常规—附加包含目录:加上头文件存放目录。
    2. 添加文件引用的lib静态库路径:工程—属性—配置属性—链接器—常规—附加库目录:加上lib文件存放目录。
    3. 然后添加工程引用的lib文件名:工程—属性—配置属性—链接器—输入—附加依赖项:加上lib文件名。

静态库优缺点

  1. 优点:
    1. 静态库对函数库的链接是放在编译时期完成的,静态库在程序的链接阶段被复制到了程序中,和程序运行的时候没有关系;
    2. 程序在运行时与函数库再无瓜葛,移植方便。
  2. 缺点:
    1. 浪费空间和资源,所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
    2. 内存和磁盘空间
      1. 静态链接这种方法很简单,原理上也很容易理解,在操作系统和硬件不发达的早期,绝大部门系统采用这种方案。随着计算机软件的发展,这种方法的缺点很快暴露出来,那就是静态链接的方式对于计算机内存和磁盘空间浪费非常严重。特别是多进程操作系统下,静态链接极大的浪费了内存空间。在现在的linux系统中,一个普通程序会用到c语言静态库至少在1MB以上,那么如果磁盘中有2000个这样的程序,就要浪费将近2GB的磁盘空间。
    3. 程序开发和发布
      1. 空间浪费是静态链接的一个问题,另一个问题是静态链接对程序的更新、部署和发布也会带来很多麻烦。
      2. 比如程序中所使用的mylib.lib是由一个第三方厂商提供的,当该厂商更新容量mylib.lib的时候,那么我们的程序就要拿到最新版的mylib.lib,然后将其重新编译链接后,将新的程序整个发布给用户。这样的做缺点很明显,即一旦程序中有任何模块更新,整个程序就要重新编译链接、发布给用户,用户要重新安装整个程序。

windows下动态库创建和使用

  1. 要解决空间浪费和更新困难这两个问题,最简单的办法就是把程序的模块相互分割开来,形成独立的文件,而不是将他们静态的链接在一起。简单地讲,就是不对哪些组成程序的目标程序进行链接,等程序运行的时候才进行链接。也就是说,把整个链接过程推迟到了运行时再进行,这就是动态链接的基本思想。

动态库的创建

  1. 创建一个新项目,在已安装的模板中选择“常规”,在右边的类型下选择“空项目”,在名称和解决方案名称中输入mydll。点击确定。
  2. 在解决方案资源管理器的头文件中添加,mydll.h文件,在源文件添加mydll.c文件(即实现文件)。
  3. 在test.h文件中添加如下代码:

     #ifndef TEST_H
     #define TEST_H
        
     __declspec(dllexport) int myminus(int a, int b);
        
     #endif
    
  4. 在test.c文件中添加如下代码:

     #include"mydll.h"
     __declspec(dllexport) int myminus(int a, int b){
     	return a - b;
     }
    
  5. 配置项目属性。
    1. 因为这是一个动态链接库,所以应在项目属性的“配置属性”下选择“常规”,在其下的配置类型中选择“动态库(.dll)。
  6. 编译生成新的解决方案,
    1. 在Debug文件夹下会得到mydll.dll (对象文件库)mydll.lib文件,将该.dll文件、.lib文件和相应头文件给用户,用户就可以使用该库里的函数了。
  7. 疑问一:__declspec(dllexport)是什么意思?
    1. windows中,静态库中的所有函数都是外部函数,都可以让外部使用;
    2. 动态链接库中定义有两种函数:
      1. 内部函数(internal function):内部函数在定义它们的DLL程序内部使用,函数前什么都不加,就默认为内部函数
      2. 导出函数(export function):导出函数可以被其它模块调用。但是函数前必须添加前缀:__declspec(dllexport)
    3. 注意:.h文件中可以不写__declspec(dllexport),但是.c文件中必须写!!!
  8. 动态库的lib文件和静态库的lib文件的区别?
    1. 在使用动态库的时候,往往提供两个文件:一个引入库(.lib)文件(也称“导入库文件”)和一个DLL(.dll)文件。虽然引入库的后缀名也是“lib”,但是,动态库的引入库文件和静态库文件有着本质的区别,对一个DLL文件来说,其引入库文件(.lib)包含该DLL导出的函数和变量的符号名,而.dll文件包含该DLL实际的函数和数据。
    2. 在使用动态库的情况下,在编译链接可执行文件时,只需要链接该DLL的引入库文件lib,该DLL中的函数代码和数据并不复制到可执行文件,直到可执行程序运行时,才去加载所需的DLL,将该DLL映射到进程的地址空间中,然后访问DLL中导出的函数。
  9. 动态库还有一种创建方式,那就是创建的时候直接选择DLL工程类型

动态库的使用

  1. 方式一:常用
    1. 创建主程序TestDll,将mydll.h、mydll.dll和mydll.lib手动复制到源代码目录下。
      1. 在主窗口中右击main.c标签->打开所在文件夹,即可进入
    2. 右击项目->添加->现有项->选中mydll.h,添加
    3. 右击项目->属性->配置属性(展开)->连接器->输入->附加依赖项->编辑->输入mydll.lib(注意只需要输入mydll.lib,不需要输入mydll.dll),确定即可
       #include<stdio.h>
       //直接不需要导入mydll.h头文件
       //#include"mydll.h"
       int main() {
           	int a, b;
           	a = 10;
           	b = 20;
                  
           	printf("%d\n", myminus(b, a));
           	printf("hello world");
           	getchar();
           	return 0;
       }
      
  2. 方法二:隐式调用
    1. 创建主程序TestDll,将mydll.h、mydll.dll和mydll.lib手动复制到源代码目录下。注意了:不需要导入(添加现有项)到项目中!!!
    2. (P.S:头文件Func.h并不是必需的,只是C++中使用外部函数时,需要先进行声明)
    3. 在程序中指定链接引用链接库 : #pragma comment(lib,"./mydll.lib")

       #include<stdio.h>
       #pragma comment(lib,"./mydll.lib")
       //有上面一句话,就可以直接导入了
       #include"mydll.h"
              
       int main() {
       	int a, b;
       	a = 10;
       	b = 20;
              
       	printf("%d\n", myminus(b,a));
       	printf("hello world");
       	getchar();
       	return 0;
       }
      
  3. 方法三:显式调用

     HANDLE hDll; //声明一个dll实例文件句柄
     hDll = LoadLibrary("mydll.dll"); //导入动态链接库
     MYFUNC minus_test; //创建函数指针
     //获取导入函数的函数指针
     minus_test = (MYFUNC)GetProcAddress(hDll, "myminus");
    

静态库与动态库编译之后的exe区别

  1. 静态库:
    1. 直接将exe文件取出即可,因为exe文件中已经包含了静态库lib的所有内容
    2. 当n个exe使用了同一个lib时,那么这n个exe就拥有n个lib,浪费内存
  2. 动态库
    1. exe文件运行必须依赖于dll文件,否则报错,因为此时的exe文件中不包含动态库dll的代码
    2. 当n个exe文件用了同一个dll时,这n个exe可以共用一个dll,节省内存。
    3. 拓展:我们下载别人的windows软件,点击exe安装时,有时会报:xx.dll文件丢失,指的就是该动态库丢失了
  3. 动态库中的lib文件呢?
    1. 动态库中的lib文件是资源描述文件,描述dll文件的。这个跟静态库一样会在编译时期编译到exe中去。
    2. 真正的动态库代码在dll文件中。
Table of Contents