1。为什么需要dll

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

源码公开;很容易与程序员的“正常”代码发生命名冲突;多份造成存储浪费;功能模块更新困难。

其实,以上四点可以概括为“源代码暴露”导致“代码耦合严重”。为了弥补这些缺点,“二进制级”代码复用被提出。采用二进制级别的代码复用在一定程度上隐藏了源代码,对缓解代码耦合现象起到了一定的作用。这种重用称为“黑盒重用”。

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

与白盒复用相比,.dll很大程度上弥补了以上四大缺点。 .dll是一个二进制文件,因此源代码是隐藏的;如果使用“显式调用”(后面会提到),一般不会发生命名冲突;由于.dll是动态链接到应用程序的,因此它在链接和生成程序时不会被原封不动地复制; .dll文件相对独立存在,因此更新功能模块是可行的。

注:实现“黑盒复用”的方式不仅仅是dll,还可以是静态链接库,甚至更高级的COM组件。本文只讨论dll。

2。创建dll

下面通过一个简单的例子来说明如何创建dll。本示例使用VS2010,使用C++编程语言。具体步骤如下。

通过“起始页”或“文件”菜单栏创建一个新项目,会弹出“新建项目”对话框。选择Win32项目向导,项目名称为CreateDLL,解决方案名称为DLLTEST(注意勾选Createdirectories for Solution),点击确定,然后点击下一步,进入应用程序设置,选择应用程序类型为dll,勾选“导出符号”,单击完成。完成此步骤后,您将在VS界面左侧的解决方案资源管理器中看到向导自动生成的文件列表,如图1所示。

图1 向导自动生成的文件列表

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

//?CreateDLL.cpp?:?定义DLL应用程序的导出函数。??//????#include?"stdafx.h"??#include?" CreateDLL.h"??????//?这是一个导出变量的示例?CREATEDLL_API?int?nCreateDLL?=?0;????//?这是?导出函数的示例。??CREATEDLL_API?int?fnCreateDLL(void)??{??????return?42;??}????//?这是?已经导出的类的构造函数。??//?请参阅 CreateDLL.h 了解该类的定义??CCreateDLL::CCreateDLL()??{??? ???返回;??}??

这里有3种类型的例子,分别是导出变量nCreateDLL、导出函数fnCreateDLL和导出类CCreateDLL。为简单起见,本例中仅考虑导出函数。将CreateDLL.h文件修改为:

#ifdef?CREATEDLL_EXPORTS??#define?CREATEDLL_API?__declspec(dllexport)??#else??#define?CREATEDLL_API?__declspec(dllimport)??#endif???CREATEDLL_API?void?printMax(int&,int& );??CREATEDLL_API?void?printMax(int&,int&,int&);??

修改CreateDLL.cpp文件为:?

CREATEDLL_API?void?printMax(int&?a,int&?b)??{??????std::cout<<"Among?("<b?a:b)<<"\n";??}??CREATEDLL_API?void?printMax(int&?a,int&?b,int&?c)??{??????std::cout<<"Among?("<b?a:b)>c )?(a>b?a:b):c)<<"\n";??}??

不难发现,printMax函数的作用就是打印出两个整数或三个整数的最大值。需要注意的是,这里故意使用同名函数来引入导出函数的修改名称,这将在第 4 节中详细解释。

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

?图2 CreateDLL.dll成功生成

3。使用dll

本示例使用“显式调用”方法的 CreateDLL.dll。显式调用方式与“隐式调用”相比,有优点也有缺点。显式调用只需要一个.dll文件,更新模块更加灵活方便;相反,程序员需要做的事情更多,使用方法也更复杂。

在解决方案资源管理器中右键单击解决方案‘DLLTEST’,在弹出的菜单中选择Add->New Project,选择Win32 Console Application,输入项目名称UseDLL,点击OK,然后点击Next,查看Application Settings界面选择 EmptyProject 并单击完成。右键单击项目 UseDLL 并将源文件 UseDLL.cpp 添加到其中。完成此操作后,Solution Explorer 中的信息如图 3 所示。

图 3 将项目 UseDLL 添加到解决方案“DLLTEST”

编写UseDLL.cpp的代码是:

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

但是,这个程序仍然存在Bug。编译运行,结果如图4所示。

???????????????图4 UseDLL运行结果

这不是预期的结果。事实上,如第2节所述,导致此错误的原因是修改了导出函数的名称。虽然两个printMax函数在CreateDLL.cpp中具有相同的名称,但在dll二进制文件中,经过编译器的“处理”后,它们实际上具有不同的名称。这也是函数重载机制得以实现的技术支撑。

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

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

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

const?char*?funName1?=?"?printMax@@YAXAAH0@Z";??const?char*?funName2?=?"?printMax@@YAXAAH00@Z";??

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

?图6 UseDLL正常运行

4。 dll导出函数名称标准化

