动态链接库(DLL)是从C语言函数库和Pascal库单元的概念发展而来的。所有的C语言标准库函数都存放在某一函数库中。在链接应用程序的过程中,链接器从库文件中拷贝程序调用的函数代码,并把这些函数代码添加到可执行文件中。这种方法同只把函数储存在已编译的OBJ文件中相比更有利于代码的重用。但随着Windows这样的多任务环境的出现,函数库的方法显得过于累赘。如果为了完成屏幕输出、消息处理、内存管理、对话框等操作,每个程序都不得不拥有自己的函数,那么Windows程序将变得非常庞大。Windows的发展要求允许同时运行的几个程序共享一组函数的单一拷贝。动态链接库就是在这种情况下出现的。动态链接库不用重复编译或链接,一旦装入内存,DLL函数可以被系统中的任何正在运行的应用程序软件所使用,而不必再将DLL函数的另一拷贝装入内存。

下面我们一步一步来建立一个DLL。

一、建立一个DLL工程
新建一个工程,选择Win32 控制台项目(Win32 Console Application),并且在应用程序设置标签(the advanced tab)上,选择DLL和空项目选项。

二、声明导出函数
这里有两种方法声明导出函数:一种是通过使用__declspec(dllexport),添加到需要导出的函数前,进行声明;另外一种就是通过模块定义文件(Module-Definition File即.DEF)来进行声明。
第一种方法,建立头文件DLLSample.h,在头文件中,对需要导出的函数进行声明。

#ifndef _DLL_SAMPLE_H
#define _DLL_SAMPLE_H

// 如果定义了C++编译器,那么声明为C链接方式
#ifdef __cplusplus
extern "C" {
#endif
 
// 通过宏来控制是导入还是导出
 #ifdef _DLL_SAMPLE
#define DLL_SAMPLE_API __declspec(dllexport)
 #else
 #define DLL_SAMPLE_API __declspec(dllimport)
 #endif
 
// 导出/导入函数声明
 DLL_SAMPLE_API void TestDLL(int);

#undef DLL_SAMPLE_API

#ifdef __cplusplus
 }
#endif

#endif

这个头文件会分别被DLL和调用DLL的应用程序引入,当被DLL引入时,在DLL中定义_DLL_SAMPLE宏,这样就会在DLL模块中声明函数为导出函数;当被调用DLL的应用程序引入时,就没有定义_DLL_SAMPLE,这样就会声明头文件中的函数为从DLL中的导入函数。

第二种方法:模块定义文件是一个有着.def文件扩展名的文本文件。它被用于导出一个DLL的函数,和__declspec(dllexport)很相似,但是.def文件并不是Microsoft定义的。一个.def文件中只有两个必需的部分:LIBRARY 和 EXPORTS。

LIBRARY DLLSample
DESCRIPTION "my simple DLL"
EXPORTS
 TestDLL @1 ;@1表示这是第一个导出函数

第一行,''LIBRARY''是一个必需的部分。它告诉链接器(linker)如何命名你的DLL。下面被标识为''DESCRIPTION''的部分并不是必需的。该语句将字符串写入 .rdata 节,它告诉人们谁可能使用这个DLL,这个DLL做什么或它为了什么(存在)。再下面的部分标识为''EXPORTS''是另一个必需的部分;这个部分使得该函数可以被其它应用程序访问到并且它创建一个导入库。当你生成这个项目时,不仅是一个.dll文件被创建,而且一个文件扩展名为.lib的导出库也被创建了。除了前面的部分以外,这里还有其它四个部分标识为:NAME, STACKSIZE, SECTIONS, 和 VERSION。另外,一个分号(;)开始一个注解,如同''//''在C++中一样。定义了这个文件之后,头文件中的__declspec(dllexport)就不需要声明了。

