我们以前写C语言代码的时候,基本上都是要包含了对应的头文件,才能使用一些函数,比如我们使用printf函数的时候,最常用的就是stdio.h头文件,stdio.h中有我们printf的声明,它真正的实现是在我们的底层库中。
    首先,我们先来看看什么是库?
    库:函数库是实现了某一类功能的若干的函数和数据二进制代码的集合。
    注意:库中虽然是函数的二进制代码集合,但不能独立执行,可以被载入内存,和其他程序结合起来执行。
    Windows    静态库:xxx.lib
                     动态库:xxx.dll
    Linux         静态库:xxx.a
                     动态库:xxx.so
    
    我们这里所说的静态库和动态库指的是链接时刻不同。首先,我们来回忆一下编译的流程,我们让普通程序编译成可执行程序的步骤:预处理——编译——汇编——链接
静态库和动态库的生成和加载详解-LMLPHP
    其实前3个步骤是我们的基本C语言代码都需要运行的,生成xxx.o文件,最终向三个方向发展=>可执行文件、静态库或动态库。

一、静态库
    静态库之所以叫静态库,是因为我们在编译的阶段,就把我们函数库的代码载入可执行文件中。由于整个函数库的所有数据都会被整合进目标代码中,那么它的优缺点就显而易见了。
    优点:就是编译后的执行程序不需要外部的函数库支持。
    缺点:就是生成的文件比较大,若是函数库的代码改变了,则需要重新编译。
    特点:静态库的代码在编译过程中已经被载入可执行程序,因此生成的可执行程序体积较大。

下面介绍使用VS2008制作和调用静态库:
1.工程的建立——选择Win32项目,输入工程名
静态库和动态库的生成和加载详解-LMLPHP
在应用程序设置中选择“静态库”选项
静态库和动态库的生成和加载详解-LMLPHP
这样就完成了工程的创建。

2.添加两个文件,一个为lib.cpp,另一个为头文件lib.h。我们所编写的函数放在lib.cpp,而其原型放在lib.h。这两个文件的内容为
  1. //lib.cpp
  2. #include "stdafx.h"
  3. #include "lib.h"

  4. int add(int x,int y)
  5. {
  6.     return x + y;
  7. }
  1. //lib.h
  2. #ifndef LIB_H
  3. #define LIB_H

  4. extern "C" int add(int x,int y);

  5. #endif
注意:可能部分同学对“lib.h”中的extern “C”的表达形式有疑问,其实就是一种编译规约,其中extern是关键字属性,表征了变量、函数等类型的作用域(可见性)属性,该关键字告诉编译器它所声明的函数和变量可以在本模块或者文件以及其他模块或文件中使用;“C”表征了编译器链接规范。所以整体可以理解为C++/C中的混合编程的编译指令。
    编译后,根据编译配置类型,可以在Release或Debug文件夹下即可查看编译后的结果。
静态库和动态库的生成和加载详解-LMLPHP

3.下面看如何使用这个函数,新建一个控制台应用程序,并将“lib.h”以及生成后的“win32_Lib.lib”复制至新建工程的源文件夹下,再编写测试应用源程序如下:
  1. //test_Lib.cpp:定义控制台应用程序的入口点
  2. #include <stdio.h>
  3. #include "lib.h"//函数原型声明

  4. #pragma comment(lib,"win32_Lib.lib");//将静态库导入

  5. int main()
  6. {
  7.     printf("2+3 = %d",add(2,3));
  8.     getchar();
  9.     return 0;
  10. }
注意:可能部分同学也没见过“#pragma comment”,所以不知道是什么意思,其实“#pragma”指令是所有的预处理指令中最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。原声明为:#pragma comment(comment-type,"commentstring")comment-type是一个预定义的标志符,指定注释的类型,应该是compiler,exestr,lib,linker之一;commentstring是一个comment-type提供附加信息的字符串。
    #pragma comment(lib,"win32_Lib.lib");表示链接win32_Lib.lib这个库。和在工程设置里写上链入win32_Lib.lib的效果是一样的,不过这种方法写的程序,别人在使用你的代码的时候,就不用再设置工程settings了

二、动态库
    顾名思义,根据名字你就知道,我们动态库肯定是动态链接的。没错,我们的动态函数库在编译的时候并没有被编译进目标代码中,而是在运行的时候,才来链接我们函数库中要用到的函数。因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库,以及库路径。
    特点:动态库的代码在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此可执行代码体积小。

下面介绍使用VS2008制作和调用动态库:
1.工程的建立——选择Win32项目,输入工程名win32_Dll
在应用程序设置中选择“静态库”选项
静态库和动态库的生成和加载详解-LMLPHP
这样就完成了工程的创建。

2.添加两个文件,一个为win32_Dll.cpp,另一个为头文件win32_Dll.h。我们所编写的函数放在win32_Dll.cpp,而其原型放在win32_Dll.h。这两个文件的内容为:
  1. // win32_Dll.cpp : 定义 DLL 应用程序的导出函数。
  2. #include "stdafx.h"
  3. #include "win32_Dll.h"

  4. #define EXPORTING_DLL

  5. int add(int x,int y)
  6. {
  7.     return x + y;
  8. }
  1. //win32_Dll.h
  2. #ifndef WIN32_DLL_H
  3. #define WIN32_DLL_H

  4. #ifdef EXPORTING_DLL
  5. #define API_TYPE __declspec(dllexport)
  6. #else 
  7. #define API_TYPE __declspec(dllimport)
  8. #endif

  9. extern "C" int API_TYPE add(int x,int y);
  10. #endif
注意:可能部分同学也没见过“__declspec(dllexport)”和“__declspec(dllimport)”,所以不知道是什么意思,其实一个是提供者,一个是使用者,二者之间的接口是头文件。头文件中声明了方法,在提供者那里方法应该被声明为“__declspec(dllexport)”,在使用者那里,方法应该被声明为“__declspec(dllimport)”。二者使用同一个头文件,作为接口,使用条件编译:定义一个变量,针对提供者和使用者,设置不同的值。

编译后,根据编译配置类型,可以在Release或Debug文件夹下即可查看
编译后的结果。
静态库和动态库的生成和加载详解-LMLPHP

3.下面看如何使用这个函数,新建一个控制台应用程序,并将生成后的“win32_Dll.dll”和“win32_Dll.lib”复制至新建工程的源文件夹下,再编写测试应用源程序如下:
  1. #include <Windows.h>
  2. #include <stdio.h>

  3. typedef int (*lpAddFun)(int,int);//函数指针

  4. int main()
  5. {
  6.     HINSTANCE hDll;
  7.     lpAddFun addFun;
  8.     hDll = LoadLibrary("win32_Dll.dll");//加载动态库
  9.     if(hDll)
  10.     {
  11.         addFun = (lpAddFun)GetProcAddress(hDll,"add");
  12.         if (addFun)
  13.         {
  14.             printf("2 + 3 = %d\n",addFun(2,3));
  15.         }
  16.     }
  17.     getchar();
  18.     FreeLibrary(hDll);//卸载动态库
  19.     return 0;
  20. }
注意:可能部分同学也没用过“函数指针”,所以对着段代码不好理解,可以参考我之前写的这篇博客:http://blog.chinaunix.net/uid-31439230-id-5763256.html
LoadLibray()函数载入指定的动态链接库,并将它映射到当前进程使用的地址空间,一旦载入,即可访问库内保存的资源;
GetProcAddress()寒山寺检索指定的动态链接库(DLL)中输出库函数地址,如果函数调用成功,返回值是DLL中输出函数地址,失败,返回值是NULL。



08-30 22:49