22.windows库程序(二)

1.动态库程序

  (1)动态库特点

     运行时独立存在

     不会链接到执行程序

     使用时加载(使动态库程序运行)

  (2)与静态库的比较

     由于静态库是将代码嵌入到使用程序中,多个程序使用时,会有多份代码,所以代码的体积会增大。

     动态库的代码只需要存在一份,其他程序通过函数地址使用,代码体积小。

     静态库发生变化后,新的代码需重新嵌入到执行程序中。

     动态库发生变化后,如果库中函数的定义(或地址)未变化,其他使用的程序不需要重新链接。

2.创建动态库

  (1)建立项目

  (2)添加库程序

  (3)库程序导出(偏移地址)- 提供给使用者库中的函数信息

3.动态库使用

  (1)隐式链接(使动态库程序运行起来的过程不需要程序员负责)

  (2)显式链接(使动态库程序运行起来的过程需要程序员自己负责)

4.动态库函数

  (1)实现动态库函数

  (2)库函数导出

     a.声明导出

       使用_declspec(dllexport)导出函数

       注意:动态库编译链接后,也会有LIB文件,是作为动态库函数映射使用,与静态库不完全相同。

     b.模块定义文件 .def

       例如:LIBRARY DLLFunc   //库

          EXPORTS               //库导出表

          DLL_Mul   @1         //导出的函数

5.库函数的使用

  (1)隐式链接

     头文件和函数原型

       可以在函数原型的定义前,增加_declspec(dllimport),例如_declspec(dllimport)int DLL_Add( .. );

       如果库函数使用C格式导出,需要在函数定义前增加extern "C"

     b.导入动态库的LIB文件

     c.在程序中使用函数

     d.隐式链接的情况,DLL可以存放的路径:

       与可执行文件在同一目录下

       当前工作目录

       Windows目录

       Windows/System32目录

       Windows/System

       环境变量PATH指定目录

       注意:高版本VC的配置文件

  (2)显示链接

     a.定义函数指针类型

     b.加载动态库(使动态库程序运行)

       HMODULE LoadLibrary( LPCTSTR lpFileName);  //动态库文件名或全路径

       返回DLL实例句柄(HINSTANCE)

     c.获取函数地址

       FARPROC GetProcAddress( HMODULE  hModule,        //DLL句柄

                                                           LPCSTR     lpProcName);  //函数名称

       成功返回函数地址

     d.使用函数

     e.卸载动态库

       BOOL FreeLibrary( HMODULE hModule);  //DLL实例句柄

  相关代码:

  (1)动态库代码 

//导出
_declspec(dllexport) int CPPdll_add(int a, int b)
{
    return a + b;
}

_declspec(dllexport) int CPPdll_sub(int a, int b)
{
    return a - b;
}

  (2)使用动态库(函数要换名)

#include "windows.h"
#include <iostream>


//1.定义函数指针类型
typedef int(*DLL_FUNC)(int, int);


int main()
{
    //2.加载动态库
    HINSTANCE hDll = LoadLibrary("CPPdll.dll");
    std::cout << "hDll:" << hDll << std::endl;

    //3.获取函数地址
    DLL_FUNC myAdd = (DLL_FUNC)GetProcAddress(hDll, "?CPPdll_add@@YAHHH@Z");
    std::cout << "myAdd:" << std::hex << myAdd << std::endl;

    //4.使用函数
    int sum = myAdd(5, 6);
    std::cout << "sum=" << std::dec << sum << std::endl;

    DLL_FUNC mySub = (DLL_FUNC)GetProcAddress(hDll, "?CPPdll_sub@@YAHHH@Z");
    std::cout << "mySub:" << std::hex << mySub << std::endl;
    int sub = mySub(5, 6);
    std::cout << "sub=" << std::dec << sub << std::endl;

    //5.下载动态库
    FreeLibrary(hDll);

    return 0;
}

  运行结果:

  

总结:

  1.制作动态库

    (1)声明导出:_declspec(dllexport),将函数的偏移地址导到了dll的文件头中。

       如果是C++编译器,dll文件中记录的是换名之后的函数名对应的偏移地址。

       lib文件中记录的仅仅是换名之后的函数名对应的编号。

    (2)模块定义文件导出:将函数的偏移地址导到了dll的文件头中。

       即便是C++编译器,dll文件头中记录的是未换名的函数名对应的偏移地址。

       lib文件中记录的仅仅是未换名之后的函数名对应的编号。

  2.使用动态库

    (1)隐式链接

       操作系统的加载器负责使动态库执行(拿到dll首地址)。

       链接器负责到lib文件中拿函数的编号,程序执行起来后,操作系统加载器拿着编号到dll文件头中查询函数的偏移地址。

    (2)显示链接

       调用LoadLibrary使动态库执行(拿到dll首地址)。

       调用GetProcAddress函数,通过函数的名称到dll文件头中查询函数的偏移地址。例如:GetProcAddress( hDll, "CPPDll_add")

  3.两种链接方式对比

    (1)在库函数定义不变的情况下

       隐式链接:由于库函数地址是在程序编译链接时设置的,所以当动态库变化后,使用程序需要重新编译链接。

       显示链接:由于库函数地址是在程序执行时动态的从库中查询,所以变化后不需要重新编译链接。

    (2)动态库加载时间

       隐式链接:动态库是在程序运行时被加载的,当dll不存在时,程序无法启动。

       显示链接:动态库只在使用LoadLibrary函数时,才会被加载。

原文地址:https://www.cnblogs.com/csqtech/p/5657457.html