创建和使用dll并不复杂。看完前三节,相信读者会有这样的体会。不过,有一个问题仍然值得思考:导出函数的修改名称太过“奇怪”,给dll的使用带来了不便。导出函数的修改名称能否更规范一些?

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

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

LIBRARY?CreateDLL??EXPORTS??pMaxA2?=??printMax@@YAXAAH0@Z??pMaxA3?=??printMax@@YAXAAH00@Z??

重建工程CreateDLL,再次使用dumpbin查看CreateDLL.dll导出的函数名。结果如图7所示。

图7标准化函数名,奇怪的修改名依然存在

预期的结果出现了,但仍然存在小缺陷:奇怪的修改名称仍然存在。这些不太标准化的修饰名能去掉吗?当然有可能。只需更改 #define CREATEDLL_API __declspec(dllexport) 即可?在 CreateDLL.h 中#define CREATEDLL_API。修改后重新编译生成CreateDLL.dll,使用dumpbin查看导出的函数名。结果如图8所示。

图8标准化函数名称,去掉奇怪的修饰名称???

回到UseDLL.cpp,修改funName:

const?char*?funName1?=?"pMaxA2";??const?char*?funName2?=?"pMaxA3";??

重新编译运行UseDLL,结果正确,类似图6。 5.dll的缺点

动态链接库虽然在一定程度上实现了“黑匣子复用”,但仍然存在很多不足。笔者可以想到以下几点。

dll节省了编译时间,但相应地延长了运行时间,因为在使用dll的导出函数时,不仅要加载dll,而且程序会在模块之间跳转,降低了缓存命中率。如果使用隐式调用,仍然需要.h、.lib、.dll文件(“三件套”),并且无法有效支持模块更新。虽然显式调用很好地支持模块更新,但它无法导出类和变量。 dll 不支持模板。

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

?一般情况下,创建或添加时选择“Windows应用程序”或“控制台应用程序”,结果会编译成exe,选择“类库”时,会编译成dll。您还可以在项目属性中更改其输出类型,如下所示:

???????下面是创建 dll 并引用它的示例。 ?????? 1、新建一个项目,选择类库,命名为DllTest。然后编写一个包含一些方法的类。为了突出主题,作为例子,我写了一个简单的类,如下:

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace DllTest{ // 求两个数字或三个数字中的最大值 public static class TestClass { public static int GetMax(int a, int b) { return (a > b ? a : b); } } public static int GetMax(int a, int b, int c) { return ((a > b ? a : b) > c ? (a > b ? a : b) : c); } } }}

???????点击“生成”后,bin\debug文件夹中会出现一个与工程名同名的dll文件

??????? 2. 创建一个新项目(也可以构建一个新的解决方案)并将其命名为DllRef。此时不要选择类库类型。选择 Win 应用程序或控制台,然后添加对生成的 dll 文件的 A 引用并使用其命名空间。

?

???????这时这个项目的bin\Debug文件夹中也出现了一个dll文件,就是我们引用的那个。 ??????编写相关调用语句:

使用 System;使用 System.Collections.Generic;使用 System.Linq;使用 System.Text;使用 DllTest;命名空间 DllRef{ 类 Program { static void Main(string[] args) { Console.WriteLine(TestClass.GetMax( 5, 6)); Console.WriteLine(TestClass.GetMax(7, 8, 9)); } }}

???????设置第二个项目为启动项,试运行成功。也就是说,在我们的新项目中,我们使用的是dll中封装的类。 ???? Dll是一个程序集,只要成功引用它并使用它的命名空间,就可以被不同的程序重复调用。

来源:?http://www.sxzhongrui.com/1982582.html

来自 Wiz 笔记(Wiz)

转载于:https://www.sxzhongrui.com/nkyhq/p/5301604.html

1。首先创建生成dll的项目:打开VS2010,创建dll项目有两种方式,基于MFC DLL和基于Win32控制台应用程序。这里我们选择基于Win32控制台构建。

a。文件--新建--项目(项目名为myAPI)--Visual C++--Win32--选择Win32控制台应用程序;

b。下一步如下图所示。选择 DLL 作为程序类型。如果没有特殊需要,选择一个空项目来完成。项目已创建;

2。定义头文件:在工程中添加头文件myAPI.h,内部添加如下代码,

#ifndef _DLL_API

#define _DLL_API _declspec(dllexport)

#其他

#define _DLL_API _declspec(dllimport)

#endif

_DLL_API int ADD(int a,int b);

内部定义了一个ADD()函数接口。如果需要添加其他功能接口,可以继续定义,比如

_DLL_API int MINUS(int a,int b);

_DLL_API int otherfunc(int,int,int);

3。定义源文件:将对应的源文件myAPI.cpp添加到工程中。然后在源文件中定义函数,内部添加如下代码,

#include "myAPI.h"