• 程序编译过程
  • 库文件
  • 静态链接和动态链接的区别?
  • 从0开始 - 创建和使用静态链接库
  • 创建静态库项目
  • 向静态库中添加文件
  • 编译静态库
  • 创建引用静态库的C++控制台应用
  • 在应用中使用静态库功能
  • 从0开始 - 创建和使用 DLL
  • 创建 DLL 项目
  • 向 DLL 中添加文件
  • 编译动态库
  • 创造使用 DLL 的客户端应用
  • 隐式调用
  • 显式调用
  • 演练
  • 创建 DLL 项目
  • 向 DLL 中添加文件
  • 创建使用 DLL 的客户端应用
  • 将 DLL 标头添加到包含路径
  • 将 DLL 导入库添加到项目中

(这个目录来的,别点了,还是这页)

笔记不写容易忘,谁脑袋记得住这么多东西(秃)。

事情是这样的,上学期老师给的一个阅读代码的任务,但由于我常年身处文化沙漠死活读不懂。一学期过去了,懒癌终于拿起花瓶浇自己了。

今天的笔记内容说的是平时经常能看见的,运行 VS 项目的时候老在下方加载的 .dll 。包括一小部分的理论和超大部分的实操。


什么是动态链接库(DLL)?

解构一下,”动态“、”链接“、”库“。

  • 动态:与静态相对,对比理解。
  • 链接:程序编译过程的一步。
  • 库:一种代码仓库。

程序编译过程

  • 编译:把文本形式的源代码翻译成机器语言,并形成目标文件。

    • 预处理:处理 # 开头的的指令。(.cpp .i)
    • 编译优化:确定指令是否符合规则,之后翻译成汇编代码。(.i .s)
    • 汇编:把汇编语言翻译成目标机器指令,生成目标文件。(.s .o)

链接:把目标文件、操作系统的启动代码和库文件组织起来形成可执行程序。(.o .exe )

库文件

可以简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接拿来用的变量、函数或类。

库文件分为静态库和动态库。

静态链接和动态链接的区别?

预编译 -> 编译 -> 汇编 -> ==链接==

区别在于链接阶段如何处理库。

静态库(.lib):链接阶段将汇编生成的目标文件.o和引用的库一起链接打包到可执行文件.exe 中。

特点:移植方便、浪费空间和资源。

动态库(.dll):程序运行时载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。

特点:资源共享、模块化、简化部署和安装。


C++ 操作实例

  • Windows10、x64
  • VS2019、C++ 的桌面开发

接下来介绍如何创建和使用动态链接库:

  • 创建和使用静态链接库
  • 创建和使用动态链接库
    • 隐式调用
    • 显式调用
      • C语言模块
      • 模块定义文件

为了便于学习,简化理解,先 从0开始 创建静态库和动态库,然后使用应用程序来链接库。最后通过演练应用来实操 DLL 。

接下来的实例可以分解为以下任务:

  • 创建 DLL 项目。

    • 声明和实现函数,导出函数到项目。
  • 创建控制台应用项目。
    • 从 DLL 导入函数。
  • 运行应用。

从0开始 - 创建和使用静态链接库

使用静态库是重用代码的一种绝佳方式。

不必在每个应用中重新实现同一例程,只需将其写入静态数据库一次,然后引用它们即可。 从静态库链接的代码成为了应用的一部分,这样就不必安装另一个文件来使用代码。

接下来从空项目开始创建静态库。

创建静态库项目

将“语言” 设置为“C++” ,将“平台” 设置为“Windows” ,并将“项目类型” 设置为“库”。选择“Windows 桌面向导”。

静态库、空项目。

向静态库中添加文件

新建头文件、源文件。

testlib.h 中添加声明:

#ifndef TESTLIB_H#define TESTLIB_H// 防止头文件重复包含// 条件编译指令// 加减乘除int add(int a, int b);  // 声明#elseint a = 0;// 满足条件才会编译,所以这里代码没有被高亮。#endif

