c++动态链接库的创建和使用

动态库

动态链接库(Dynamic Link Library,DLL)是Windows操作系统中实现共享函数库概念的一种方式,这些库函数的扩展名是 ”.dll”、”.ocx”(包含ActiveX控制的库);

在使用动态库的时候,往往需要提供两个文件:一个引人库(.lib)和一个DLL文件(.dll);

  1. 引人库(.lib)

    引入库是包含该dll导出的函数和变量的符号名,可以认为是函数和变量的声明;在编译和链接可执行文件时,需要引用引人库中的符号名;

  2. DLL文件(.dll)

    当可执行文件运行并且使用到DLL文件中的函数和变量时,系统才去加载相对应的DLL,并将DLL文件映射到进程的地址空间,然后去访问DLL文件中的导出函数;一句话,dll文件应用于程序运行时。


创建Win32 DLL文件

1.创建工程

在工程中右键添加新项目,项目类型:Win32,模板:Win32项目,输入Dll名字;在弹出的对话框中,选择应用程序类型为“DLL(D)选项”。如下图所示:

这里写图片描述

2.添加导出函数

应用程序如果想要访问某个dll中的函数,那么该函数必须是已经被导出的函数;设置方法如下:

需要在每一个被导出的函数前面增加标识符:_declspec(dllexport);
形如:_declspec(dllexport) int add(int a, int b);

按照指定格式添加其他的导出函数后,点击Build按钮就可以生成.lib文件和.dll文件;我将动态链接库的工程名命名为Sampledll,生成结果位于Ctest解决方案下的Debug目录:

这里写图片描述


隐式链接方式加载dll头

1.函数名字改编问题

由于C++语言中有函数重载特性,在实际目标文件中函数名会被编译器作特殊处理,比如目标文件的函数名会包含参数类型,而C语言则不会包含参数类型,比如:

int  add(int a, int b);//代码函数声明
_add                   //C语言的目标文件函数名
_add_int_int           //c++语言的目标文件函数名

为了C语言和C++都能调用dll文件中API函数,我们希望动态链接库文件在编译时,导出函数的名称不要发生变化,为了实现这个目的,在定义导出函数时,需要添加上限定符:extern “C”,C一定要大写

利用限定符:extern “C”可以解决C++和C语言之间相互调用时函数命名的问题,但是该方法只能用于导出全局函数,不能用于导出一个类的成员函数。

另外,如果导出函数的调用约定方式发生了变化,即使添加了限定符extern “C”,函数名字在编译dll时还会发生变化。

2.隐式加载dll方式

  1. 利用extern声明的外部函数,形如:
    extern int _stdcall add(int a, int b);

  2. 利用_declspec(dllimport)声明的外部函数
    _declspec(dllexport) int _stdcall add(int a, int b);

与extern关键字这种方式相比,使用_declspec(dllimport)标识符声明的外部函数时,它将告诉编译器该函数是从动态链接库中引入的,编译器可以生成运行效率更高的代码。因此,如果调用的函数来自于动态链接库,应该采用_declspec(dllimport)声明外部函数。

3.头文件设计

为了方便其他模块引用DLL文件中的API函数,我们在DLL工程中,添加Sampledll.h头文件;其他模块需要使用dll文件中的函数,则先添加Sampledll.h至本工程;
需要注意的,如果使用这种共享头文件的方式,一定要用_declspec(dllexport)声明的方式,否则没有.lib文件生成。

头文件内容如下:

#ifndef _SAMPLE_H_
#define  _SAMPLE_H_

#ifdef DLL_API
    //内部调用
#else
    #define DLL_API extern "C" _declspec(dllexport)//外部调用
#endif
//接口API
DLL_API  int _stdcall add(int a, int b);
DLL_API  int _stdcall  Multiply(int a,int b);

#endif

4.使用动态链接库编程

我们创建一个MultiThread Win32控制台程序,并在该工程中使用dll文件中的API函数,主要关键步骤如下:
1. MultiThread工程中添加.lib文件;
2. 将.lib文件和dll文件放在MultiThread 工程目录下;
3. #include”Sampledll.h”

添加.lib文件参考VS2005中添加lib文件的方法文章。

在MultiThread 工程中,主要关键代码如下:

stdafx.h

#pragma once

#include "targetver.h"

#include <stdio.h>
#include <tchar.h>
//新增内容,添加lib文件
#pragma comment(lib, "..\Debug\SampleDll.lib")

MultiThread .cpp

#include "..\SampleDll\SampleDll.h"

int _tmain(int argc, _TCHAR* argv[])
{
    cout << "add      func: " <<      add(2,4) << endl;
    cout << "Multiply func: " << Multiply(2,4) << endl;
 }

运行结果:

这里写图片描述


显示加载方式加载dll

显示加载dll需要用到以下几个API函数,其函数声明如下:

//加载dll文件到进行地址空间
HMODULE WINAPI LoadLibrary(
  __in  LPCTSTR lpFileName
);

//获取导出函数的地址
FARPROC WINAPI GetProcAddress(
  __in  HMODULE hModule,
  __in  LPCSTR lpProcName
);

//释放加载的dll文件
BOOL WINAPI FreeLibrary(
  __in  HMODULE hModule
);

//如果有DllMain函数,系统加载dll会调用该函数
BOOL WINAPI DllMain(
  __in  HINSTANCE hinstDLL,
  __in  DWORD fdwReason,
  __in  LPVOID lpvReserved
);

需要注意的是,若dll中的导出函数采用的是标准调用约定时,则访问该dll的客户端程序也应该是采用标准调用约定的导出函数,两者需要一致。

导通加载dll时,客户端程序不再需要包含导出函数声明的头文件和引入库文件,需要的dll文件即可。

对于显示加载和隐式链接加载dll的区别如下:

  1. 隐式链接方式加载dll客户端调用简单、便捷;
  2. 在程序启动时,dll文件已经完成加载,并映射到进程的地址空间,可以随时的调用导出函数,没有调用的顺序要求;
  3. 若应用程序需要加载较多的dll文件,则更适用于显示加载dll方式,可以在需要用到某个dll的某个导出函数时,再加载dll,节约程序的启动时间。

最后,我们想要查看动态链接库导出信息的时,可以使用Dumpbin命令和Depends工具;

原文地址:https://www.cnblogs.com/jinxiang1224/p/8468279.html