一、为什么需要dll

代码复用是提高软件开发效率的重要途径。一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用。比较常见的例子是各种应用程序框架,如ATL、MFC等,它们都以源代码的形式发布。由于这种复用是“源码级别”的,源代码完全暴露给了程序员,因而称之为“白盒复用”。“白盒复用”的缺点比较多,总结起来有4点。

  1. 暴露了源代码;
  2. 容易与程序员的“普通”代码发生命名冲突;
  3. 多份拷贝,造成存储浪费;
  4. 更新功能模块比较困难。

实际上,以上4点概括起来就是“暴露的源代码”造成“代码严重耦合”。为了弥补这些不足,就提出了“二进制级别”的代码复用。使用二进制级别的代码复用一定程度上隐藏了源代码,对于缓解代码耦合现象起到了一定的作用。这样的复用被称为“黑盒复用”。

在Windows操作系统中有两种可执行文件,其后缀名分别为.exe和.dll。它们的区别在于,.exe文件可被独立的装载于内存中运行;.dll文件却不能,它只能被其它进程调用。然而无论什么格式,它们都是二进制文件。上面说到的“二进制级别”的代码复用,可以使用.dll来实现。

与白盒复用相比,.dll很大程度上弥补了上述4大缺陷。.dll是二进制文件,因此隐藏了源代码;如果采用“显式调用”(后边将会提到),一般不会发生命名冲突;由于.dll是动态链接到应用程序中去的,它并不会在链接生成程序时被原原本本拷贝进去;.dll文件相对独立的存在,因此更新功能模块是可行的。

说明:实现“黑盒复用”的途径不只dll一种,静态链接库甚至更高级的COM组件都是。本文只对dll进行讨论。

二、创建dll

接下来用一个简单的例子来说明创建dll的方法。本例采用VS2010,使用C++编程语言,具体操作步骤如下。

通过Start Page或者File菜单栏,新建一个Project,将会弹出新建项目对话框。选择Win32 Project向导,项目名为CreateDLL,解决方案名为DLLTEST(注意Create directories for solution是勾选上的),点击OK,接着点击Next,到Application Settings,选择应用程序类型为dll,并勾选“Export Symbols”,点击Finish。完成这一步之后,VS界面上左边的Solution Explorer中将会看到向导自动生成的文件列表,如图1所示。

图1 wizard自动生成的文件列表

在VS界面的编辑窗口中,展示了自动生成的CreateDLL.cpp的代码。

[cpp] view plaincopyprint?
  1. // CreateDLL.cpp : Defines the exported functions for the DLL application.
  2. //
  3. #include "stdafx.h"
  4. #include "CreateDLL.h"
  5. // This is an example of an exported variable
  6. CREATEDLL_API int nCreateDLL = 0;
  7. // This is an example of an exported function.
  8. CREATEDLL_API int fnCreateDLL(void)
  9. {
  10. return 42;
  11. }
  12. // This is the constructor of a class that has been exported.
  13. // see CreateDLL.h for the class definition
  14. CCreateDLL::CCreateDLL()
  15. {
  16. return;
  17. }
// CreateDLL.cpp : Defines the exported functions for the DLL application.
//#include "stdafx.h"
#include "CreateDLL.h"// This is an example of an exported variable
CREATEDLL_API int nCreateDLL = 0;// This is an example of an exported function.
CREATEDLL_API int fnCreateDLL(void)
{return 42;
}// This is the constructor of a class that has been exported.
// see CreateDLL.h for the class definition
CCreateDLL::CCreateDLL()
{return;
}

这里有3种类型的example,分别为导出变量nCreateDLL、导出函数fnCreateDLL以及导出类CCreateDLL。为了简化起见,本例只考虑导出函数。修改CreateDLL.h文件为:

[cpp] view plaincopyprint?
  1. #ifdef CREATEDLL_EXPORTS
  2. #define CREATEDLL_API __declspec(dllexport)
  3. #else
  4. #define CREATEDLL_API __declspec(dllimport)
  5. #endif
  6. CREATEDLL_API void printMax(int&,int&);
  7. CREATEDLL_API void printMax(int&,int&,int&);
