本文为原创,转载或其他用途请注明出处:
http://blog.youkuaiyun.com/ydbcsdn/archive/2007/10/10/1817622.aspx
http://blog.youkuaiyun.com/ydbcsdn
作者E-mail: yaodebo@163.com
本人不才, 暂时写不出什么好文, 今天, 我想在这里写一点我认为比较实用的技巧。
在项目稍微大点的时候,我们不可避免得要把某些功能模块分开来进行开发,动态链接库对我们来说是个不错的选择。
一、DLL简介
库文件有两种,一个是动态链接库,一种是静态链接库,这里只讲其中的动态链接库,本文介绍的对象是Windows 32 DLL 和 MFC扩展类DLL。
动态链接库生成后,提供给其他的动态库、静态库或应用程序进行调用。做后库文件后,需要提供给调用方这样几个文件:
1. DLL文件 —— 包含编译链接好的代码和变量
2. LIB文件 —— 包含一系列对DLL文件中资源的描述
3. H头文件 —— 声明DLL中可以被用户引用的类型、宏、变量、函数等
对调用者来说,调用DLL有隐式调用和显式调用两种不同的方法。下面分别进行简要介绍:
1) 显式调用DLL
调用者在编译链接过程中,并不需要链接和包含任何与DLL相关的文件。不过,我要预先了解DLL中导出函数的原型和函数名称,才能正确的调用DLL中的函数。例如:DLL中有这样的一个导出函数:
int MyFunction(int a, int b);
假如我们知道上述的这个函数,那么我们在程序中调用这个函数的步骤如下:
1、 调用 HMODULE LoadLibray(LPCTSTR lpszDLL)
函数功能:将DLL文件加载到应用程序的地址空间
参 数:lpszDLL 为字符串,表示要加载的DLL文件的名称(包含路径)
返 回:HMODULE 类型(即HINSTANCE类型)
例如:
HINSTANCE hIns = LoadLibrary(“MyFunc.dll”);
If(hIns == NULL)
{
AfxMessageBox(“Failed to Load MyFunc.dll!”);
}
2、调用 FRAPRO GetProcAddress(HMODULE hModule, LPCTSTR lpszProName)
函数功能:从一个模块中查找指定名称的函数,并返回函数在应用程序中的首地址。
参 数: hModule 为模块句柄(我们需要传入LoadLibrary的返回值)
lpszProName为函数的名称
我们得到指针后,需要根据我们掌握的函数的原型进行强制的指针类型转换。
例如:
// 对函数 int MyFunction(int a, int b);
HINSTANCE hIns = LoadLibrary(“MyFunc.dll”);
typedef (*int LPFUNCTION)(int, int); // 函数原型的重定义
LPFUNCTION lpFun; // 函数指针声明
lpFun = (LPFUNCTION)(GetProcAddress(hIns, MyFunction));
// 调用方法
int a, b;
int nResult = lpFun(a, b)
2) 隐式调用DLL
调用者需要包含DLL提供的3个文件—— H头文件、LIB文件、DLL文件。以下代码最直接有效:
#include “xxxx.h”
#pragma comment(lib, “xxxx.lib”)
以后,在需要调用DLL中函数和类型的地方可以直接调用函数,就如同调用windows API一样。
二、值得关注的地方
1)namespace 的用法:
DLL中可以导出很多用户自定义的函数、类型、全局变量等。但是,我们有必要注意到这样的一种情形:我们写的DLL中有一些函数,能满足我们的需要,同时,我们也需要用到其他人写的DLL,但是不幸的是,这两个DLL中有同名的函数。尽管你可能还没碰到这样的情形,但是你可能要开始留意这个问题了,那么有什么好的办法来避免吗?
我们可以使用namespace , namespace能将用户自定义的类型、变量、函数等全部归类到一起,当我们调用函数遇到冲突的时候,我们就可以显式地指定调用某个namespace中的这个函数。如 MyNameSpace::MyFunction();
现在我们的任务是:在普通DLL的基础上,进行更改,以便支持namespace 。
首先,我们需要在H文件的导出声明中添加 namespace MySpace{},并将所有要导出的函数都放在{}中。试试编译你的程序,你可能会发现,编译无法通过,提示的是很多函数都是未声明的了,为什么会这样?答案是:要调用namespace中的资源,必须先将namespace引用。引用方法:using namespace MySpace;
这时,你再编译,恭喜你,通过了!但是,你用 VC6自带的Dependence 打开这个DLL,你会发现在导出函数列表中没有任何函数了,换句话说,这个DLL没导出函数了。为什么呢?同样是语法问题:我们对这些导出函数进行实现的时候,需要进行一些改变,改成类似于C++类的函数声明方式。如函数 void MyFunction(), 应该这样写实现:
void MySpace::MyFuntion(){}
2)“生成后事件”的使用
VC的集成化开发环境提供了很多意想不到的东西,其中有一个叫“生成后事件”的项目设置选项就有实用!
VC2003中的这项设置在“项目属性”中,如果您不知道什么是“项目属性”那就关闭网页吧(开个玩笑,都快12点了,比较累)。
顾名思义,“生成后事件”就是项目执行生成工程文件之后要做的事情。对于DLL,我们就可以利用这一点,很方便地实现将最新的DLL、LIB、H文件拷贝到指定的地点,这样其他项目需要的调用库的时候就能保证调的库是最新的而且匹配好的,不用你到处去找了。
要实现这点,你需要做些什么呢?你只需要在“生成后事件”对应的编辑框里写上几句批处理语句,如;
copy .//debug//123.dll ..//Lib//123.dll
copy .//debug//123.lib ..//Lib//123.lib
copy .//123.h ..//lib//123.h
这样就可以了。然后在执行完生成dll后,就会执行这3条复制文件的语句,如果写的命令格式正确,那么在消息窗里就可以看到复制文件成功的提示了。