三、编写DllMain函数和导出函数
DllMain函数是DLL模块的默认入口点。当Windows加载DLL模块时调用这一函数。系统首先调用全局对象的构造函数,然后调用全局函数DLLMain。DLLMain函数不仅在将DLL链接加载到进程时被调用,在DLL模块与进程分离时(以及其它时候)也被调用。

#include "stdafx.h"
#define _DLL_SAMPLE

#ifndef _DLL_SAMPLE_H
#include "DLLSample.h"
#endif

#include "stdio.h"

//APIENTRY声明DLL函数入口点
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
 switch (ul_reason_for_call)
 {
  case DLL_PROCESS_ATTACH:
  case DLL_THREAD_ATTACH:
  case DLL_THREAD_DETACH:
  case DLL_PROCESS_DETACH:
   break;
  }
 return TRUE;
 }

void TestDLL(int arg)
{
 printf("DLL output arg %d/n", arg);
 }

如果程序员没有为DLL模块编写一个DLLMain函数,系统会从其它运行库中引入一个不做任何操作的缺省DLLMain函数版本。在单个线程启动和终止时,DLLMain函数也被调用。
然后,F7编译,就得到一个DLL了。

变量导出类似。

从 DLL 导出

.DLL 文件的布局与 .exe 文件非常相似,但有一个重要的差异:DLL 文件包含导出表。导出表包含 DLL 导出到其他可执行文件的每个函数的名称。这些函数是 DLL 中的入口点;只有导出表中的函数可由其他可执行文件访问。DLL 中的任何其他函数都是 DLL 私有的。通过使用带 /EXPORTS 选项的Dumpbin 工具,可以查看 DLL 的导出表。

有两种从 DLL 导出函数的方法:

  • 在生成 DLL 时,创建一个模块定义 (.def) 文件并使用该 .def 文件。如果希望按序号而不是按名称从 DLL 导出函数,则请使用此方法。

  • 在函数的定义中使用 __declspec(dllexport) 关键字。

用上述任何方法导出函数时,确保使用 __stdcall 调用约定。

使用 DEF 文件从 DLL 导出

模块定义 (.def) 文件是包含一个或多个描述 DLL 各种属性的 Module 语句的文本文件。如果不使用__declspec(dllexport) 关键字导出 DLL 的函数,则 DLL 需要 .def 文件。

.def 文件必须至少包含下列模块定义语句:

  • 文件中的第一个语句必须是 LIBRARY 语句。此语句将 .def 文件标识为属于 DLL。LIBRARY 语句的后面是 DLL 的名称。链接器将此名称放到 DLL 的导入库中。

  • EXPORTS 语句列出名称,可能的话还会列出 DLL 导出函数的序号值。通过在函数名的后面加上 @ 符和一个数字,给函数分配序号值。当指定序号值时,序号值的范围必须是从 1 到 N,其中 N 是 DLL 导出函数的个数。如果希望按序号导出函数,请参见按序号而不是按名称从 DLL 导出函数以及本主题。

例如,包含实现二进制搜索树的代码的 DLL 看上去可能像下面这样:

 复制代码
LIBRARY   BTREE
EXPORTSInsert   @1Delete   @2Member   @3Min   @4

如果使用 MFC DLL 向导创建 MFC DLL,则向导将为您创建主干 .def 文件并将其自动添加到项目中。添加要导出到此文件的函数名。对于非 MFC DLL,必须亲自创建 .def 文件并将其添加到项目中。

如果导出 C++ 文件中的函数,必须将修饰名放到 .def 文件中,或者通过使用外部“C”定义具有标准 C 链接的导出函数。如果需要将修饰名放到 .def 文件中,则可以通过使用 DUMPBIN 工具或 /MAP 链接器选项来获取修饰名。请注意,编译器产生的修饰名是编译器特定的。如果将 Visual C++ 编译器产生的修饰名放到 .def 文件中,则链接到 DLL 的应用程序必须也是用相同版本的 Visual C++ 生成的,这样调用应用程序中的修饰名才能与 DLL 的 .def 文件中的导出名相匹配。

