C++中dll的生成与使用

一、动态链接库(dll)概述

在实际编程中,我们可以把完成某项功能的函数放在一个动态链接库里,然后提供给其他程序调用。像Windows API中所有的函数都包含在dll中,如Kernel32.dll, User32.dll, GDI32.dll等。那么dll究竟有什么好处呢?

1.1 静态库和动态库

  • 静态库:函数和数据被编译进一个二进制文件(扩展名通常为.lib),在使用静态库的情况下,在编译链接可执行文件时,链接器从静态库中复制这些函数和数据,并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.exe)。当发布产品时,只需要发布这个可执行文件,并不需要发布被使用的静态库。

  • 动态库:在使用动态库时,往往提供两个文件:一个引入库(.lib,非必须)和一个.dll文件。这里的引入库和静态库文件虽然扩展名都是.lib,但是有着本质上的区别,对于一个动态链接库来说,其引入库文件包含该动态库导出的函数和变量的符号名,而.dll文件包含该动态库实际的函数和数据。

1.2 使用动态链接库的好处

  1. 可以使用多种编程语言编写:比如我们可以用VC++编写dll,然后在VB编写的程序中调用它。
  2. 增强产品功能:可以通过开发新的dll取代产品原有的dll,达到增强产品性能的目的。比如我们看到很多产品踢动了界面插件功能,允许用户动态地更换程序的界面,这就可以通过更换界面dll来实现。
  3. 提供二次开发的平台:用户可以单独利用dll调用其中实现的功能,来完成其他应用,实现二次开发。
  4. 节省内存:如果多个应用程序使用同一个dll,该dll的页面只需要存入内存一次,所有的应用程序都可以共享它的页面,从而节省内存。

二、dll的创建

dll的创建主要有两种方法:一是使用 __declspec(dllexport) 创建dll,二是使用模块定义(.def)文件创建dll。

方法1: 使用 __declspec(dllexport) 创建dll

先创建一个空的DlL的项目:
这里写图片描述

然后为工程添加一个C++源文件:Dll1.cpp,假设我要实现的是加法和减法运算,则代码如下:
myDl1.cpp

//extern "C"表示动态链接库中文件名称不发生改变 ,解决C和c++之间调用的问题
#define DL1_API extern "C" _declspec(dllexport)
#include "dl1.h"

int add(int a,int b)
{
    return a+b;
}

int substract(int a ,int b )
{
    return a - b ;
}

dl1.h


//定义宏
#ifdef DL1_API
#else
#define DL1_API extern "C" _declspec(dllimport) //交给客户端使用时
#endif


DL1_API int add (int a , int b);
DL1_API int substract(int a, int b);

点击运行,我们会得到以下文件:
这里写图片描述

既然已经有了这个dll文件,是不是就可以在其他程序中访问该dll中的add和subtract函数了呢?必须注意的一点是:应用程序如果想要访问某个dll中的函数,那么这个函数必须是已经被导出的函数。

为了查看一个dll中有哪些导出函数,Visual Studio提供了一个命令行工具:Dumpbin。

使用Dumpbin命令确认dll的导出函数

