1.动态链接库(dll)概述
没接触dll之前觉得它很神秘,就像是一个黑盒子,既不能直接运行,也不能接收消息。它们是一些独立的文件,其中包含能被可执行程序或其他dll调用来完成某项工作的函数,只有在其他模块调用dll中的函数时,dll才发挥作用。
在实际编程中,我们可以把完成某项功能的函数放在一个动态链接库里,然后提供给其他程序调用。像Windows API中所有的函数都包含在dll中,如Kernel32.dll, User32.dll, GDI32.dll等。那么dll究竟有什么好处呢?
1.1 静态库和动态库
-
静态库
:函数和数据被编译进一个二进制文件(扩展名通常为.lib),在使用静态库的情况下,在编译链接可执行文件时,链接器从静态库中复制这些函数和数据,并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.exe)。当发布产品时,只需要发布这个可执行文件,并不需要发布被使用的静态库。
-
动态库
:在使用动态库时,往往提供两个文件:一个引入库(.lib,非必须)和一个.dll文件。这里的引入库和静态库文件虽然扩展名都是.lib,但是有着本质上的区别,对于一个动态链接库来说,其引入库文件包含该动态库导出的函数和变量的符号名,而.dll文件包含该动态库实际的函数和数据。
1.2 使用动态链接库的好处
-
可以使用多种编程语言编写
:比如我们可以用VC++编写dll,然后在VB编写的程序中调用它。
-
增强产品功能
:可以通过开发新的dll取代产品原有的dll,达到增强产品性能的目的。比如我们看到很多产品踢动了界面插件功能,允许用户动态地更换程序的界面,这就可以通过更换界面dll来实现。
-
提供二次开发的平台
:用户可以单独利用dll调用其中实现的功能,来完成其他应用,实现二次开发。
-
节省内存
:如果多个应用程序使用同一个dll,该dll的页面只需要存入内存一次,所有的应用程序都可以共享它的页面,从而节省内存。
2. dll的创建
dll的创建主要有两种方法:一是使用 __declspec(dllexport) 创建dll,二是使用模块定义(.def)文件创建dll。
2.1 使用 __declspec(dllexport) 创建dll
首先在VS中的Visual C++中创建一个Win32 Project,取名为Dll1。在Application Type中选择DLL,在Additional options中选择Empty project,即创建一个空的动态链接库工程。
然后为工程添加一个C++源文件:Dll1.cpp,假设我要实现的是加法和减法运算,则代码如下:
__declspec(dllexport) int add(int a, int b){
return a + b;
__declspec(dllexport) int subtract(int a, int b){
return a - b;
为了让dll导出函数,需要在每一个需要被导出的函数前面加上标识符:__declspec(dllexport)。
利用Build命令生成Dll1动态链接库,这时在Dll1/Debug目录下就会生成.dll文件和.lib文件,这两个文件即为所需的动态链接库的文件。
既然已经有了这个dll文件,是不是就可以在其他程序中访问该dll中的add和subtract函数了呢?必须注意的一点是:应用程序如果想要访问某个dll中的函数,那么这个函数必须是已经被导出的函数。
为了查看一个dll中有哪些导出函数,Visual Studio提供了一个命令行工具:Dumpbin。
2.2 使用Dumpbin命令确认dll的导出函数
首先在命令行中进入到VS的安装目录下,运行一个名为VCVARS32.bat的批处理程序(对于VS2013来说,该bat文件位于\VC\bin目录下),该文件的作用是用来创建VC++使用的环境信息。(注意,当在命令行界面执行VCVARS32.bat文件后,该文件设置的环境信息只在当前命令行窗口生效。)
然后输入dumpbin命令,即可列出该命令的使用方法:
那么想要查看一个dll提供的导出函数,在Dll1.dll文件所在目录下,在命令行中输入下述命令:
dumpbin -exports Dll1.dll
在上图中可以看到我们导出了两个函数,但是导出函数的名称长得很奇怪,add导出函数的名称是“?add@@YAHHH@Z”,subtract导出函数的名称是“?subtrct@@YAHHH@Z”。这是因为在编译链接时,C++会按照自己的规则篡改函数的名称,这一过程称为“名字改编”。这会导致不同的编译器、不同的语言下调用dll发生问题。因此我们希望动态链接库文件在编译时,导出函数的名称不要发生变化。
为了实现这一目的,可以再定义导出函数时加上限定符:extern “C”,如
extern "C" __declspec(dllexport) int add(int a, int b){
//...
但是这种方式只能解决C++和C语言之间相互调用时函数命名的问题。为了彻底解决这个问题,可以通过模块定义(.def)文件实现。
2.3 使用模块定义(.def)文件创建dll
使用def文件创建dll的话就不再需要__declspec(dllexport),因此将代码写成最原始的样子:
int add(int a, int b){
return a + b;
int subtract(int a, int b){
return a - b;
同时为工程创建一个后缀名为.def的文件,并添加进工程,编辑其内容为:
LIBRARY Dll1
EXPORTS
subtract
其中LIBRARY语句用于指定动态链接库的名称,该名称与生成的动态链接库名称一定要匹配。EXPORTS语句用于表明dll将要导出的函数,以及为这些导出函数指定的符号名。
将该模块定义文件链接到工程中,方法为工程属性页面>链接器>输入>模块定义文件中写入“Dll1.def”。
然后重新Build Solution,并用dumpbin工具查看现在dll导出的函数,可以发现函数的名字改编问题得到了解决!
以上就是创建dll的两种方法,个人比较提倡使用模块定义(.def)文件创建dll,代码简洁的同时还没有名字改编的问题。
接下来我们来看看如何使用创建好的dll。
3. dll的使用
dll的使用也有两种方法,一是隐式链接的方式加载dll,二是显示加载方式加载dll。
3.1 隐式链接方式加载dll
为了更好地展示dll的使用,我首先创建了一个基于MFC的对话框程序,然后为其添加两个按钮。
将生成好的Dll1.dll和Dll1.lib复制到对话框程序所在的文件夹,然后在CXXXDlg.h中注册动态链接库的引入库文件。因为.lib文件包含了Dll1.dll中导出函数的符号名,相当于告诉对话框程序相关函数应该去dll中调用。
#pragma comment(lib,"Dll1.lib")
然后在CXXXDlg.cpp中声明外部函数:
_declspec(dllimport) int add(int a, int b);
_declspec(dllimport) int subtract(int a, int b);
这样我们就可以使用这两个函数了。为两个按钮添加事件响应程序,并添加如下代码:
void CXXXDlg::OnBtnAdd()
CString str;
str.Format(_T("5 + 3 = %d"), add(5, 3));
MessageBox(str);
运行程序发现可以通过dll的导出函数来实现加法功能了。说明dll可以使用。
3.2 显示加载方式加载dll
另一种是通过LoadLiabrary函数显示加载dll。代码如下。需要注意的是这时候我们不再需要注册.lib文件,也不需要声明外部函数。只要在需要使用的地方调用dll文件即可。
void CXXXDlg::OnBtnSubtract()
HINSTANCE hInst;
hInst = LoadLibrary(L"Dll1.dll");
typedef int(*SUBPROC)(int a, int b);
SUBPROC Sub = (SUBPROC)GetProcAddress(hInst, "subtract");
CString str;
str.Format(_T("5-3=%d"), Sub(5, 3));
FreeLibrary(hInst);
MessageBox(str);
3.3 两种加载方式对比
通过以上的例子,可以看到隐式链接和动态加载两种加载dll的方式各有优点。
- 隐式链接方式实现简单,一开始就把dll加载进来,在需要调用的时候直接调用即可。但是如果程序要访问十多个dll,如果都采用隐式链接方式加载他们的话,在该程序启动时,这些dll都需要被加载到内存中,并映射到调用进程的地址空间,这样将加大程序的启动时间。而且一般来说,在程序运行过程中只是在某个条件满足的情况下才需要访问某个dll中的函数,如在上述例子中,我只有在点击按钮时才需要访问dll,其他情况下并不需要访问。这样如果所有dll都被加载到内存中,资源浪费是比较严重的。
- 显示加载的方法则可以解决上述问题,dll只有在需要用到的时候才会被加载到内存中。另外,其实采用隐式链接方式访问dll时,在程序启动时也是通过调用LoadLibrary函数加载该进程需要的动态链接库的。
reference
《VC++深入详解》孙鑫
1.动态链接库(dll)概述没接触dll之前觉得它很神秘,就像是一个黑盒子,既不能直接运行,也不能接收消息。它们是一些独立的文件,其中包含能被可执行程序或其他dll调用来完成某项工作的函数,只有在其他模块调用dll中的函数时,dll才发挥作用。 在实际编程中,我们可以把完成某项功能的函数放在一个动态链接库里,然后提供给其他程序调用。像Windows API中所有的函数都包含在dll中,如Kerne
最简单的dll并不比c的helloworld难,只要一个DllMain函数即可,包含objbase.h头文件(支持COM技术的一个头文件)。若你觉得这个头文件名字难记,那么用windows.H也可以。源代码如下:dll_nolib.cpp
#include
#include
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
HANDLE g_hModule;
switch(dwReason)
case DLL_PROCESS_ATTACH:
cout<<"Dll is attached!"<<endl;
g_hModule = (HINSTANCE)hModule;
break;
case DLL_PROCESS_DETACH:
cout<<"Dll is detached!"<<endl;
g_hModule=NULL;
break;
return true;
其中DllMain是每个dll的入口函数,如同c的main函数一样。DllMain带有三个参数,hModule表示本dll的实例句柄(听不懂就不理它,写过windows程序的自然懂),dwReason表示dll当前所处的状态,例如DLL_PROCESS_ATTACH表示dll刚刚被加载到一个进程中,DLL_PROCESS_DETACH表示dll刚刚从一个进程中卸载。当然还有表示加载到线程中和从线程中卸载的状态,这里省略。最后一个参数是一个保留参数(目前和dll的一些状态相关,但是很少使用)。
从上面的程序可以看出,当dll被加载到一个进程中时,dll打印"Dll is attached!"语句;当dll从进程中卸载时,打印"Dll is detached!"语句。
编译dll需要以下两条命令:
cl /c dll_nolib.cpp
这条命令会将cpp编译为obj文件,若不使用/c参数则cl还会试图继续将obj链接为exe,但是这里是一个dll,没有main函数,因此会报错。不要紧,继续使用链接命令。
Link /dll dll_nolib.obj
这条命令会生成dll_nolib.dll。
注意,因为编译命令比较简单,所以本文不讨论nmake,有兴趣的可以使用nmake,或者写个bat批处理来编译链接dll。
加载DLL(显式调用)
在C#开发软件的过程中,要经常调用C/C++
生成的
dll,本文主要介绍用C/C++
生成dll的步骤,以及用C/C++、C#两种语言实现
dll的引用实现。本文
使用的IDE是VS2019,文章通过具体实例进行讲解,大家可以跟着自己动手进行测试。
原文链接:https://blog.csdn.net/sinat_40003796/article/details/124348814
什么是.DLL文件?
DLL 是一个包含可由多个程序同时使用的代码和数据的库。例如,在 Windows 操作系统中,Comdlg32 DLL 执行与对话框有关的常见函数。因此,每个程序都可以使用该 DLL 中包含的功能来实现“打开”对话框。这有助于促进代码重用和内存的有效使用。
通过使用 DLL,程序可以实现模块化,由相对独立的组件组成。例如,一个计帐程序可以按模块来销售。可以在运行时
第一步,建立一个CPP的DLL工程,然后写如下代码,生成DLL
复制代码 代码如下:#include #define DLLEXPORT extern “C” __declspec(dllexport) DLLEXPORT int __stdcall hello() { printf(“Hello world!\n”); return 0; }
第二步,编写一个 python 文件:复制代码 代码如下:# coding: utf-8 import os import ctypes CU
由于静态库是将代码嵌入到
使用程序中,多个程序
使用时,会有多份代码,所以代码体积会增大;动态库只需要存在一份,其它程序通过函数地址
使用,所以代码体积小.
静态库发生变化后,新的代码需要重新嵌入到执行程序中,进行编译;动态库发生变化,如果库中的函数定义(或地址)未变化,其它
使用dll的程序不要重新链接.
动态库创建
创建动态库项目
添加库程序
库程序导出
声明式导出
_declspec(
dllexport
要生成一个C++的DLL,你需要完成以下步骤:
1. 创建一个C++ DLL项目。可以在Visual Studio中使用"Win32 Console Application"项目类型,选择“DLL”作为应用程序类型。
2. 添加需要导出的函数。在项目中添加需要导出的函数,并且使用__declspec(dllexport)来标记它们。例如:
```cpp
__declspec(dllexport) int Add(int a, int b)
return a + b;
3. 生成DLL。选择“生成”->“生成解决方案”来生成DLL文件。
4. 使用DLL。将生成的DLL文件复制到你的应用程序目录下,并且在代码中使用LoadLibrary函数来加载DLL文件,使用GetProcAddress函数来获取导出函数的地址。例如:
```cpp
HMODULE hModule = LoadLibrary("yourdll.dll");
if (hModule != NULL)
typedef int (*AddFunc)(int, int);
AddFunc add = (AddFunc)GetProcAddress(hModule, "Add");
if (add != NULL)
int result = add(1, 2);
// do something with result
FreeLibrary(hModule);
这样,你就可以生成一个C++的DLL,并且在其他应用程序中使用它了。
【论文笔记】视频物体检测(VID)系列 FGFA:Flow-Guided Feature Aggregation for Video Object Detection
qq_52896125:
C++编程笔记:dll的生成与使用
A1200266:
解读Batch Normalization
m0_64967806:
IPython Notebook error - Unreadable Notebook: Unsupported JSON nbformat
m0_70230434:
MFC界面编程1:GDI+实现不规则窗体
Aili_Xiao: