动态库

1 动态库的特点

(1)运行时独立存在

(2)不会链接到执行程序

(3)使用时加载(使用动态库,必须使动态库执行)

2 动态库和静态库的比较

(1)由于静态库使将代码嵌入到使用程序中,多个程序使用时,会有多份代码,所以代码体积会增大,动态库的代码只需要存在一份,其他程序通过函数地址使用,所以代码体积小

(2)静态库发生变化后,新的代码需要重新链接嵌入到执行程序中(需要重新编译),动态库发生变化后,如果库中的函数的定义(或地址)未变化,其他使用dll的程序不需要重新链接(直接运行即可,不需要重新编译)。

(3)动态库编译链接后,也会生成lib文件,是作为动态库函数映射使用,与静态库不相同

3 动态库的创建

3.1 使用__declspec(dllexport)导出函数

(1)应用程序选择dll,选择空项目

(2)添加头文件和源文件

头文件如下:

#ifndef _DLLTEST_H_
#define _DLLTEST_H_

extern "C" __declspec(dllexport) void MyPrint();

#endif // _DLLTEST_H_

源文件如下:

#include "dlltest.h"

#include <iostream>

void MyPrint()
{
    std::cout << "it is myprint function" << std::endl;
}

注:当使用显示链接(dll函数的使用方法之一)的时候动态库导出函数前面要加extern "C",因为函数名会被编译器改变,导致GetProcAddress函数找不到函数

(3)测试程序如下

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

int main()
{
    HMODULE hmodule  = LoadLibrary("dlltest.dll");
    if (hmodule)
    {
        typedef void(*LoadProc)();
        LoadProc loadproc = (LoadProc)GetProcAddress(hmodule, "MyPrint");
        if (loadproc)
        {
            loadproc();
            FreeLibrary(hmodule);
        }
        else
        {
            FreeLibrary(hmodule);
        }
    }
    return 0;
}

注:测试程序使用显示链接方式加载动态库

3.2 使用模块定义文件.def

(1)应用程序选择dll类型,选择空项目

(2)添加头文件和源文件

头文件如下:

#ifndef _DEFTEST_H_
#define _DEFTEST_H_

void MyPrint();

#endif //_DEFTEST_H_

源文件如下:

#include "deftest.h"

#include <iostream>

void MyPrint()
{
    std::cout << "def test function" << std::endl;
}

(3)添加def文件

 

 

(4)编写def文件内容,def文件内容的格式如下:

LIBRARY DllName
EXPORTS
Insert @1
Delete @2
Member @3
Min @4

注:

(1).def 文件中的第一条 LIBRARY 语句不是必须的,但LIBRARY 语句后面的 DLL 的名称必须正确,即与生成的动态链接库的名称必须匹配。此语句将 .def 文件标识为属于 DLL。链接器将此名称放到 DLL 的导入库中。

(2)EXPORTS 语句列出名称,可能的话还会列出 DLL 导出函数的序号值。通过在函数名的后面加上 @ 符和一个数字,给函数分配序号值。当指定序号值时,序号值的范围必须是从 1 到 N,其中 N 是 DLL 导出函数的个数。

def文件的内容如下:

LIBRARY deftest

EXPORTS
    MyPrint @1

(5)测试动态库,测试程序如下:

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

int main()
{
    HMODULE hmodule  = LoadLibrary("deftest.dll");
    if (hmodule)
    {
        typedef void(*LoadProc)();
        LoadProc loadproc = (LoadProc)GetProcAddress(hmodule, "MyPrint");
        if (loadproc)
        {
            loadproc();
            FreeLibrary(hmodule);
        }
        else
        {
            FreeLibrary(hmodule);
        }
    }
    return 0;
}

4 动态库的使用,动态库的使用方法有两种,一种隐式链接,一种显示链接,上面的测试程序使用的都是显示链接

4.1 显示链接动态库

(1)加载动态库

HMODULE LoadLibrary(LPCTSTR lpFileName  /*动态库文件名或全路径*/
); //返回DLL的实例句柄(HINSTANCE)

(2)使用typedef定义函数指针类型

(3)获取函数的绝对地址

FARPROC GetProcAddress(
        HMODULE hModule,    //DLL句柄
        LPCSTR lpProcName   //函数名称 //可以查出来导出来的相对地址
    ); //成功返回函数地址

(4)使用函数

(5)卸载动态库

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

注:具体使用方法见上述测试程序

4.2 隐式链接动态库

(1)将lib文件,dll文件,头文件拷贝到测试项目目录下

(2)包含头文件,lib文件(可在项目设置链接器里加,也可通过代码显示加载,具体方法见静态库的加载使用)

(3)直接使用库中函数

注:也可不包含头文件,使用语句__declspec(dllimport) void MyPrint();告诉编译器函数声明,一般会在头文件通过条件编译设置不同的值,如下:

#ifndef DLL_H_
#define DLL_H_

#ifdef DLLProvider
#define DLL_EXPORT_IMPORT __declspec(dllexport)
#else
#define DLL_EXPORT_IMPORT __declspec(dllimport)
#endif

DLL_EXPORT_IMPORT int add(int ,int);

#endif

但一般我们在使用过程中,不需要添加__declspec(dllimport) 调用一般函数也可以,所以上面的测试程序就没有使用__declspec(dllimport)导入函数,那是不是__declspec(dllimport)没啥用?

当然不是,当我们使用动态库的类中静态函数时,必须使用__declspec(dllimport)导出函数,否则会出现无法解析的外部符号,一般动态库包含类时,都需要条件编译区分导入导出宏,因为只是记录动态库的基础知识,故不深究,以后有时间再记录这部分的知识。

测试程序很简单,如下:

#include "stdafx.h"

// #include "deftest.h"

__declspec(dllimport) void MyPrint();

int main()
{
    MyPrint();
    return 0;
}

注:我已经在项目属性中的链接器里加上lib库,故在代码里没有加载静态库的代码(#pragma comment语句)

(4)隐式链接的情况,dll可以存放的路径:与执行文件在同一个目录下(建议使用),当前工作目录,windows目录,windows/system32目录,windows/system目录,环境变量path指定目录

4.3 隐式链接和显示链接对比

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

显示链接,动态库只在使用loadlibrary函数,才会被加载

建议:调用大公司的库,使用隐式链接,小公司的库使用显示链接

 

4 dll中类的使用,前面的测试程序都是直接使用函数,当在dll中声明定义类时,也使用_declspec(dllexport) 导出,如下:

class _declspec(dllexport) CMath {
    ...
    }; //导出类的成员函数
//类的声明给用户看,类的实现不给用户看。

当使用dll中的类时,使用方法相同,先导入dll中lib,然后定义类的实例,调用类中的函数

5 显示链接动态库注意事项

(1)LoadLibrary加载动态库失败,一般为文件路径错误和字符串格式(项目编码方式使用多字节方式,也可使用TEXT宏包含字符串)问题

(2)GetProcAddress返回空,找不到函数地址,在dll中的函数定义前加上 extern ”C“即可(因为函数名字被编译器改变产生的问题)

(3)64位进程和32位的动态库不可互相调用,当然,32位的进程和64位的动态库也不可调用,两者版本需要统一

6 静态库lib和动态库输出的lib的区别

静态库的lib:lib包含函数代码本身(即包括函数的索引,也包括实现),在编译时直接将代码加入程序当中

动态库的lib:lib包含了函数所在的dll文件和文件中函数位置的信息(索引),函数实现代码由运行时加载在进程空间中的dll提供

posted @ 2019-07-18 16:28  Truman001  阅读(607)  评论(0编辑  收藏  举报