首先在命令行中进入到VS的安装目录下,运行一个名为VCVARS32.bat的批处理程序(对于VS2013来说,该bat文件位于\VC\bin目录下),该文件的作用是用来创建VC++使用的环境信息。(注意,当在命令行界面执行VCVARS32.bat文件后,该文件设置的环境信息只在当前命令行窗口生效。
然后输入dumpbin命令,即可列出该命令的使用方法:
这里写图片描述

那么想要查看一个dll提供的导出函数,在Dll1.dll文件所在目录下,在命令行中输入下述命令:

dumpbin -exports Dll1.dll

其中以下代码是为了在编译链接时,C++会按照自己的规则篡改函数的名称,这一过程称为“名字改编”。这会导致不同的编译器、不同的语言下调用dll发生问题。因此我们希望动态链接库文件在编译时,导出函数的名称不要发生变化。
为了实现这一目的,可以再定义导出函数时加上限定符:extern “C”,如

extern "C" __declspec(dllexport) int add(int a, int b){
//...
}

但是这种方式只能解决C++和C语言之间相互调用时函数命名的问题。为了彻底解决这个问题,可以通过模块定义(.def)文件实现。

方法2: 使用模块定义(.def)文件创建dll

1.创建.def结尾的文件
在项目目录创建.def结尾的文件,并导入到项目中
这里写图片描述
dll2.def

LIBERARY DLL2

EXPORTS
add
substract 

表示DLL2.dll导出add和substract函数,LIBRARY语句用于指定动态链接库的名称,该名称与生成的动态链接库名称一定要匹配

DLL2.cpp

int add(int a,int b)
{
    return a+b;
}

int substract(int a ,int b )
{
    return a - b ;
}

以上就是创建dll的两种方法,个人比较提倡使用模块定义(.def)文件创建dll,代码简洁的同时还没有名字改编的问题。

三、dll的使用

dll的使用也有两种方法,一是隐式链接的方式加载dll,二是显示加载方式加载dll。

方法1:隐式加载(需要.lib和.dll)
方法2:显式加载(需要.dll)

方法1:隐式链接方式加载dll

但截至目前我们已经制作了一个DLL的Demo程序了,我们可以先来看下效果:
新建一个新的Win32程序,添加一个dllTest.cpp用于测试我们生成dll

准备工作:
将生成的DL1.dll放到测试程序会生成的.exe文件的地方(Debug或Release,···\DllTest\Debug)

软件的DLL搜索目录:
.lib 文件搜索顺序:
.exe软件算在目录(Debug,Release)
当前目录
System32
System
Path中的目录

DL1.lib文件放入项目文件中(···\DllTest\)

.lib 表示输入库文件,没有实际代码,包含重定位表

这里写图片描述

dllTest.cpp

//第一种引用的方法
//extern int add ( int a ,int b);
//extern int substract (int a, int b);

//第一种引用的方法
/*
告诉编译器函数是从.lib文件中导入的,编译器会据此生成效率更高的代码
*/
//_declspec(dllimport) int add (int a , int b);
//_declspec(dllimport) int substract(int a, int b);

//第三种方法
//导入头文件
#include "I:\SDK_settle\DL_Test\DL1\DL1\dl1.h"

#include <iostream> 


using namespace std;

int main()
{
    cout<< add(3,4);
    cout<< substract(3,4);
}

可以看出,我们使用了三种引用的方法,这三种方法都是可以的。

方法2:显式链接方式加载dll

DLL显式加载时指在程序运行过程中,需要用到dll里的函数时,再动态加载dll到内存中,这种加载方式因为是在程序运行后再加载的,所以可以让程序启动更快,而且dll的维护更容易,使得程序如果需要更新,很多时候直接更新dll,而不用重新安装程序.只是这种加载方式,函数调用稍微复杂一点

//第一种引用的方法
//extern int add ( int a ,int b);
//extern int substract (int a, int b);

//第一种引用的方法
/*
告诉编译器函数是从.lib文件中导入的,编译器会据此生成效率更高的代码
*/
//_declspec(dllimport) int add (int a , int b);
//_declspec(dllimport) int substract(int a, int b);

//第三种方法
//导入头文件
//#include "I:\SDK_settle\DL_Test\DL1\DL1\dl1.h"

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

using namespace std;

int main()
{
    //动态加载dll
    HINSTANCE hInst;
    hInst = LoadLibrary(L"DL1.dll");
    //根据函数名获取dll地址
    typedef int(*SUBPROC)(int a, int b);
    SUBPROC Sub = (SUBPROC)GetProcAddress(hInst, "substract");
    //调用dll的导出函数
    cout<< Sub(3,4);
    FreeLibrary(hInst);       //LoadLibrary后要记得FreeLibrary
}

以上就是两种生成dll的方法和两种加载dll的方法,建议使用第二种生成方法和第二种加载方法结合使用。

参考:
http://blog.csdn.net/elaine_bao/article/details/51784864
http://blog.csdn.net/enjoy5512/article/details/51548795

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 成长之路 设计师:Amelia_0503 返回首页