#ifdef CREATEDLL_EXPORTS
#define CREATEDLL_API __declspec(dllexport)
#else
#define CREATEDLL_API __declspec(dllimport)
#endifCREATEDLL_API void printMax(int&,int&);
CREATEDLL_API void printMax(int&,int&,int&);

修改CreateDLL.cpp文件为:

[cpp] view plaincopyprint?
  1. CREATEDLL_API void printMax(int& a,int& b)
  2. {
  3. std::cout<<"Among ("<<a<<","<<b<<"), the Max Number is "<<(a>b?a:b)<<"\n";
  4. }
  5. CREATEDLL_API void printMax(int& a,int& b,int& c)
  6. {
  7. std::cout<<"Among ("<<a<<","<<b<<","<<c<<"), the Max Number is "<<(((a>b?a:b)>c)?(a>b?a:b):c)<<"\n";
  8. }
CREATEDLL_API void printMax(int& a,int& b)
{std::cout<<"Among ("<<a<<","<<b<<"), the Max Number is "<<(a>b?a:b)<<"\n";
}
CREATEDLL_API void printMax(int& a,int& b,int& c)
{std::cout<<"Among ("<<a<<","<<b<<","<<c<<"), the Max Number is "<<(((a>b?a:b)>c)?(a>b?a:b):c)<<"\n";
}

不难发现,printMax函数的作用就是打印出两个整数或三个整数中的最大值。需要说明的是,这里故意使用同名函数是为了引出导出函数的修饰名称,具体将在第四节中阐述。

接下来,选择菜单Build->Build CreateDLL,Output窗口提示CreateDLL.dll文件生成成功,如图2所示。

图2 CreateDLL.dll成功生成

三、使用dll

本例采用“显式调用”的方式使用CreateDLL.dll。显式调用方式相比于”隐式调用“有好有坏。显式调用只需要一个.dll文件就可以了,灵活性更好,更新模块方便;相对的,程序员需要做的事情更多,使用方法更为复杂。

右键单击Solution Explorer中的Solution 'DLLTEST',在弹出的菜单中选择Add->New Project,选择Win32 Console Application,输入项目名为UseDLL,点击OK,接着点击Next,在Application Settings界面勾选EmptyProject并点击Finish。右键单击项目UseDLL,给它添加源文件UseDLL.cpp。这样操作之后,Solution Explorer的信息如图3所示。

图3 向Solution'DLLTEST'添加项目UseDLL

编写UseDLL.cpp的代码为:

[cpp] view plaincopyprint?
  1. /*--UseDLL.cpp
  2. *Author: ume(李优米)
  3. *Use CreateDLL.dll explicitly
  4. */
  5. #include<Windows.h>
  6. #include<iostream>
  7. typedef void(*FUNA)(int&,int&);
  8. typedef void(*FUNB)(int&,int&,int&);
  9. int main()
  10. {
  11. const char* dllName = "CreateDLL.dll";
  12. const char* funName1 = "printMax";
  13. const char* funName2 = "printMax";
  14. int x(100), y(100), z(100);
  15. HMODULE hDLL = LoadLibrary(dllName);
  16. if(hDLL != NULL)
  17. {
  18. FUNA fp1 = FUNA(GetProcAddress(hDLL,funName1));
  19. if(fp1 != NULL)
  20. {
  21. std::cout<<"Input 2 Numbers:";
  22. std::cin>>x>>y;
  23. fp1(x,y);
  24. }
  25. else
  26. {
  27. std::cout<<"Cannot Find Function "<<funName1<<std::endl;
  28. }
  29. FUNB fp2 = FUNB(GetProcAddress(hDLL,funName2));
  30. if(fp2 != NULL)
  31. {
  32. std::cout<<"Input 3 Numbers:";
  33. std::cin>>x>>y>>z;
  34. fp2(x,y,z);
  35. }
  36. else
  37. {
  38. std::cout<<"Cannot Find Function "<<funName2<<std::endl;
  39. }
  40. FreeLibrary(hDLL);
  41. }
  42. else
  43. {
  44. std::cout<<"Cannot Find "<<dllName<<std::endl;
  45. }
  46. return 1;
  47. }