testlib.cpp 中实现功能:

#include "testlib.h"// 加法
int add(int a, int b)  // 实现
{return a + b;
}

编译静态库

在菜单栏上依次选择“生成” > “生成解决方案” ,将创建一个可供其他程序使用的静态库 testLib.lib

查看项目路径可以找到。

注:.lib 文件不能独立运行。

至此,静态库已经创建完毕!

创建引用静态库的C++控制台应用

在“解决方案资源管理器”中,右键单击顶部节点“解决方案”,打开快捷菜单 。 选择“添加” > “新建项目”,打开“添加新项目”对话框 。

控制台、空项目。

在应用中使用静态库功能

必须引用静态库才能使用其中的算术例程。 打开“解决方案资源管理器”中 test项目的快捷菜单,然后选择“添加” > “引用” 。然后选中之前编写的.lib 文件。

若要引用 testlib.h 头文件,需要修改引用路径。

在“解决方案资源管理器”中,右键单击 test ,选择“属性” 。

将“配置”下拉列表设置为“所有配置” 。 将“平台”下拉列表设置为“所有平台” 。

”链接器 - 常规 - 附加库目录“ 中指定存放 .lib 的文件夹路径,推荐使用相对路径,这样迁移的时候不容易出错。

test.cpp 中调用:

#include <stdio.h>
#include <iostream>
#include "../testLib/testlib.h"  // 引用路径using namespace std;int main()
{cout << "input 2 integer:";int a, b;cin >> a >> b;// 调用静态库printf("%d + %d = %d", a, b, add(a, b));return 0;
}

最后运行一下就行!

至此,静态库调用完成!


从0开始 - 创建和使用 DLL

与静态链接库不同,Windows 在加载时或在运行时将应用中的导入连接到 DLL 中的导出,而不是在链接时连接它们。

Windows 需要不属于标准 C++ 编译模型的额外信息才能建立这些连接。 MSVC 编译器实现了一些 Microsoft 专用 C++ 扩展,以提供此额外信息。接下来我们将介绍这些扩展。

DLL 使用 C 调用约定。 只要平台、调用约定和链接约定匹配,便可从采用其他编程语言编写的应用中进行调用。客户端应用使用隐式链接,其中 Windows 在加载时将应用链接到 DLL。此链接允许应用调用 DLL 提供的函数,就像调用静态链接库中的函数一样。

创建 DLL 项目

新建动态库、空项目(操作可参考 创建静态库项目 )

向 DLL 中添加文件

然后添加头文件、源文件:

testdll.h 中添加声明:

#ifndef TESTDLL_H#define TESTDLL_H// 条件编译指令// 在预处理器里事先定义好_DLLAPI,保证dll项目有预定义;// 而新程序项目里没有,从而区分导入和导出。#ifdef _DLLAPI#define DLLAPI __declspec(dllexport)  // 导出#else#define DLLAPI __declspec(dllimport)  // 导入#endif// 声明导出函数DLLAPI int add(int a, int b);  // 导出add接口#endif

并且在预处理器里进行定义:

testdll.cpp 中实现功能:

#include "testdll.h"int add(int a, int b)  // 函数实现
{return a + b;
}

编译动态库

在菜单栏上依次选择“生成” > “生成解决方案” ,将创建 .dll 和相关编译器输出。

查看输出:

至此,动态链接库已经创造完成!

创造使用 DLL 的客户端应用

创建新项目:控制台、.exe(操作参考 创建引用静态库的C++控制台应用 )

添加源文件

使用 DLL 需要:查找声明 DLL 导出的标头、链接器的导入库和 DLL 本身。

方法

  1. 建议在客户端项目中设置包含路径,使其直接包括 DLL 项目中的 DLL 头文件。
  2. 在客户端项目中设置库路径以包括 DLL 项目中的 DLL 导入库。
  3. 将生成的 DLL 从 DLL 项目复制到客户端生成输出目录中。

可执行文件可以通过以下两种方法链接到DLL:

  • 隐式链接

操作系统会与使用 DLL 的可执行文件同时加载它。

客户端可执行文件调用 DLL 的导出函数的方式与函数进行静态链接并包含在可执行文件中时的方式相同。

隐式链接有时称为静态加载 或加载时动态链接 。

  • 显式链接

操作系统会在运行时按需加载 DLL。

通过显式链接使用 DLL 的可执行文件必须显式加载和卸载 DLL。它还必须设置函数指针,用于访问它从 DLL 使用的每个函数。

显式链接有时称为动态加载 或运行时动态链接 。

隐式调用

跟静态库调用类似。

右键 test 项目,属性 - “链接器” - “常规” - 附加库目录,编辑添加 .lib 文件存放路径。

test.cpp 文件里写入:

#include <iostream>
#include "../testDll/testdll.h"
#pragma comment(lib, "testDll.lib")  // 隐式调用,类似静态库但不同using namespace std;int main()
{int a, b;cout << "input 2 integer:";cin >> a >> b;// 调用printf("%d + %d = %d", a, b, add(a, b));return 0;
}

最后运行一下就行!

至此,隐式调用完成!

显式调用

应用程序必须在运行时进行函数调用以显式加载 DLL。

test.cpp 文件中手动加载动态库。在需要使用的地方加载,使用完后释放。

#include <iostream>
#include "../testDll/testdll.h"
#include <Windows.h>  // LoadLibrary函数使用using namespace std;typedef int (*PADD)(int a, int b);  // 定义所调用的导出函数的调用指针int main()
{// 显式调用// 加载 DLL 文件,获取模块句柄。HMODULE hDLL = LoadLibrary(L"testDll.dll");if (hDLL == NULL){cout << "加载 DLL 失败!n";return 0;}int a, b;cout << "input 2 integer:";cin >> a >> b;PADD pAdd = (PADD)GetProcAddress(hDLL, "add");  // 获取函数指针// 显性调用,在需要的时候使用。printf("%d + %d = %d", a, b, pAdd(a, b));FreeLibrary(hDLL);  // 使用完后释放。return 0;
}

注意:调用 GetProcAddress 以获取指向名为“DLLFunc1”的函数的指针,调用该函数并保存结果。

此处如果按照之前的 testdll.h 内的声明,则会改变引用的函数名,导致 PADD pAdd = (PADD)GetProcAddress(hDLL, "add"); 返回函数值时候引用函数名 “add” 出错。

所以需要修改头文件的声明方式,让编译器使用C语言编译,保留函数名。

导出 C++ 函数以用于 C 语言可执行文件

如果要从 C 语言模块访问用 C++ 编写的 DLL 中的函数,则应使用 C 链接(而不是 C++ 链接)声明这些函数。增加 extern "C"

修改 testdll.h 当中的声明:

#ifndef TESTDLL_H#define TESTDLL_H// 条件编译指令// 在预处理器里事先定义好_DLLAPI,保证dll项目有预定义;// 而新程序项目里没有,从而区分导入和导出。#ifdef _DLLAPI#define DLLAPI __declspec(dllexport)  // 导出#else#define DLLAPI __declspec(dllimport)  // 导入#endif// 声明导出函数extern "C" DLLAPI int add(int a, int b);  // 导出add接口// 使用C链接声明函数,导出时不会改变函数名。// 使用C语言的方式进行编译。#endif

运行一下

至此,显性调用完成!

使用模块定义文件调用

如果不想使用 导出 C++ 函数以用于 C 语言可执行文件 的方式还原函数名,还可以另外添加一个 .def (模块定义)文件。

将头文件 testdll.h 修改为:

#ifndef TESTDLL_H#define TESTDLL_Hint add(int a, int b);#endif

添加一个模块定义文件:

