当前位置: 首页 > news >正文

利用__declspec(dllexport)和__declspec(dllimport)在Windows平台编写和使用DLL的小例子

文章目录

  • 前言
  • 说明
  • 疑问
  • DLL的编写与使用
    • 测试环境
    • 编写DLL
    • 使用DLL
    • 加载DLL
  • 总结

前言

关于 __declspec(dllexport)__declspec(dllimport) 这两个关键字在上大学期间就没见过几次面,直到毕业后在公司项目的代码中又遇到过几次,每次也是绕着走,生怕和它产生什么联系,只知道它和动态链接库 DLL 有关,但是当前这个项目中几乎没有用到自己写的动态链接库,所以我也就心安理得的躲了它这么久。

最近看一些开源项目的源码时又发现了这两个关键字,此时凭借自己掌握的知识和学习方法再来看这两个关键字,发现也没有什么值得害怕的地方,其实简单来说就是 __declspec(dllexport) 是用来说明指定类和函数需要从 DLL 中导出的,而 __declspec(dllimport) 是用来说明指定的类和函数是从DLL中导入的。

说明

  • __declspec(dllexport)__declspec(dllimport) 只在 Windows 平台才有,用来说明类或函数的导出和导入。
  • 在 Linux 平台上源文件中的所有函数都有一个的visibility属性,默认导出。如果要隐藏所有函数导出,则需要在GCC编译指令中加入 -fvisibility=hidden 参数。
  • 生成 dll 的同时还会生成对应的 lib 文件,一般是一些索引信息,记录了 dll 中函数的入口和位置,这在之前还真的不知道,原来一直以为 lib 只是静态库文件呢。

疑问

  1. 为什么要导入导出,直接把代码拿过来一起编译不好吗?

想要一起编译前提是你得有源代码,如果人家就给你一个动态库或者静态库,你想把源代码放到一起编译的愿望根本实现不了。

  1. 为什么要分为静态库和动态库?搞这么麻烦,还要导入导出。

这具体的就要查查他们两者的优缺点了,每种事务的产生必要有其产生的原因,比如静态库,很可能就是一个程序员今天在A工程写了一个读取文件的类,过一段时间又在B工程写了一个读取文件的类,代码都差不多,不久又在C工程中直接把代码复制过来改一改又写了一份,这时想到干脆了写个“静态库”这种东西吧,相同的代码直接封装到库中,哪个工程需要就直接拿过来编译,也不需要再复制代码了。

又比如动态库,前面的静态库解决了代码重复开发和维护的问题,但是读取文件的静态库中的代码在A、B、C三个工程中都存在一份,导致每个可执行程序都很大,可不可以共用一份呢?结果又发明了动态库,在编译时只指定函数的入口地址,运行时才加载动态库,这样就使得可执行程序体积大大缩小。

以上内容纯粹我个人想像的,真正发明静态库和动态库是由于什么原因,大家可以自行去了解…

  1. 动态库要比静态库好吗?

个人感觉合适的才是最好的,不存在动态库要比静态库好的说法,最起码不是全都好,动态库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小,但是运行时要去加载库会花费一定的时间,执行速度相对会慢一些,总的来说静态库是牺牲了空间换时间,而动态库是牺牲了时间换空间。

  1. .h(头文件) .lib(库文件) .dll(动态链接库文件) 之间的联系和区别

.h 文件是编译时需要的, .lib 是链接时需要的, .dll 是运行时需要的。如果有 .dll 文件,那么 .lib 一般是一些索引信息,记录了 .dll 中函数的入口和位置,.dll 中是函数的具体的执行内容。如果只有 .lib 文件,那么这个 .lib 文件是静态编译出来的,索引和实现都在文件中。

DLL的编写与使用

前面说了这么多,其实就是想带大家先了解一下动态链接库 DLL ,接下来开始编写一个DLL并在另一个工程中使用它,前提是你已经会使用开发工具VS,如果不会先查查教程。

测试环境

  • VS2013随意版(个人感觉这个版本启动能快一点)
  • Win10畅想版(我也不知道啥版本)

编写DLL