/*--UseDLL.cpp*Author: ume(李优米)*Use CreateDLL.dll explicitly*/
#include<Windows.h>
#include<iostream>
typedef void(*FUNA)(int&,int&);
typedef void(*FUNB)(int&,int&,int&);
int main()
{const char* dllName = "CreateDLL.dll";const char* funName1 = "printMax";const char* funName2 = "printMax";int x(100), y(100), z(100);HMODULE hDLL = LoadLibrary(dllName);if(hDLL != NULL){FUNA fp1 = FUNA(GetProcAddress(hDLL,funName1));if(fp1 != NULL){std::cout<<"Input 2 Numbers:";std::cin>>x>>y;fp1(x,y);}else{std::cout<<"Cannot Find Function "<<funName1<<std::endl;}FUNB fp2 = FUNB(GetProcAddress(hDLL,funName2));if(fp2 != NULL){std::cout<<"Input 3 Numbers:";std::cin>>x>>y>>z;fp2(x,y,z);}else{std::cout<<"Cannot Find Function "<<funName2<<std::endl;}FreeLibrary(hDLL);}else{std::cout<<"Cannot Find "<<dllName<<std::endl;}return 1;
}

代码比较长,但是并不难理解,这里仅说明代码中的一些要点。

  • 包含头文件Windows.h,原因在于程序中用到了LoadLibrary、FreeLibrary、GetProcAddress等Win32 API函数。
  • FUNA和FUNB是函数指针类型的声明。
  • 当程序不再使用dll时,应该调用FreeLibrary及时释放它占用的内存空间。
  • 如果在const char* dllName和funName底部出现红色波浪线提示,说明采用的字符集不匹配,需要修改项目UseDLL的属性CharaterSet为Not Set。
  • 为方便项目的调试,建议修改解决方案的Startup Project属性为Single startup project并以UseDLL为首选。

然而,这个程序还有错误。编译并运行,结果如图4所示。

图4 UseDLL的运行结果

这并不是期望中的结果。实际上,正如第二节提到的那样,造成这种错误的原因正是导出函数的修饰名称。虽然在CreateDLL.cpp中两个printMax函数有相同的名称,但在dll二进制文件中,经过编译器的“加工”,它们实际上各自有不同的名称了。这也是函数重载机制得以实现的一个技术支持。

使用VS2010附带工具dumpbin,查看CreateDLL.dll的导出函数名,结果如图5所示。

图5 查看CreateDLL.dll的导出函数名

观察图5可以发现,CreateDLL.dll导出函数名为?printMax@@YAXAAH00@Z和?printMax@@YAXAAH0@Z。它们分别对应着三个整数的printMax和两个整数的printMax。因此,Use.DLL中funName应当相应修改为:

[cpp] view plaincopyprint?
  1. const char* funName1 = "?printMax@@YAXAAH0@Z";
  2. const char* funName2 = “?printMax@@YAXAAH00@Z”;
const char* funName1 = "?printMax@@YAXAAH0@Z";
const char* funName2 = “?printMax@@YAXAAH00@Z”;

修改之后,再次编译运行,结果正确,如图6所示。

图6 UseDLL正常运行

四、dll导出函数名称规范化

创建、使用dll并不复杂,走过前三节,相信读者肯定有这样的体会。然而,一个问题仍然值得思考:导出函数的修饰名称太“奇怪”,为dll的使用带来了不便,能不能让导出函数的修饰名称规范一些?

答案是肯定的,而且方法至少有两种:一是运用extern "C"修饰printMax;二是运用模块定义文件.def。后者的效果更好,所以本节将使用.def来规范化导出函数的修饰名称。

CreateDLL.dll导出的两个函数功能很简单,根据功能描述,理想的函数名称是pMaxA2和pMaxA3。在CreateDLL项目中添加CreateDLL.def文件:

[cpp] view plaincopyprint?
  1. LIBRARY CreateDLL
  2. EXPORTS
  3. pMaxA2 = ?printMax@@YAXAAH0@Z
  4. pMaxA3 = ?printMax@@YAXAAH00@Z
LIBRARY CreateDLL
EXPORTS
pMaxA2 = ?printMax@@YAXAAH0@Z
pMaxA3 = ?printMax@@YAXAAH00@Z