Source.def 里写入:

LIBRARY testDll
EXPORT
add

注意:这里编译出错了,似乎是指针找不到函数名。上网查询之后,可能是函数名已经被使用过了,所以换一个名字,并且加上 __stdcall 调用约定。

提示:就是把所有的 add 改名并且加上约定。

将头文件 testdll.h 修改为:

#ifndef TESTDLL_H#define TESTDLL_Hint __stdcall Add(int a, int b);#endif

testdll.cpp 修改为:

#include "testdll.h"int __stdcall Add(int a, int b)  // 函数实现
{return a + b;
}

test.cpp 修改为:

#include <iostream>
#include "../testDll/testdll.h"
#include <Windows.h>  // LoadLibrary函数使用using namespace std;typedef int (__stdcall *PADD)(int a, int b);  // 定义所调用的导出函数的调用签名int main()
{// 显式调用// 加载 DLL 文件,获取模块句柄。HMODULE hDLL = LoadLibrary(L"testDll.dll");if (hDLL == NULL){cout << "加载 DLL 失败!n";return 0;}int a, b;cout << "input 2 integer:";cin >> a >> b;PADD pAdd = (PADD)GetProcAddress(hDLL, "Add");  // 获取函数指针// 显性调用,在需要的时候使用。printf("%d + %d = %d", a, b, pAdd(a, b));FreeLibrary(hDLL);  // 使用完后释放。return 0;
}

Source.def 里写入:

LIBRARY testdll
EXPORTS
Add

运行一下:

至此,.def 文件调用完成!


演练

上面介绍的方法都是从0开始创建并使用的,但我面对的项目不是从0开始的,多了很多初始化文件和模块。这里加一个演练版本的动态库操作实例。

创建 DLL 项目

创建新 DLL 项目。

起名,取消勾选“将解决方案和项目放在同一目录下”,这样创建的文件夹会整齐一点,这一步对结果没有任何影响。

创建完后初始化界面如下,帮我们初始了一些文件。

向 DLL 中添加文件

添加头文件和源文件,起名如下:

接下来在头文件 MathLibrary.h 中添加代码:

// MathLibrary.h - Contains declarations of math functions
#pragma once#ifdef MATHLIBRARY_EXPORTS
#define MATHLIBRARY_API __declspec(dllexport)
#else
#define MATHLIBRARY_API __declspec(dllimport)
#endif// The Fibonacci recurrence relation describes a sequence F
// where F(n) is { n = 0, a
//               { n = 1, b
//               { n > 1, F(n-2) + F(n-1)
// for some initial integral values a and b.
// If the sequence is initialized F(0) = 1, F(1) = 1,
// then this relation produces the well-known Fibonacci
// sequence: 1, 1, 2, 3, 5, 8, 13, 21, 34, ...// Initialize a Fibonacci relation sequence
// such that F(0) = a, F(1) = b.
// This function must be called before any other function.
extern "C" MATHLIBRARY_API void fibonacci_init(const unsigned long long a, const unsigned long long b);// Produce the next value in the sequence.
// Returns true on success and updates current value and index;
// false on overflow, leaves current value and index unchanged.
extern "C" MATHLIBRARY_API bool fibonacci_next();// Get the current value in the sequence.
extern "C" MATHLIBRARY_API unsigned long long fibonacci_current();// Get the position of the current value in the sequence.
extern "C" MATHLIBRARY_API unsigned fibonacci_index();

注意:

头文件用于声明函数。

预处理语句用于定义接口,查看 “属性 - 预处理器 - 预处理定义“ 会发现新建项目时已经帮忙定义了。原因如 向 DLL 中添加文件 里 testdll.h 代码处注释所说。

向源文件 MathLibrary.cpp 里写入代码:

