VS2010制作dll
一、为什么需要dll
代码复用是提高软件开发效率的重要途径。一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用。比较常见的例子是各种应用程序框架,如ATL、MFC等,它们都以源代码的形式发布。由于这种复用是“源码级别”的,源代码完全暴露给了程序员,因而称之为“白盒复用”。“白盒复用”的缺点比较多,总结起来有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的代码。
- // 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;
- }
// 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文件为:
- #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&);
#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 ("<<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";
- }
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的代码为:
- /*--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;
- }
/*--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应当相应修改为:
- const char* funName1 = "?printMax@@YAXAAH0@Z";
- 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文件:
- LIBRARY CreateDLL
- EXPORTS
- pMaxA2 = ?printMax@@YAXAAH0@Z
- 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:
- const char* funName1 = "pMaxA2";
- const char* funName2 = "pMaxA3";
const char* funName1 = "pMaxA2";
const char* funName2 = "pMaxA3";
重新编译运行UseDLL,结果正确,与图6类似。 五、dll的不足
动态链接库虽然一定程度上实现了“黑盒复用”,但仍存在着诸多不足,笔者能够想到的有下面几点。
- dll节省了编译期的时间,但相应延长了运行期的时间,因为在使用dll的导出函数时,不但要加载dll,而且程序将会在模块间跳转,降低了cache的命中率。
- 若采用隐式调用,仍然需要.h、.lib、.dll文件(“三件套”),并不能有效支持模块的更新。
- 显式调用虽然很好地支持模块的更新,但却不能导出类和变量。
- 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
VS2010制作dll的更多相关文章
- Delphi制作DLL
一.开使你的第一个DLL专案 1.File->Close all->File->New﹝DLL﹞ 代码: //自动产生Code如下 library Project2; //这有段废话 ...
- 用Delphi制作DLL
一.开使你的第一个DLL专案 1.File->Close all->File->New﹝DLL﹞代码: //自动产生Code如下 library Project2; //这有段 ...
- 如何寫一個自定義控件/vs2010生成Dll文件并引用dll(C#)
1.最簡單的例子 首先你先新建->項目->類庫.然後右鍵項目.添加一個用戶控件.設置其用戶控件繼承button. egg: namespace ClassLibrary1{ publ ...
- 使用C库制作DLL
一.用C编写制作 DLL 如下图所示,是在C++的基础上新建的项目工程: 新建项目的工程文件中有.cpp文件. 由于我们是用C库制作的DLL,显然用C++来编写的是不合适的,我为什么用C库,而不用C+ ...
- 使用mingw制作dll文件
使用mingw制作dll文件 安装mingw 准备math.c文件 //math.c #include<stdio.h> int add(int a,int b){ return a+b; ...
- Visual C# 制作DLL文件
一.制作.dll1.首先创建一个新类库工程文件 文件->新建->项目->Visual C#->类库.填入工程文件名称,并且选择文件要存放的目录. 2.工程文件 将Class1 ...
- VS2010制作安装程序
转自(http://blog.csdn.net/wenmang1977/article/details/7733685) 序 前些天想写一下制作安装程序,由于要写的内容比较多,一拖再拖,不过坚持就是胜 ...
- 制作dll自动注册工具
记录一个简单的dll自动注册工具制作:主要用到的是DllRegisterServer()方法,其实我们平常注册dll文件内部都会调用这个方法. 这里我就直接写在主程序里面了,需要注意的地方也直接在代码 ...
- VS2010制作网站自定义安装程序 转
最近在把一个网站打包成安装程序,这方面的文章网上有很多,也看了不少,但因为开发环境的不同,遇到了一些问题,便写下这篇文章记下整个流程(有很多资源都来自互联网,由于条目颇多,所以无法说明其来处,敬请谅解 ...
随机推荐
- angular路由(自带路由篇)
一.angular路由是什么? 为了实现SPA多视图的切换的效果,其原理可简述为每个 URL 都有对应的视图和控制器.所以当我们给url后面拼上不同的参数就能通过路由实现不同视图的切换. 二.文件总览 ...
- 2229: [Zjoi2011]最小割(最小割树)
Description 小白在图论课上学到了一个新的概念——最小割,下课后小白在笔记本上写下了如下这段话: “对于一个图,某个对图中结点的划分将图中所有结点分成两个部分,如果结点s,t不在同一个部分中 ...
- 03010_防止SQL注入
1.预处理对象 (1)使用PreparedStatement预处理对象时,建议每条sql语句所有的实际参数,都使用逗号分隔: String sql = "insert into sort(s ...
- postgresql 查看单个表大小
3中方法,不论什么一个都行 方法一 ,查一个表 select pg_size_pretty(pg_relation_size('table_name')); 方法二 ,查出全部表并按大小排序 SELE ...
- AQS -> AbstractQueuedSynchronizer
前言 : 先说说这个 CLH锁: 加锁 1. 创建一个的需要获取锁的 Node 2. 通过 CAS操作 让自己 成为这个尾部的节点,然后令 设置自己的pre 3. 自旋,直到pre节点释放 释放: 1 ...
- 18/9/16牛客网提高组Day2
牛客网提高组Day2 T1 方差 第一眼看就知道要打暴力啊,然而并没有想到去化简式子... 可能因为昨晚没睡好,今天上午困死 导致暴力打了一个半小时,还不对... #include <algor ...
- mIsFunui-判断Funui方法
1.有时候我们根据自己的需要,修改了frameword下的代码,但是,我们又不希望影响第三方,这时候我们就可以在修改处添加一个我们自己的标志位,如,mIsFunui 它是定义在我们自定义的theme里 ...
- bootstrap课程10 从外部引入视频到页面用什么标签
bootstrap课程10 从外部引入视频到页面用什么标签 一.总结 一句话总结:a.iframe标签:b.embed标签:c.video标签 1.bootstrap具有响应式特性的嵌入内容如何实现? ...
- 1.IntelliJ IDEA搭建SpringBoot的小Demo
转自:http://www.cnblogs.com/weizaibug/p/6657077.html 首先简单介绍下Spring Boot,来自度娘百科:Spring Boot是由Pivotal团队提 ...
- Android Notification.setLatestEventInfo弃用和Notification.Builder用法
今天在学习小米便签的源码的时候,至于源码的地址,http://m.blog.csdn.net/article/details?id=50544248 ,里面有好多github的开源项目,打开项目,报错 ...