重新build项目CreateDLL,使用dumpbin再次查看CreateDLL.dll的导出函数名称,结果如图7所示。

图7 规范化的函数名,奇怪的修饰名称还存在

出现了期望的结果,但仍有小缺憾:奇怪的修饰名称仍然存在。能否去掉这些不太规范的修饰名称呢?当然是可以的。只需要将CreateDLL.h中#define CREATEDLL_API __declspec(dllexport) 修改为#define CREATEDLL_API即可。修改之后重新编译生成CreateDLL.dll,使用dumpbin查看导出函数名称,结果如图8所示。

图8 规范化的函数名,去除了奇怪的修饰名称

回到UseDLL.cpp,修改funName:

[cpp] view plaincopyprint?
  1. const char* funName1 = "pMaxA2";
  2. const char* funName2 = "pMaxA3";
const char* funName1 = "pMaxA2";
const char* funName2 = "pMaxA3";

重新编译运行UseDLL,结果正确,与图6类似。 五、dll的不足

动态链接库虽然一定程度上实现了“黑盒复用”,但仍存在着诸多不足,笔者能够想到的有下面几点。

  1. dll节省了编译期的时间,但相应延长了运行期的时间,因为在使用dll的导出函数时,不但要加载dll,而且程序将会在模块间跳转,降低了cache的命中率。
  2. 若采用隐式调用,仍然需要.h、.lib、.dll文件(“三件套”),并不能有效支持模块的更新。
  3. 显式调用虽然很好地支持模块的更新,但却不能导出类和变量。
  4. dll不支持Template。

二进制级别的代码复用相比源码级别的复用已经有了很大的进步,但在二进制级别的代码复用中,dll显得太古老。想真正完美实现跨平台、跨语言的黑盒复用,采用COM才是正确的选择

个人感言:

  第一、extern "C"的作用
       比如一个C源程序A.c要使用C++编写的库函数,在A.c中#include "B.h",其中B.h中有要使用的函数的原形声明func。当编译链接源程序时,却发现了“链接错误,未决的外部符号...”的错误,这是什么原因呢?
原因就是,C编译器编译A.c时,将func编译为func,当链接时链接器去C++库中寻找func,但是C++的编译器在编译库时将func编译成 _func@yyy@rrr,自然链接器就找不着相应的函数的信息了,所以就会报错!有什么办法可以处理这种情况呢?——可以在编写C++库的时候,为每一个函数(或导出函数)加上extern "C",它的含义是告知C++编译器在编译这些函数的时候,以C编译器的方式处理函数名。这样生成的库中的函数名字就是func了,当C程序调用库函数,编译链接时,链接器就能找到期望的信息,则链接成功。

第二、.def文件的作用(仅与VC++编程相关)
       前面提到,不同厂商开发的两个C编译器也会有一些差异,最突出的就是microsoft的C编译器,它对函数名字的处理很特别(究竟是什么样子,可以使用 Dumpbin工具查看dll的导出函数),所以要在使用他方编写的库时,程序链接能成功,有两种方法:1使用库编写者使用的C编译器(这里指 VC++),显然这种方法不合理;2库的编写者在使用VC++编写库时使用.def文件。
       .def文件的作用即是,告知编译器不要以microsoft编译器的方式处理函数名,而以指定的某方式编译导出函数(比如有函数func,让编译器处理后函数名仍为func)。这样,就可以避免由于microsoft VC++编译器的独特处理方式而引起的链接错误。

第三 你在def文件里写的函数名字就是导出的函数名字,相当于你使用了 extern c

第四:def文件写法

LIBRARY
  EXPORTS 
   printMax @1
   printmax @2

转自http://blog.csdn.net/ixsea/article/details/6676802

转载于:https://www.cnblogs.com/dengpeng1004/p/3853567.html