如果生成扩展 DLL 并使用 .def 文件导出,则将下列代码放在包含导出类的头文件的开头和结尾:

 复制代码
#undef AFX_DATA
#define AFX_DATA AFX_EXT_DATA
// <body of your header file>
#undef AFX_DATA
#define AFX_DATA

这些代码行确保内部使用的 MFC 变量或添加到类的变量是从扩展 DLL 导出(或导入)的。例如,当使用 DECLARE_DYNAMIC 派生类时,该宏扩展以将 CRuntimeClass 成员变量添加到类。省去这四行代码可能会导致不能正确编译或链接 DLL,或在客户端应用程序链接到 DLL 时导致错误。

当生成 DLL 时,链接器使用 .def 文件创建导出 (.exp) 文件和导入库 (.lib) 文件。然后,链接器使用导出文件生成 DLL 文件。隐式链接到 DLL 的可执行文件在生成时链接到导入库。

请注意,MFC 本身使用 .def 文件从 MFCx0.dll 导出函数和类。

使用 __declspec(dllexport) 从 DLL 导出

Microsoft 在 Visual C++ 的 16 位编译器版本中引入了 __export,使编译器得以自动生成导出名并将它们放到一个 .lib 文件中。然后,此 .lib 文件就可以像静态 .lib 那样用于与 DLL 链接。

在更新的编译器版本中,可以使用 __declspec(dllexport) 关键字从 DLL 导出数据、函数、类或类成员函数。__declspec(dllexport) 会将导出指令添加到对象文件中,因此您不需要使用 .def 文件。

当试图导出 C++ 修饰函数名时,这种便利最明显。由于对名称修饰没有标准规范,因此导出函数的名称在不同的编译器版本中可能有所变化。如果使用 __declspec(dllexport),仅当解决任何命名约定更改时才必须重新编译 DLL 和依赖 .exe 文件。

许多导出指令(如序号、NONAME 和 PRIVATE)只能在 .def 文件中创建,并且必须使用 .def 文件来指定这些属性。不过,在 .def 文件的基础上另外使用 __declspec(dllexport) 不会导致生成错误。

若要导出函数,__declspec(dllexport) 关键字必须出现在调用约定关键字的左边(如果指定了关键字)。例如:

 复制代码
__declspec(dllexport) void __cdecl Function1(void);

若要导出类中的所有公共数据成员和成员函数,关键字必须出现在类名的左边,如下所示:

 复制代码
class __declspec(dllexport) CExampleExport : public CObject
{ ... class definition ... };

生成 DLL 时,通常创建一个包含正在导出的函数原型和/或类的头文件,并将 __declspec(dllexport) 添加到头文件中的声明中。若要提高代码的可读性,请为 __declspec(dllexport) 定义一个宏并对正在导出的每个符号使用该宏:

 复制代码
#define DllExport   __declspec( dllexport ) 

__declspec(dllexport) 将函数名存储在 DLL 的导出表中。如果希望优化表的大小,请参见按序号而不是按名称从 DLL 导出函数。

注意

将 DLL 源代码从 Win16 移植到 Win32 时,请用 __declspec(dllexport) 替换 __export 的每个实例。

转载于:https://www.cnblogs.com/yangxx-1990/p/4847459.html

