C/C++: 在Windows上关于DLL你应当知道的

前几天舍友用 CLion 开发时出现了在 IDE 中能运行,但在 cmd 中出现“缺少 libstdc++-6.dll ”而导致无法运行的的问题。

这几日抽空研究了一下,有了一些收获,写在这里与大家分享。

目录

C/C++: 在Windows上关于DLL你应当知道的目录什么是DLL?为什么我的电脑上有这么多Microsoft Visual C++ 20xx Redistributable (x64/x86)?Windows 系统如何寻找程序对应的DLL?为什么 CLion 开发的 C/C++ 在命令行报错我能构建自己的.dll库吗?准备工作A. 使用 Visual Studio 2017创建一个 DLL 项目添加源代码注意如何使用新生成的DLL?为自己的库准备一个目录(可选)创建“控制台程序”项目更改源码生成项目最后一步B. 使用 cl.exe/g++.execl.exeg++如何查看某个程序依赖什么.dll?

什么是DLL?

在 Windows 中,Dynamic-link Library(DLL)是微软对共享库(Shared Library)的一种实现,作用等同于 Linux/UNIX 中的.so文件。DLL 是一种在程序运行时或运行后才加载的库,因此优势明显:明显减小可执行文件(exe)的体积;多个程序共用同一个DLL可以减少内存占用;更新大型软件时仅需更新部分DLL,不用整个软件重新编译……因此当今的 Windows 基本处处离不开DLL。

即使是不做编程,经常玩单机游戏的你也很有可能遇到过类似“msvc140xx.dll Not Found”的报错信息。这是什么呢?请看下一节:

为什么我的电脑上有这么多Microsoft Visual C++ 20xx Redistributable (x64/x86)?

msvc-redist

如果你喜欢从控制面板卸载软件而不是用傻X的XX软件管家,那你对上图一定很熟悉——大量的Microsoft Visual C++ 20xx Redistributable(x64/x86) 被安装在你的电脑上,而你根本不知道它们是何时被安装的。

首先你要知道什么是Visual C++ Redistributable。众所周知 Visual C++是微软开发的Windows C/C++ IDE(现今是Visual Studio套件里的一个部分)。Visual C++ 包含了大量共享库(比如C++标准库),以DLL的形式供开发者调用。随着 Visual C++/ Visual Studio 的版本迭代,这些共享库也在不断的更新,于是就有了以20xx和小版本号区分的不同版本的VC++ Redistributable。

当开发者使用某一版本的 Visual C++ 开发并发布他们的软件时,有两种选择:一是将外部库与自己的程序静态链接(static link)起来,这样做的好处是,用户能够不依赖外部库就能运行程序,但缺点是最终软件包将会更大;二是选择动态链接那些.dll库。这样,只要用户电脑上有相关DLL,就能运行。因此很多软件为了确保自己能够被运行,就在安装时顺便把自己的那一版的 VC++ Redistributable 也安装了。因此你的电脑上就出现了很多版本的 VC++ Redistributable。

这些分发包都很小,所以不要为了节省空间卸掉了某一个,不然你的其他程序可能就因此跑不起来了。因此有这样一个调侃:

当有人问你“玩游戏缺少msvcrt140xx.dll”该怎么解决时,直接让他装最新版的Visual Studio,选上完整版的Visual C++。

Windows 系统如何寻找程序对应的DLL?

这一节比较重要,需要提前说一下。

TL;DR 一般在可执行文件所在目录、当前工作目录、系统PATH目录中搜索。

DLL在哪儿?系统如何加载它们?其实这个问题要看情况,因为DLL的加载方式分为 Implicit Linking 和 Explicit Linking (Determine which linking method to use)。但对于这两种方式,系统都会从以下路径的顺序来搜索所需DLL,如果找不到,就报错(Search Path Used by Windows to Locate a DLL)

  1. 可执行文件所在目录
  2. 当前工作目录
  3. Windows系统目录,可使用GetSystemDirectory()来获得
  4. Windows目录,可使用GetWindowsDirectory()来获得
  5. PATH环境变量中被列出的目录