// MathLibrary.cpp : Defines the exported functions for the DLL.
#include "pch.h" // use stdafx.h in Visual Studio 2017 and earlier
#include <utility>
#include <limits.h>
#include "MathLibrary.h"// DLL internal state variables:
static unsigned long long previous_;  // Previous value, if any
static unsigned long long current_;   // Current sequence value
static unsigned index_;               // Current seq. position// Initialize a Fibonacci relation sequence
// such that F(0) = a, F(1) = b.
// This function must be called before any other function.
void fibonacci_init(const unsigned long long a,const unsigned long long b)
{index_ = 0;current_ = a;previous_ = b; // see special case when initialized
}// Produce the next value in the sequence.
// Returns true on success, false on overflow.
bool fibonacci_next()
{// check to see if we'd overflow result or positionif ((ULLONG_MAX - previous_ < current_) ||(UINT_MAX == index_)){return false;}// Special case when index == 0, just return b valueif (index_ > 0){// otherwise, calculate next sequence valueprevious_ += current_;}std::swap(current_, previous_);++index_;return true;
}// Get the current value in the sequence.
unsigned long long fibonacci_current()
{return current_;
}// Get the current index position in the sequence.
unsigned fibonacci_index()
{return index_;
}

注意:该文件用于实现函数。

到这里可以编译 DLL 查看是否能成功生成。”生成 - 生成解决方案“。如果出现如下输出,则说明一切正常。

至此,已经成功创建了一个 DLL !

创建使用 DLL 的客户端应用

解决方案上右键,新添加控制台项目。

添加一个源文件。

MathClient.cpp 里添加代码:

// MathClient.cpp : Client app for MathLibrary DLL.
// #include "pch.h" Uncomment for Visual Studio 2017 and earlier
#include <iostream>
#include "MathLibrary.h"int main()
{// Initialize a Fibonacci relation sequence.fibonacci_init(1, 1);// Write out the sequence values until overflow.do {std::cout << fibonacci_index() << ": "<< fibonacci_current() << std::endl;} while (fibonacci_next());// Report count of values written before overflow.std::cout << fibonacci_index() + 1 <<" Fibonacci sequence values fit in an " <<"unsigned 64-bit integer." << std::endl;
}

注意:此时还不能直接运行,因为缺少一些配置

首先是 MathLibrary.h 头文件无法找到,也就是说此时项目没有包括头文件。

将 DLL 标头添加到包含路径

属性页面。所有配置。“C/C++” > “常规” 。将包含 MathLibrary.h 头文件的路径写入。

注意:此时代码可以编译,但还无法链接。

需要告诉链接器如何找到 .lib 文件。

将 DLL 导入库添加到项目中

属性。所有配置。 “链接器” > “输入” > “附加依赖项”,写入 MathLibrary.lib

“链接器” > “常规” > “附加库目录” ,写入指向 MathLibrary.lib 文件位置的路径,可以从我们之前编译 DLL 的输出窗口得知路径。

运行一下。

至此,已经成功调用 DLL 了!


参考资料

微软文档-静态库

微软文档-动态库

创建纯资源 DLL

将可执行文件链接到 DLL

使用 DEF 文件从 DLL 导出

bilibili :操作流程