DLL 的导入与导出相关推荐

  1. R 数据的导入和导出

    2019独角兽企业重金招聘Python工程师标准>>> R 数据的导入和导出 这是从R中导入或导出数据的一个指导手册. 本文档的当前版本为0.01 β.该文档译自 R-2.6.1 文 ...

  2. C++中的库文件导入与导出

    前言 C++的库文件分为两种:lib文件和dll文件,前者是静态的,会在build时就被打包到exe内,单独的一个exe文件就可以运行,而后者是动态的,不会被打包到exe内,除了exe,还需要对应的d ...

  3. C# ASP.NET MVC模式 WPS的导入与导出的实现

    前提准备: 1.想要成功进行WPS的导入与导出,你得先下载WPS,然后找到etapi.dll文件(路径:\Kingsoft\WPS Office\10.1.0.7520\office6),WPS安装路 ...

  4. npoi把xlsx文件转为html,C# NPOI 导入与导出Excel文档 兼容xlsx, xls(xf13中已经引用了xlsx的npoi)...

    这里使用的NPOI版本为: 2.1.3.1 版本内包含.Net 2.0 与.Net 4.0 .Net 4.0中包含文件 使用时需引用需要引用所有5个dll 使用到的引用 using NPOI.HSSF ...

  5. Docker 入门系列(3)- Docker 容器(创建、启动、终止、进入、删除、导入、导出容器、容器和镜像转化)

    Docker 容器 简单来说,容器是镜像的一个运行实例.所不同的是,镜像是静态的只读文件,而容器带有运行时需要的可写文件层. 如果认为虚拟机是模拟运行的一整套操作系统(包括内核.应用运行态环境和其他系 ...

  6. 基于Metronic的Bootstrap开发框架经验总结(7)--数据的导入、导出及附件的查看处理...

    在很多系统模块里面,我们可能都需要进行一定的数据交换处理,也就是数据的导入或者导出操作,这样的批量处理能给系统用户更好的操作体验,也提高了用户录入数据的效率.我在较早时期的EasyUI的Web框架上, ...

  7. MySQL基础day03_数据的导入、导出-MySQL 5.6

    MySQL基础day03_数据的导入.导出-MySQL 5.6 注:把数据按照一定格式存放到文件里才能进行数据的导入. 1,数据导入的条件 把文件里的内容保存到数据的表里: 把数据按照一定格式存放文件 ...

  8. MySql的导入与导出

    1.导入 load data infile '/tmp/yhb/skin_info.txt' into table t_skin fields terminated by '\t' (skin_id, ...

  9. mysql 数据库的导入和导出

    mysql 数据库的导入和导出 视频 https://www.bilibili.com/video/BV1tV411o7zv?from=search&seid=2492452830997848 ...

最新文章

  1. mysql会话级表_php – MySQL会话表方法
  2. 删除本地文件后 Git pull从远程仓库重新获取不到解决办法
  3. 通过tomcat实现多域名配置
  4. 实现远程调用_微服务的那些事(三),微服务的远程调用方式。RPC和HTTP
  5. db2 修改表空间自增长_db2表空间及日志文件调整
  6. [Buzz.Today]2013.03.28
  7. 玻璃体定点注入(个人猜想)
  8. 数据结构——图-有向图和无向图的邻接表基础
  9. 操作系统实验一 进程管理
  10. 【机器学习】网络表征学习、网络嵌入必读论文
  11. SSL 3.0 Poodle漏洞修复方法
  12. 【论文阅读】LOKI-Practical Data Poisoning Attack against Next-Item Recommendation
  13. 关于uni-app入门看完这篇就够了
  14. Latex-条目、编号、描述
  15. PHP脚本定时任务实现及crontab实现定时任务
  16. oracle冲账语句_ORA-00xx问题 -oracle卸载不成功
  17. Linux学习(Kali为蓝本)
  18. avg制作工具 开源_23种开源视听制作工具
  19. Android总笔记(未全)
  20. Android滑动头部控件

热门文章

  1. 关闭数据执行保护(DEP)
  2. Pixhawk代码分析-启动代码及入口函数
  3. LeetCode每日一题 52. N皇后 II
  4. mysql 特殊函数_MySQL中sleep函数的特殊现象示例详解
  5. pb,json,二进制,xml数据对比
  6. 思科模拟器,计算机网络实验三之:静态路由配置
  7. [转] CMake入门
  8. svn 分支上新增文件合并发生冲突_SVN的使用、分支合并及解决冲突详解
  9. 5.5.3 per-connection time zone support
  10. 微信 SQLite 数据库修复实践