VS2010制作dll

一、为什么需要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)
#endif

CREATEDLL_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导出函数名为[email protected]@[email protected]和[email protected]@[email protected]。它们分别对应着三个整数的printMax和两个整数的printMax。因此,Use.DLL中funName应当相应修改为:

[cpp] view plaincopyprint?

  1. const char* funName1 = "[email protected]@[email protected]";
  2. const char* funName2 = “[email protected]@[email protected]”;
const char* funName1 = "[email protected]@[email protected]";
const char* funName2 = “[email protected]@[email protected]”;

修改之后,再次编译运行,结果正确,如图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 = [email protected]@[email protected]
  4. pMaxA3 = [email protected]@[email protected]
LIBRARY CreateDLL
EXPORTS
pMaxA2 = [email protected]@[email protected]
pMaxA3 = [email protected]@[email protected]

重新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编译成 [email protected]@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

VS2010制作dll

时间: 2024-11-08 04:16:28

VS2010制作dll的相关文章

利用vs2010制作C语言 dll文件,并在其它程序中调用该dll文件

一.为什么需要dll 代码复用是提高软件开发 效率的重要途径.一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用.比较常见的例子是各种应用程序框架, 如ATL.MFC等,它们都以源代码的形式发布.由于这种复用是“源码级别”的,源代码完全暴露给了程序员,因而称之为“白盒复用”.“白盒复用”的缺点 比较多,总结起来有4点. 暴露了源代码: 容易与程序员的“普通”代码发生命名冲突: 多份拷贝,造成存储浪费: 更新功能模块比较困难. 实际上,以上4点概括起来就是

WIN7+VS2010 制作与调用DLL

***************************************转载请注明出处:http://blog.csdn.net/lttree******************************************** 嗯哼~之前帮老师做的那个模块,不用做了=  =. 做了近十天,结果跑了13分钟 人家,秒级别的,甚至毫秒级的速度.... 哎.... 跪拜ING,2000+ X 2000+阶矩阵,有乘法,点乘,转置,求逆,一系列操作, 而且,乘法至少要进行40+次,求逆也要进行

Delphi制作DLL

一.开使你的第一个DLL专案 1.File->Close all->File->New﹝DLL﹞ 代码: //自动产生Code如下 library Project2; //这有段废话 uses SysUtils, Classes; {$R *.RES} begin end. 2.加个Func进来: 代码: library Project2; uses SysUtils, Classes; Function MyMax ( X , Y : integer ) : integer ; std

使用JNI,vs2010 生成dll, JSP中调用dll

JNI是Java Native Interface的缩写,中文为JAVA本地调用.从Java1.1开始,Java Native Interface(JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互.JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了. 主要运行过程:创建Java文件---->使用 javah 编译生成相应的头文件供vs2010 中使用--->使用vs2010 编译生成dll文件

MyElipse6.5环境下java调用vs2010动态链接库DLL人脸检测

Java调用C++动态链接库的网络上的文章也很多,但是还是有个别的问题没有提到,导致操作起来还是难度较大,关键是程序的疑难杂症不好治. 准备工具:vs2010,java1.6,MyElipse6.5,opencv2.4.6(其他版本请留意程序中的版本号),摄像头. 操作流程,流水式操作: 1.先建立文件FaceDetect.java文件并通过指令生成.h头文件 FaceDetect.java的代码: public class FaceDetect { static { System.loadLi

制作dll自动注册工具

记录一个简单的dll自动注册工具制作:主要用到的是DllRegisterServer()方法,其实我们平常注册dll文件内部都会调用这个方法. 这里我就直接写在主程序里面了,需要注意的地方也直接在代码中标明. /// <summary> /// 应用程序的主入口点. /// </summary> const string strdll ="DCIModelApp.dll"; [DllImport(strdll)] //这里的参数只能是常量,将该工具与要注册的dl

mingw制作dll

1.配置mingw环境,下载使用的mingw下的库 windows和linux中都可以 (windows中gxx命令) (linux中i686-w64-mingw32-gxx和x86-w64-mingw32-gxx) 2.dll的头文件 #ifndef _DLLTEST_H #define _DLLTEST_H #include <stdio.h> #define EXPORT __declspec(dllexport) extern "C" { int EXPORT my

vs2010生成Dll文件并引用dll(C#)

1.创建新C#控制台应用程序,项目命名createBll,打开Program.cs重命名为TestA(可以不重命名)并修改代码,如图: 写好后,可以写其它的类.cs文件 2.完成后,点击菜单栏的“项目”,下拉单选择“createDll属性”,打开如图窗口 在输出类型选择“类库”,然后关掉该窗口,生成解决方案,即生成了creatBll.dll文件,在项目下createBll\createBll\bin\Debug\createBll.dll目录下. 3.引用Dll文件,新建项目referenceD

VS2010中dll不可用问题

最近做项目的时候,深圳那边提供了一个算法.算法在那边跑的好的很,但是在我这边怎么跑都跑不起来,总是报错:说找不到dll. 1.第一种想法:找不到dll,是不是dll放的位置不对.找了一下目录,导入的路径与所在文件目录对应.排除! 2.第二种想法:之前我同学说过,这里面会涉及到编译的问题.就是你的dll编译的是多少位,而你的项目编译的是多少位. 这就是我们经常在下软件的时候会涉及到32位的还是64的问题一样.64位的软件在32位上跑不起来. 一般的话选择Any CPU 较好.