为什么 CLion 开发的 C/C++ 在命令行报错

回到最开始的问题——为啥 CLion 开发的程序在 CLion 内可以运行,在系统的外部命令行就会报“缺少 libstdc++-6.dll”的问题呢?

这得从 MinGW(Minimalist GNU for Windows) 说起。众所周知,在 Windows 平台,除了微软自家的 Visual Studio 使用 msvc 编译器(cl.exe),其他 C/C++ 的 IDE 使用的编译器基本都是 MinGW 中的 gcc/g++,CLion 也一样。MinGW 提供了另一套动态链接库(它位于MinGW分发包的bin目录下),与微软提供的完全不是一套东西。如果你在安装 MinGW 时没有把bin目录加入系统的PATH环境变量中,那么在系统命令行中将会找不到对应的.dll,因此要报错。而 CLion 自动将 MinGW 作为运行环境,因此可以找到相关DLL。

解决此问题的方案

  1. 将 MinGW 的bin目录加入到系统环境变量中,这是 MinGW 推荐的做法。
  2. 不使用动态链接,而是为 gcc/g++ 增加编译选项 -static -static-libgcc -static-libstdc++来静态链接。这样出来的可执行文件会更大。

我能构建自己的.dll库吗?

当然可以。以下介绍两种方法:「使用 Visual Studio 图形化 IDE 构建」和「使用命令行编译器(cl.exe/g++.exe)构建」。(此节过长,可以跳过)

准备工作

desktop-development-with-cpp

A. 使用 Visual Studio 2017

创建一个 DLL 项目

直接选择“动态链接库(DLL)”,我们这里建一个"FuckLib"项目。

添加源代码

增加头文件 Fucklib.h,其内容如下:

 

在源文件FuckLib.cpp中,代码为:

 

然后生成项目

OK,你的生成文件夹(比如我这里就是$(ProjectDir)\x64\Debug)里就会有相应的DLL产出了。

1525393442128

注意

  1. 创建项目后确保自己的项目配置里有这个(FUCKLIB_EXPORTS):

preprocessor

如果没有就会失败,说明你的VS2017版本太低,需升级(这是一个bug)。可以自己改过来。

  1. 源文件中stdafx.h要放在最前面,否则在它前面include的库都会被忽略!

如何使用新生成的DLL?

为自己的库准备一个目录(可选)

把刚才生成的 FuckLib.dll FuckLib.lib 以及源码中的FuckLib.h放到一个结构清晰的目录中,如下:

 

这个步骤只是为了以后分发自己的库时更加方便。

创建“控制台程序”项目

改动项目属性,如下:

  1. 附加包含目录

  1. 附加库目录

  1. 附加依赖库

更改源码

WannaFuck.cpp源码改为:

 
生成项目

最后一步

如果你此时运行程序,将会报错:

dll_error

因为系统现在找不到FuckLib.dll在哪里!你需要把FuckLib.dll放入到WannaFuck.exe的同一个目录下,

fyq

然后运行。运行结果:

run_output


B. 使用 cl.exe/g++.exe

对于下面这两份代码:

two

cl.exe

cl /DFUCKLIB_EXPORTS /LD FuckLib.cpp

g++

g++ -shared -DFUCKLIB_EXPORTS -o FuckLibg++.dll FuckLib.cpp

如何查看某个程序依赖什么.dll?

Visual Studio 自带了一个 Dumpbin.exe工具可以用来查看二进制文件的很多信息,其中就包括该程序依赖什么DLL。如果你的电脑安装了 Visual Studio,那么在开始菜单中搜索vs将会出来:

vstools

在此命令提示符下转到自己要查看的exe目录下,输入dumpbin /IMPORTS xxx.exe回车就会显示相关信息,比如

showfucklib

这是我之前编译的一个加载了自制DLL(FuckLib.dll)的程序,其结果就被显示出来了。可以看到这个程序还加载了其他的DLL,比如VCRUNTIME140D.dll


本文完。2018.5.4