automake生成静态库文件_动手 | 奶奶级的动态库入门相关推荐

  1. vue生成静态js文件_如何立即使用Vue.js生成静态网站

    vue生成静态js文件 by Ondřej Polesný 通过OndřejPolesný 如何立即使用Vue.js生成静态网站 (How to generate a static website w ...

  2. vue生成静态html文件_使用 VuePress 生成静态文档

    本文作者:IMWeb HuQingyang 未经同意,禁止转载 VuePress 由两部分组成:一部分是支持用 Vue 开发主题的极简静态网站生成器,另一个部分是为书写技术文档而优化的默认主题.它的诞 ...

  3. 【linux】程序找不到动态库.so的解决办法|查看.so动态库信息|.so动态库加载顺序

    目录 找不到.so解决方法 方法一:添加环境变量 方法二:复制so文件到lib路径 方法三:(推荐)添加ldconfig寻找路径 方法四:在编译目标代码时指定该程序的动态库搜索路径 让程序在本目录找到 ...

  4. automake生成静态库文件_基于CocoaPods的组件化原理及私有库实践

    轮子为什么会存在 智人能在残酷的进化大战中存活下来,原因之一就是智人懂得将知识沉淀成外物,辅助彼此之间的合作,从而使得整个群体产生了规模效应,即1+1>2的效果. 从一个角度上说,石器时代是基于 ...

  5. automake生成静态库文件_Automake 详解

    automake配置 一.安装 命令:sudo apt-get install automake 二.配置过程 1. 使用Autoscan工具生成configure.ac文件 命令: autoscan ...

  6. linux 环境变量文件_应急响应系列之Linux库文件劫持技术分析,有点硬核哟

    0×01 菜逼阶段 Linux库文件劫持这种案例在今年的9月份遇到过相应的案例,当时的情况是有台服务器不断向个可疑IP发包,尝试建立连接,后续使用杀软杀出木马,重启后该服务器还是不断的发包,使用net ...

  7. C++文件如何在linux下生成动态库So,以及如何使用这个动态库

    引用:http://blog.csdn.net/xuguang121/article/details/7457565 不同组件之间,如何提供一个动态库给其他Team使用,这样使用者也不需要关心和维护你 ...

  8. freemarker html 乱码,Freemarker生成静态html文件及中文乱码的问题.pdf

    Freemarker生成静态html文件及中文乱码的问题,freemarker静态化页面,freemarker静态化,freemarker乱码,freemarker静态方法,freemarker中文乱 ...

  9. thinkphp html php文件,ThinkPHP生成静态HTML文件

    View.class.php /** * 加载模板和页面输出 可以返回输出内容 * @access public * @param string $templateFile 模板文件名 * @para ...

最新文章

  1. Apache Camel 2.15.0 发布,Java 规则引擎
  2. ANDROID STUDIO 2.2 来啦
  3. 在linux中安装rpm包
  4. boost::iterator_adaptor用法的测试程序
  5. 定时器中断实验 编写程序使定时器0或者定时器1工作在方式1,定时500ms使两位数码管从00、01、02……98、99每间隔500ms加1显示。
  6. “不会Linux,怎么当程序员?”骨灰级程序员:你的代码正在毁掉你!
  7. RHEL5.8系统裁减(内含做小Linux前的准备工作)
  8. java基础之java输入输出语句
  9. 西威变频器avo下载调试资料_变频器设置面板及参数设置方法
  10. 力行《促进大数据发展行动纲要》 普元数据治理解决方案出炉
  11. 【Java】指定【微信】好友自动发送消息
  12. [Unity][摄像机视角]多个摄像机之间切换
  13. 【读书笔记】《读懂一本书》——如何读书不枯燥,读得懂,记得住
  14. Java equal
  15. Isometric Tile Engine 的遮挡处理
  16. [区块链]初识R3-Corda,解析区块链结构
  17. 一个简单的PLC运动控制项目
  18. VSCode-darwin-universal.zip 下载 mac os 版本
  19. WebAR技术尝试与基本环境搭建
  20. Office2003宏病毒——台湾No.1MacroVirus的运行

热门文章

  1. 简单jQuery实现选项框中列表项的选择
  2. Silverlight/Windows8/WPF/WP7/HTML5周学习导读(6月25日-7月1日)
  3. spring MVC做form提交Neither BindingResult nor plain target object for bean name 'command' available...
  4. linux文件管理相关操作
  5. js复制input 框中的值
  6. linux驱动之i2c学习
  7. 图像处理之基础---周末戏说卷积
  8. Python的内置方法(二)
  9. C#让程序只运行一次实例
  10. 2pc oracle dba_2pc_pending,dba_2pc_pending视图中的信息不清除会对以后有影响吗?如何根本解决问题?...