一、为什么需要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的代码。

  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. }

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

修改CreateDLL.h文件为:

  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&);

修改CreateDLL.cpp文件为:

  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. }

不难发现,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的代码为:

  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. }

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

  • 包含头文件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应当相应修改为:

  1. const char* funName1 = "?printMax@@YAXAAH0@Z";
  2. const char* funName2 = “?printMax@@YAXAAH00@Z”;

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

图6 UseDLL正常运行

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

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

为dll的使用带来了不便,能不能让导出函数的修饰名称规范一些?答案是肯定的,而且方法至少有两种:一是运用extern "C"修饰printMax;

二是运用模块定义文件.def。后者的效果更好,所以本节将使用.def来规范化导出函数的修饰名称。

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

  1. LIBRARY CreateDLL
  2. EXPORTS
  3. pMaxA2 = ?printMax@@YAXAAH0@Z
  4. 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:

  1. const char* funName1 = "pMaxA2";
  2. const char* funName2 = "pMaxA3";

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

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

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

二进制级别的代码复用相比源码级别的复用已经有了很大的进步,但在二进制级别的代码复用中,dll显得太古老。想真正完美实现跨平台、

跨语言的黑盒复用,采用COM才是正确的选择。

http://blog.csdn.net/wondergdf/article/details/7870491?reload

摘: VS2010 C++ 调用 DLL (C++编写)的更多相关文章

  1. VC动态调用DLL

    1. //函数指针声明 typedef int (_stdcall MYDLLFUN)(char* _pcOut, /*INOUT*/int *_piOutBufLen, char* _pcIn, i ...

  2. Java调用C/C++编写的第三方dll动态链接库(zz)

    这里主要用的方法是JNI.在网上查资料时看到很多人说用JNI非常的复杂,不仅要看很多的文档,而且要非常熟悉C/C++编程.恐怕有很多人在看到诸如此类的评论时已经决定绕道用其他方法了.本文将做详细的介绍 ...

  3. 以前编写的inno setup脚本,涵盖了自定义安装界面,调用dll等等应用 (转)

    以前编写的inno setup脚本,涵盖了自定义安装界面,调用dll等等应用 (转) ; Script generated by the Inno Setup 脚本向导. ; SEE THE DOCU ...

  4. delphi 基础之三 编写和调用dll文件

    delphi 编写和调用dll文件   Windows 的执行文件可以划分为两种形式程序和动态连接库 (DLLs).一般程序运行是用.EXE文件,但应用程序有时也可以调用存储在DLL的函数. 在如下几 ...

  5. 关于在VB.NET中调用使用VC++编写的类库dll的一点笔记

    前言 结对作业要求一出来,我就立刻想到了把“计算核心”封装成dll,然后使用vb.net编写UI调用dll的思路.然而在实现过程中却遇到了很多的问题. 我在这个过程中是负责使用vb.net编写UI并调 ...

  6. delphi编写与调用DLL(delphi7下测试通过)

    http://blog.sina.com.cn/s/blog_4dbbf76f01000anz.html delphi编写DLL 下面在delphi中编写一个简单的dll,在该dll中只有一个max函 ...

  7. VS2010 C#调用C++ DLL文件 【转】

    http://www.soaspx.com/dotnet/csharp/csharp_20110406_7469.html 背景 在项目过程中,有时候你需要调用非C#编写的DLL文件,尤其在使用一些第 ...

  8. Delphi 7调用C语言编写的DLL

    DLL一定是要C语言导出的符号,也就是extern “C”. 当然,我们都知道DLL调用分为动态调用和静态调用. 动态调用的一般思想为,先LoadLibrary那个你想加载的DLL,然后通过GetPr ...

  9. c++builder delphi 调用dll dll编写

    c++builder动态调用dll // 定义 typedef int __stdcall MyFunction (int x, char *str); ; String dllName = &quo ...

随机推荐

  1. jquery如何判断checkbox(复选框)是否被选中(转)

    谁都知道 在html 如果一个复选框被选中 是 checked="checked". 但是我们如果用jquery alert($("#id").attr(&qu ...

  2. Cocos2d-x3.0 解压zip

    2dx3.0为我们集成了unzip库,帮助我们实现对文件的解压,但使用起来略显复杂我这里封装了一个解压工具库.分享一下. 工具类下载:http://download.csdn.net/detail/q ...

  3. java并发集合知识点(二)

    我们平时写程序需要经常用到集合类,比如ArrayList.HashMap等,但是这些集合不能够实现并发运行机制,这样在服务器上运行时就会非常的消耗资源和浪费时间,并且对这些集合进行迭代的过程中不能进行 ...

  4. bionase

    BIONASE BIONASE是一个革命性的新设备,针对过敏性鼻炎(花粉病或枯草热)以及大多其他类型的鼻炎.BIONASE能够缓解以及有效阻止与过敏性鼻炎相关的临床症状,例如:鼻塞,打喷嚏,头疼以及流 ...

  5. pm2-web

    A web based monitor for PM2. Multiple hosts With the release of 0.11 pm2 no longer uses TCP sockets ...

  6. 最好的 NMAP 扫描策略

    # 适用所有大小网络最好的 nmap 扫描策略 # 主机发现,生成存活主机列表 $ nmap -sn -T4 -oG Discovery.gnmap 192.168.56.0/24 $ grep &q ...

  7. poj 3258 River Hopscotch 题解

    [题意] 牛要到河对岸,在与河岸垂直的一条线上,河中有N块石头,给定河岸宽度L,以及每一块石头离牛所在河岸的距离, 现在去掉M块石头,要求去掉M块石头后,剩下的石头之间以及石头与河岸的最小距离的最大值 ...

  8. WCF项目中出现“目标程序集不包含服务类型”的解决办法

    如果创建新项目时(以下简称A项目)选择的是WCF相关的项目模板,并且在A项目中只定义接口而不实现接口,那么任何引用了A项目的项目,在调试时都会弹出警告框“目标程序集不包含服务类型.可能需要调整此程序集 ...

  9. go语言基础之流程控制 if语句

    Go语言支持最基本的三种程序运行结构:顺序结构.选择结构.循环结构. 顺序结构:程序按顺序执行,不发生跳转. 选择结构:依据是否满足条件,有选择的执行相应功能. 循环结构:依据条件是否满足,循环多次执 ...

  10. (转载)uCOS-II的嵌入式串口通信模块设计

    在嵌入式应用中,使用RTOS的主要原因是为了提高系统的可靠性,其次是提高开发效率.缩短开发周期.uCOS-II是一个占先式实时多任务内核,使用对象是嵌入式系统,对源代码适当裁减,很容易移植到8~32位 ...