编写 DLL 的方法不知一种,这里只简单介绍一种,对于直接写 .def 文件的方法这里不会展开,尽量依靠开发工具一步步向下执行就好,其实当你理解了开发工具的是怎样工作的,一切就没有那么神秘了,有些步骤直接修改配置文件也是可以实现的,只不过开发工具给我们提供了界面,操作起来更加方便了而已,下面我们开始编写:

  1. 打开VS新建项目,选择Win32项目,项目名称GenDLL,解决方案名称DLLExample,点击确定:

dll_1

  1. 直接下一步,应用程序类型选择DLL,点击完成:

dll_2

  1. 项目会自动创建一个GenDLL.cpp文件,我们在手动创建一个GenDLL.h文件,两个文件中编写如下代码:
// GenDLL.h

#ifdef GENDLL_EXPORTS
#define TEST_API __declspec(dllexport)
#else
#define TEST_API __declspec(dllimport)
#endif

TEST_API int add(int a, int b);
// GenDLL.cpp : 定义 DLL 应用程序的导出函数。

#include "stdafx.h"
#include "GenDLL.h"

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

这段代码中有一个 TEST_API 是我在头文件中自定义的,当存在GENDLL_EXPORTS宏时, TEST_API 代表 __declspec(dllexport) 也就是导出函数,当不存在GENDLL_EXPORTS宏时, TEST_API 代表 __declspec(dllimport) 表示导入函数,而 GENDLL_EXPORTS 这个宏是与项目名相关的,自动生成的宏,在 DLL 项目中存在格式为 “大写项目名_EXPORTS”。

也就是说同一个头文件中计算加法的函数 addGenDLL 这个生成 DLL 的项目中表示导出函数,在其他使用这个 DLL 的项目中表示导入函数。

  1. 编译看输出发现有GenDLL.lib和GenDLL.dll两个文件:
1>------ 已启动生成:  项目: GenDLL, 配置: Debug Win32 ------
1>  stdafx.cpp
1>  dllmain.cpp
1>  GenDLL.cpp
1>     正在创建库 c:\users\administrator\documents\visual studio 2013\Projects\DLLExample\Debug\GenDLL.lib
    和对象 c:\users\administrator\documents\visual studio 2013\Projects\DLLExample\Debug\GenDLL.exp
1>  GenDLL.vcxproj -> c:\users\administrator\documents\visual studio 2013\Projects\DLLExample\Debug\GenDLL.dll
========== 生成:  成功 1 个,失败 0 个,最新 0 个,跳过 0 个 ==========

使用DLL

  1. 在DLLExample这个解决方案下添加一个新项目,命名为UseDLL,然后点击确定:

dll_3

  1. 直接下一步,应用程序类型选择“控制台应用程序”,点击完成:

dll_4

  1. 在文件UseDLL.cpp文件中引用之前GenDLL项目的头文件,编写使用 add 函数的代码:
// UseDLL.cpp : 定义控制台应用程序的入口点。

#include "stdafx.h"
#include <iostream>
#include "../GenDLL/GenDLL.h"

int _tmain(int argc, _TCHAR* argv[])
{
    std::cout << "100+1=" << add(100, 1) << std::endl;
    system("pause");
    return 0;
}
  1. 编译代码发现报错,提示有一个无法解析的外部命令:
1>------ 已启动生成:  项目: UseDLL, 配置: Debug Win32 ------
1>  UseDLL.cpp
1>  stdafx.cpp
1>  正在生成代码...
1>UseDLL.obj : error LNK2019: 无法解析的外部符号 "__declspec(dllimport)
    int __cdecl add(int,int)" (__imp_?add@@YAHHH@Z),该符号在函数 _wmain 中被引用
1>c:\users\administrator\documents\visual studio 2013\Projects\DLLExample\Debug\UseDLL.exe :
    fatal error LNK1120: 1 个无法解析的外部命令
========== 生成:  成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ==========

提示这个错误本意就是说链接没有找到函数实现,链接需要什么文件,前面提到需要lib文件,那么我们设置一下,让UseDLL工程能够找到GenDLL.lib文件。

  1. 打开UseDLL工程的属性,在“配置属性->链接器->输入->附加依赖项”中添加GenDLL.lib:

dll_5

  1. 然后在“配置属性->链接器->常规->附加库目录”中添加GenDLL.lib所在路径“…/Debug”即可成功编译:

dll_6

  1. 直接运行就可以看到调用DLL的结果,因为这两个工程在同一解决方案下,所以最终UseDLL.exe和GenDLL.dll在同一目录下,这样不会报找不到DLL的错误

