动态链接库(DLL)的创建和使用
最近想做个记录日志的C++库,方便后续使用。想着使用动态库,正好没用过,学习下。概念这里不赘述。学习过程中碰到的几点,记录下来。学习是个渐进的过程,本文也是一个逐渐完善的过程。
一、Static Library
标准Turbo 2.0中的C函数库(scanf、pringf、memcpy等)来自静态库。创建方法很简单,建立win32 application工程,选择static library,添加变量、方法和类等就可以了。使用的方法如下:
#include "../LogBuilderSL/LogBuilder.h"
#pragma comment(lib, "../Debug/LogBuilderSL.lib")
之后便可以像C库函数一样正常使用了。
\#pragma comment(lib, "../Debug/LogBuilderSL.lib")表明,本工程与静态库(参数指定的路径下的*.lib)一起编译。或者将该lib添加到【Project Property】-->【Linker】-->【Input】下的Additional Dependencies中,添加的方式为全路径,如:“C:\Users\ SAMSUNG-PC\ Desktop\ C S\ LogBuilderSL\ Debug\ LogBuilderSL.lib”。
再或者将.lib放置到Library Directories下(或者在其中添加.lib路径),在上面的【Input】中添加LogBuilderSL.lib。
二、Dynamic Link Library
对于DLL,VC支持的有三类:No-MFC DLL、MFC Regular DLL和MFC Extension DLL。
- No-MFC DLL:导出函数为标准的C接口(extern "C");
- MFC Regular DLL:包含一个继承自CWinApp的类,无消息循环;
- MFC Extension DLL:采用MFC动态链接版本创建,只用于MFC类库的应用程序。
1、No-MFC DLL
动态链接库通过导出函数对外提供的接口,有两种导出函数的方法:a、通过模块定义(.ref)文件声明;b、通过关键字__declspec(dllexport)声明导出函数。这里仅讨论第二种方式,模块定义文件的方式请自行查阅。
给出简单的DLL创建方法,头文件声明了类LogBuilder和两个导出函数,其中CreateLogBuilder()函数为C风格函数。CPP文件照常定义即可。
<LogBuilderDL.h>
class LogBuilder{ ... };
extern "C" __declspec(dllexport) LogBuilder* CreateLogBuilder(string path);
__declspec(dllexport) void DeleteLogBuilder(LogBuilder *lpLogBuilder);
1)显示(动态)加载该DLL
<Main.cpp>
#include "../LogBuilderDL/LogBuilder.h"
typedef LogBuilder*(*CreatorByPath)(string); // 宏定义函数指针类型
int _tmain(int argc, _TCHAR* argv[])
{
HINSTANCE hDll; // DLL句柄
CreatorByPath creator; // 函数指针
hDll = LoadLibrary(L"..\\Debug\\LogBuilderDL.dll");
if (hDll != NULL)
{
creator = (CreatorByPath)GetProcAddress(hDll, "CreateLogBuilder");
if (creator != NULL)
{
LogBuilder* log = creator("log.log");
log->WriteLog("Liwuqingxin", true);
}
FreeLibrary(hDll);
}
getchar();
return 0;
}
- 首先,加载DLL;
- 然后,获取了
CreateLogBuilder()函数的地址; - 最后,通过函数地址调用该函数。
这里需要注意两点。
其一,以上DLL间接导出了C++类,这里通过C风格函数封装类的获取过程,获取到类的实例后可正常使用该类,但类的静态成员(需要使用域作用符访问的成员)便无法导出。另外可直接导出C++类,第三点深入讨论。当需要使用DLL中的类型、宏定义或者变量时,需要包含该DLL的头文件(显式(动态)调用时,仅仅使用函数时并不需要)。
其二,CreateLogBuilder()为C风格函数。如果不声明为extern "C",该函数被C编译器编译后在符号库中的名字为"CreateLogBuilder",而C++编译器则会产生名称为"?CreateLogBuilder@@YAPAVLogBuilder@@V?\(basic_string@DU?\)char_traits@D@std@@V?$allocator@D@2@@std@@@Z"之类的外部链接符号(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)[参考:http://www.jianshu.com/p/5d2eeeb93590]。显示(动态)加载DLL时,GetProcAddress()函数需要通过上述真实的外部链接符号名称去获取函数地址,隐式(静态)加载没有影响。因此,导出函数应使用extern "C"声明为C风格函数更加合适(对加载方式没有要求)。
其三,导出C++类:在class关键字与类名中间添加导出声明(这里需要使用宏代替__declspec(dllexport),因为在调用DLL时需要声明导入类,直接使用该声明则DLL用户需要另外定义.h文件)。这样,DLL用户可直接使用该类。但是静态成员需要额外声明为导出。如下:
<LogBuilder.cpp>
API_DECLSPEC int LogBuilder::s = 0;
API_DECLSPEC int LogBuilder::fun()
{
return 0;
}
并且在用户使用时需要加上导入lib的声明:
<Main.cpp>
...
#pragma comment(lib, "../Debug/LogBuilderDL.lib") // 使用类的静态成员时需要
...
所谓的显示(动态)加载,是通过windows API函数加载DLL,并获取需要的函数地址,这个工作由API完成。而在客户程序中直接使用类的静态成员,编译会报无法解析外部符号的错,因为编译器无法找到这些符号(未调用API),那么我们只能自己显示加载.lib文件,并在DLL中声明导出静态成员。更深入理解为,我们还可以直接将“?fun@LogBuilder@@SAHXZ”传递给GetProcAddress()函数获取静态成员的地址,这样也能不加载.lib直接使用。
2)隐式(静态)加载DLL
<Main.cpp>
#include "../LogBuilderDL/LogBuilder.h"
#pragma comment(lib, "LogBuilderDL.lib")
extern "C" __declspec(dllimport) LogBuilder* CreateLogBuilder(std::string path);
int _tmain(int argc, _TCHAR* argv[])
{
LogBuilder *log = CreateLogBuilder("log.log");
if (log != NULL)
log->WriteLog("Liwuqingxin<span style="font-family: Arial, Helvetica, sans-serif;">", true);
getchar();
return 0;
}
- 首先,包含DLL的头文件;
- 然后,告诉编译器.lib文件的路径(方式和1中的静态库方法一致);
- 再次,声明导入函数,对应于DLL导出函数;
- 最后,可以直接像正常函数一样使用了。
需要注意几点。
其一,CreateLogBuilder()为导出函数,可以用来创建类的对象。若使用导出类(前面有提到),还可以直接实例化该类(但是不推荐,会导致DLL HELL,后面详述)。
其二,全局变量需要声明导出,否则客户程序包含头文件后使用的全局变量和DLL的中的全局变量将是两份副本!
其三,extern "C" __declspec(dllimport) LogBuilder* CreateLogBuilder(std::string path);这句声明没有似乎也可以调用该函数[参见:http://bbs.csdn.net/topics/330169671 ]。总结一下这里查阅资料的收获:
前文中,使用__declspec(dllexport)声明导出函数,这个方法没错,但是代码的写法有些问题。明确一下:一个DLL创建后,需要提供给使用者的有三个文件:.h、.lib、.dll。DLL创建者和使用者共用.h文件,但需求不一样:创建者需要声明函数为__declspec(dllexport);使用者需要声明函数为__declspec(dllimport)。因此,出于维护性和规范性考虑,使用预编译宏和宏定义区分.h文件的包含者:DLL自身加入预编译宏*_EXPORTING。否则,假如一个DLLA调用另一个DLLB而包含其头文件时,将会使用__declspec(dllexport)而错误地将DLLB中导入的函数作为DLLA的函数导出了。(如此,Main.cpp中应该不用再加入extern "C" __declspec(dllimport) LogBuilder* CreateLogBuilder(std::string path);语句了)代码如下:
#ifdef HFILENAME_EXPORTING
#define API_DECLSPEC __declspec(dllexport)
#else
#define API_DECLSPEC __declspec(dllimport)
#endif
使用DLL时,__declspec(dllimport)声明编译时明确函数为从DLL导入的外部函数,不需要间接寻址,效率更高
3)DLL HELL
bz刚开始学习DLL相关,这里参考:DLL导出类。总结一下。[参考:http://m.blog.csdn.net/blog/guyue35/16996713]
1、DLL和客户程序是分开编译的,这会导致某些编译时确定的内容在DLL中修改无法更新到客户程序(除非你重新编译客户程序,这不现实)。以下情况会导致错误:
- 应用程序直接访问类的公有变量,而该公有变量在新DLL中定义的位置发生了变化;
- 应用程序调用类的一个虚函数,而新的类中,该虚函数的前面又增加了一个虚函数;
- 新类的后面增加了成员变量,并且新类的成员函数将访问、修改这些变量;
- 修改了新类的基类,基类的大小发生了变化;
- 其他编译时确定的内容,如C的常量(C++新特性常量为运行时确定),宏等。
2、导出类的大小、成员的位置等的改变无法通知到客户程序,要想做一个可升级的DLL,以下三点用来使DLL远离地狱:
不直接生成类的实例。对于类的大小,当我们定义一个类的实例,或使用new语句生成一个实例时,内存的大小是在编译时决定的。要使应用程序不依赖于类的大小,只有一个办法:应用程序不生成类的实例,使用DLL中的函数来生成。把导出类的构造函数定义为私有的(privated),在导出类中提供静态(static)成员函数(如NewInstance())(静态成员函数能够用类名直接调用,而一般的成员函数要使用类对象来调用,这样只有先声明对象才能调用一般成员函数,此处要先用函数来构造类对象,故为静态的)用来生成类的实例。因为NewInstance()函数在新的DLL中会被重新编译,所以总能返回大小正确的实例内存。
不直接访问成员变量。应用程序直接访问类的成员变量时会用到该变量的偏移地址。所以避免偏移地址依赖的办法就是不要直接访问成员变量。把所有的成员变量的访问控制都定义为保护型(protected)以上的级别,并为需要访问的成员变量定义Get或Set方法。Get或Set方法在编译新DLL时会被重新编译,所以总能访问到正确的变量位置。
忘了虚函数吧,就算有也不要让应用程序直接访问它。因为类的构造函数已经是私有(privated)的了,所以应用程序也不会去继承这个类,也不会实现自己的多态。如果导出类的父类中有虚函数,或设计需要(如类工场之类的框架),一定要把这些函数声明为保护的(protected)以上的级别,并为应用程序重新设计调用该虑函数的成员函数。这一点也类似于对成员变量的处理。
事实上,建议你在发布导出类的DLL的时候,重新定义一个类的声明,这个声明可以不管原来的类里的成员变量之类的,只把接口函数列在类的声明里。
[主要参考:《VC++动态链接库(DLL)编程》 系列,作者:宋宝华,http://21cnbao.blog.51cto.com/109393/120777。
PS:本文参考了很多优秀的博客、论坛等,感谢这些大虾们的总结。在参考的地方基本上给出了原文链接。本文在此基础上进行了实验验证并做了一些整理和总结。
动态链接库(DLL)的创建和使用的更多相关文章
- 动态链接库DLL的创建生成及调用
一.背景 最近在做CANTOUSB底层驱动的调用,是调用别人已经封装好的库,看不到别人写的源程序.程序中调用的是隐式调用即 x.h+x.lib+x.dll,其中DLL即是动态链接库(Dynamic L ...
- vs2010创建和使用动态链接库(dll)
本文将创建一个简单的动态链接库,并编写一个应用台控制程序使用该动态链接库,并提出了与实现相关的几个问题,供初学者交流. 本文包含以下内容: 创建动态链接库项目 向动态链接库添加类 创建引用动态链接库的 ...
- 创建一个动态链接库 (DLL),使用VS2010
在本演练中,您将创建一个动态链接库 (DLL),其中包含可供其他应用程序使用的有用例程.使用 DLL 是一种重用代码的绝佳方式.您不必在自己创建的每个程序中重新实现这些例程,而只需对这些例程编写一次, ...
- C# 创建和引入动态链接库dll文件
一.创建动态链接库dll文件 新建 -> 项目->类库 名称为:dlltest 添加函数:消息框弹出消息 using System.Collections.Generic; using S ...
- 编译可供C#调用的C/C++动态链接库dll文件
编译可供C#调用的C/C++动态链接库dll文件,C语言控制台应用程序,探索生成dll过程 由于项目需求,需要公司另一个团队提供相关算法支持,是用C语言编译好的dll库提供给我们进行调用. 但是拿到d ...
- VC++动态链接库(DLL)编程深入浅出(zz)
VC++动态链接库(DLL)编程深入浅出(zz) 1.概论 先来阐述一下DLL(Dynamic Linkable Library)的概念,你可以简单的把DLL看成一种仓库,它提供给你一些可以直接拿来用 ...
- VS2010编写动态链接库DLL及单元测试用例,调用DLL测试正确性
转自:http://blog.csdn.net/testcs_dn/article/details/27237509 本文将创建一个简单的动态链接库,并编写一个控制台应用程序使用该动态链接库,该动态链 ...
- 动态链接库 DLL
动态链接库DLL 不使用时不会有任何作用,只有在其他模块调用动态链接库中的函数时,它才发挥作用. 一.静态库与动态库 1.静态库 函数和数据被编译进一个二进制文件(.LIB),编译时,会将其组合起来创 ...
- 动态链接库dll,静态链接库lib, 导入库lib
转载地址:http://www.cnblogs.com/chio/archive/2008/08/05/1261296.html 目前以lib后缀的库有两种,一种为静态链接库(Static Libar ...
- VS2010编写动态链接库DLL和单元测试,转让DLL测试的正确性
本文将创建一个简单的动态库-link,谱写控制台应用程序使用该动态链接库,该动态链接库为"JAVA调用动态链接库DLL之JNative学习"中使用的DLL,仅仅是项目及文件名不同. ...
随机推荐
- P1967 货车运输(倍增LCA,生成树)
题目链接: https://www.luogu.org/problemnew/show/P1967 题目描述 A国有n座城市,编号从 1到n,城市之间有 m 条双向道路.每一条道路对车辆都有重量限制, ...
- AWD生存之道
比赛开始阶段 常见漏洞的防御手段:https://www.freebuf.com/articles/web/208778.html 一.登陆SSH 重点 如果ssh的密码不是随机密码,记得一开始就进行 ...
- yum配置文件下使用自定义变量
yum的配置文件中,可以使用的变量,简称为yum变量: 默认的yum变量有: $releasever(Release Version),发行版的版本 $arch,CPU体系结构,通过 Python 的 ...
- 24V转5V稳压芯片,高效率的同步降压DC-DC变换器3A输出电流
PW2330开发了一种高效率的同步降压DC-DC变换器3A输出电流.PW2330在4.5V到30V的宽输入电压范围内工作集成主开关和同步开关,具有非常低的RDS(ON)以最小化传导损失.PW2330采 ...
- 订阅者模式,公众号、B站、快手用了都说好!
大家好,今天和大家来聊一个新的设计模式--订阅者模式. 这个模式在我们的生活当中非常常见,可以说是几乎所有的媒体平台都用或多或少地用到了这个模式.比如公众号,我们来仔细梳理一下公众号这个平台当中的整个 ...
- 如果using语句中出现异常,资源会被释放掉吗?
<CLR Via C#>第三版 P489 在using内部抛出了异常,被using的对象还是会被释放掉. Using编译时会自动生成Try Finally代码块. 同样Using只能用于实 ...
- 内存空间有限情况下的词频统计 Trie树 前缀树
数据结构与算法专题--第十二题 Trie树 https://mp.weixin.qq.com/s/nndr2AcECuUatXrxd3MgCg
- Serverless对研发效能的变革和创新 云托管和Serverless应用差异
https://mp.weixin.qq.com/s/J4RXtKanh3IMr4fY7t0nyQ Serverless对研发效能的变革和创新 杨皓然(不瞋) 阿里巴巴中间件 2020-10-23
- Server:www121 Server:www120 Server:NWS_SP 内容被散列,并在响应中放入Etag When to Use Entity-Tags and Last-Modified Dates
1 Request URL:http://www.biyao.com/minisite/bzzx 2 Request Method:GET 3 Status Code:200 OK 4 Remote ...
- nginx常用功能和配置
nginx常用功能和配置 1.nginx常用功能和配置 1.1 限流 1.2 压力测试工具--Ab 1.2.1安装 1.2.2 测试 1.2.3 返回值 1.3 limit_conn_zone 1.4 ...