动态链接库(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,仅仅是项目及文件名不同. ...
随机推荐
- Oracle 10g 如何调整 sga_max_size 与 sga_target
sga_max_size是相对于操作系统来讲的,当启动oracle时,一次性分配给oracle实例的sga不会超过sga_max_size值:而sga_target是相对于oracle这个正在运行的应 ...
- 单片机—Arduino UNO-R3—学习笔记001
连接方法 下载Arduino软件 安装完成打开如图所示 观察右下角的连接接口"Arduino Uno在COM(X)" 在工具-->端口-->选择之前查看的端口 即为连接 ...
- ctfhub技能树—RCE—过滤目录分隔符,过滤运算符
过滤目录分隔符 打开靶机 查看页面信息 查询当前目录下文件结构 进入flag_is_here目录 127.0.0.1;cd flag_is_here 127.0.0.1||ls 执行之后发现还是在当前 ...
- 使用axis1.4生成webservice的客户端代码
webservice服务端: https://blog.csdn.net/ghsau/article/details/12714965 跟据WSDL文件地址生成客服端代码: 1.下载 axis1.4 ...
- Pku1236 Network of Schools
题目描述 n个学校构成一个有向图,通过m条边连接,一:问至少向图中多少个学校投放软件,可以使得所有学校直接或者间接的通过边(假设存在边(u,v),则向u投放v可以得到,而向v投放u不能通过v直接得到) ...
- 三节锂电池充电管理芯片,IC电路图如何设计
关于三节锂电池供电的产品,在三节锂电池上,需要三个电路系统: 1,三节锂电池保护电路, 2,三节锂电池充电电路, 3,三节锂电池输出电路. 1.三节锂电池保护电路,芯片电路图 控制三节锂电池池的充电电 ...
- RPC 实战与原理 精简版
什么是 RPC? RPC 有什么作用? RPC 步骤 为什么需要序列化? 零拷贝 什么是零拷贝? 为什么需要零拷贝? 如何实现零拷贝? Netty 的零拷贝有何不同? 动态代理实现 HTTP/2 特性 ...
- python_mmdt:从0到1--实现简单恶意代码分类器(二)
概述 上篇文章python_mmdt:一种基于敏感哈希生成特征向量的python库(一)我们介绍了一种叫mmdt_hash(敏感哈希)生成方法,并对其中的概念做了基本介绍.本篇,我们重点谈谈mmdt_ ...
- jQuery 勾选显示
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- Netty服务端Server代码说明
本文是简单的Netty启动服务端代码理解笔记 public class MyServer { public static void main(String[] args) throws Excepti ...