dll_7

  1. 如果是不同的目录就会像下图那样,提示找不到GenDLL.dll,只要把GenDLL.dll复制到和UseDLL.exe相同目录即可:

dll_8

加载DLL

上面提到当运行程序找不到 DLL时可以把 DLL 放到可执行程序程序的目录,有时运行大型软件找不到 DLL 时,我们也会下载一个放到System32目录,其实程序在加载 DLL 的时候是会按照一定顺序的,这些目录包括:包含exe文件的目录、进程的当前工作目录、Windows系统目录、Windows目录、Path环境变量中的一系列目录等等,这些目录的搜索顺序还会受到安全 DLL 搜索模式是否启用的影响。

所以说如果不是对DLL 放置的位置有特殊要求,那么直接放在exe文件所在的目录就好了,一般也是会优先搜索的。

总结

  • Windows上才有 __declspec(dllexport)__declspec(dllimport)
  • .h 文件是编译时需要的, .lib 是链接时需要的, .dll 是运行时需要的
  • 程序运行时加载 DLL 一般优先从exe文件的所在目录优先加载

相关文章:

  • 挥一挥衣袖,开始一段新的旅程
  • git branch常用分支操作
  • git checkout/git reset/git revert/git restore常用回退操作
  • 没想到C++中的std::remove_if()函数历史还挺悠久
  • git stash帮你在切换分支前暂存不想提交的修改
  • Win10通过带命令行的安全模式清除顽固的广告弹窗文件
  • C++11中的时间库std::chrono(引发关于时间的思考)
  • .bat批处理(九):替换带有等号=的字符串的子串
  • 简单聊聊C/C++中的左值和右值
  • C++11在左值引用的基础上增加右值引用
  • 汇编指令入门级整理
  • 使用c++filt命令还原C++编译后的函数名
  • 配置Beyond Compare 4作为git mergetool来解决git merge命令导致的文件冲突
  • git在回退版本时HEAD~和HEAD^的作用和区别
  • 对称加密、非对称加密、公钥、私钥究竟是个啥?
  • [nginx文档翻译系列] 控制nginx
  • 【个人向】《HTTP图解》阅后小结
  • js 实现textarea输入字数提示
  • leetcode46 Permutation 排列组合
  • Mac转Windows的拯救指南
  • Material Design
  • mongo索引构建
  • 从零开始的webpack生活-0x009:FilesLoader装载文件
  • 我这样减少了26.5M Java内存!
  • 小程序01:wepy框架整合iview webapp UI
  • Prometheus VS InfluxDB
  • Spring Batch JSON 支持
  • ​​​​​​​ubuntu16.04 fastreid训练过程
  • ​一文看懂数据清洗:缺失值、异常值和重复值的处理
  • ​云纳万物 · 数皆有言|2021 七牛云战略发布会启幕,邀您赴约
  • #pragma预处理命令
  • #我与Java虚拟机的故事#连载11: JVM学习之路
  • #中国IT界的第一本漂流日记 传递IT正能量# 【分享得“IT漂友”勋章】
  • (11)工业界推荐系统-小红书推荐场景及内部实践【粗排三塔模型】
  • (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)
  • (android 地图实战开发)3 在地图上显示当前位置和自定义银行位置
  • (webRTC、RecordRTC):navigator.mediaDevices undefined
  • (windows2012共享文件夹和防火墙设置
  • (附源码)spring boot公选课在线选课系统 毕业设计 142011
  • (附源码)ssm基于微信小程序的疫苗管理系统 毕业设计 092354
  • (十六)串口UART
  • (学习日记)2024.02.29:UCOSIII第二节
  • (转)原始图像数据和PDF中的图像数据
  • ******之网络***——物理***
  • .MyFile@waifu.club.wis.mkp勒索病毒数据怎么处理|数据解密恢复
  • .NET/MSBuild 中的发布路径在哪里呢?如何在扩展编译的时候修改发布路径中的文件呢?
  • .NET是什么
  • @DataRedisTest测试redis从未如此丝滑
  • @拔赤:Web前端开发十日谈
  • [ Algorithm ] N次方算法 N Square 动态规划解决
  • [ Linux ] Linux信号概述 信号的产生
  • [ NOI 2001 ] 食物链
  • []指针
  • [2]十道算法题【Java实现】
  • [20190416]完善shared latch测试脚本2.txt