VS2010制作dll相关推荐

  1. vs2008制作dll笔记

    本文参考http://leongod.iteye.com/blog/1104575 使用vs2008制作dll文件,生成动态链接库,采用显示加载,以下记录实现过程: 1.制作dll文件 vs2008中 ...

  2. 如何用VB制作DLL文件

    1.新建一个ActiveX Dll,工程名字为vbmytestdll,类模块名字为mytestdll  2.类模块内容只有一个函数,主要返回DLL的HELLO WORLD  Public Functi ...

  3. MFC中制作Dll中带对话框资源的动态库

    Data:2019/10/23 这篇文章本来是17年写的,现在对这边文章进行细化.更新的是有一些晚了些 想要在MFC中的dll里面添加对话框资源,首先必须的条件是,当前的dll库类型必须是可扩展的DL ...

  4. 使用vs2008制作dll文件,生成动态链接库

    转自:http://blog.csdn.net/howard_liu1314/article/details/7862326 1.制作dll文件     vs2008中,File > New P ...

  5. 英文版VS2010制作中文环境安装包

    英文版VS2010制作中文环境安装包 .NET資料庫 2010-11-26 16:52:20 阅读289 评论0  字号:大中小 订阅 自从VS2010发布以来,一直在用英文版本,接下来就说明用英文版 ...

  6. VS2019制作DLL文件

    用VS2019制作DLL文件的一般步骤很简单: 1创建新项目 2找到动态链接库DLL项目 3创建 4.分别在源文件和头文件添加相关函数f.cpp,f.h 第4步中,f.cpp中是没有主函数的各个函数, ...

  7. asp.net网站服务器,vs2010制作简单的asp.net网站

    直入主题: 打开visual studio 2010程序开发软件 单击菜单栏的文件,依次选新建->网站->ASP.NET空网站,这里我们选择空网站,利于今后DIY自己的网站,最好什么从头来 ...

  8. vs 2017 制作Dll文件的两种方法,以及调用Dll文件的两种方法。

    近来学习制作Dll文件,看了几个视频教程,看了网上的例子,看了msdn上的例子.现在做个总结,以便来日回顾,同时也希望以大家相互交流学习. 注意1:用 method 1 named "Usi ...

  9. VS2010制作安装程序

    序 前些天想写一下制作安装程序,由于要写的内容比较多,一拖再拖,不过坚持就是胜利,今天终于写完了. 1概述 做应用软件制作安装程序是很必要的一件事情,本文主要介绍使用VS2010自带的打包组建来开发一 ...

  10. [转]英文版VS2010制作中文环境安装包

    本文转自:http://www.cnblogs.com/upupto/archive/2010/10/29/1864726.html 自从VS2010发布以来,一直在用英文版本,接下来就说明用英文版本 ...

最新文章

  1. android GridView item中组件获取焦点
  2. 无人驾驶技术排名:百度居中游,苹果特斯拉垫底 | 行业
  3. 【SCOI 2009】生日快乐
  4. matlab对手写数字聚类的方法_scikitlearn — 聚类
  5. Rancher搭建NFS服务器
  6. php ajax sucess 失败,Ajax请求发送成功但不进success的解决方法(图文教程)
  7. (4) ebj学习:ejb发布web service
  8. 【Zookeeper】源码分析之服务器(一)
  9. c#进阶(7)—— 异步编程基础(async 和 await 关键字)
  10. python语言原理_梯度下降算法的原理用Python语言实现,易于理解,python,更
  11. 传智播客pscs6ppt_freeCodeCamp播客直播。 这是6集,您现在可以狂欢。
  12. 死锁、EAT、页表、单双缓冲区典型题目及解析
  13. python数据类型--数字、字符串
  14. poj 2181 jumping cows
  15. Hive常见的存储格式的区别与应用场景
  16. 【PHP面向对象(OOP)编程入门教程】20.PHP5接口技术(interface)
  17. Mac OS 系统的发展历史
  18. 一键实现视频二维码分享功能,So easy!
  19. SPSS Statistics 分位数回归 翻译文档
  20. 如何提高工作杠杆率?卖得更贵与卖出更多次

热门文章

  1. 苹果电脑屏幕刷新率如何调整
  2. 看雪CTF.TSRC 2018 团队赛 第八题 『二向箔』 解题思路
  3. 基本类型的默认值和取值范围
  4. 利用whistle调试移动端页面
  5. RAC 特点   character
  6. selenium chromedriver usage
  7. Hibernate常出现的报错
  8. 宜人贷CTO段念:透明与面向目标是管理理念的核心
  9. 那些年,我们一起追的女孩。
  10. Oracle根据外键名称查找关联的表与列