WINDOWS的三种DLL结构
在VC的创建工程向导中,我们可有如下3种生成DLL的方式。
一:常规的win32 动态连接库:Win32 Dynamic-Link Library
这是常规的dll,不支持MFC。支持C接口的win32 Api。
空说无益,还是以具体的例子来说要好一些。
我们采用Win32 Dynamic-Link Library方式创建一个名为dllDemo1的工程。在创建工程的step 1中,我们选择“A dll exports some symbols"。
这样我们的工程中,就可以得到4个文件。分别是StdAfx.h ,StdAfx.cpp ,dllDemo1.h, dllDemo1.cpp。
StdAfx.h 和StdAfx.cpp中包含了windows的一些头文件,暂时不管它们,后面两种情况类似。但是要记住不要删除它们,否则,程序编译会出错。
把注意力集中在dllDemo1.h, dllDemo1.cpp上。
下面是它们的代码。
//----------dllDemo1.h------------------
#ifdef DLLDEMO1_EXPORTS
#define DLLDEMO1_API __declspec(dllexport)
#else
#define DLLDEMO1_API __declspec(dllimport)
#endif
//------注意看看上面的宏定义
class DLLDEMO1_API CDllDemo1
{//这是一个要被输出的类
public:
CDllDemo1(void);
double add(double,double);
double z;
private:
double x;
double y;
};
extern DLLDEMO1_API int nDllDemo1;//要被输出的一个变量
DLLDEMO1_API char *fnDllDemo1(void);//要被输出的一个函数
//-------dllDemo1.cpp------------
#include "stdafx.h"
#include "dllDemo1.h"
BOOL APIENTRY DllMain( HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
DLLDEMO1_API int nDllDemo1=19810801;
DLLDEMO1_API char *fnDllDemo1(void)
{
char *str="我被人家调用了";
return str;
}
CDllDemo1: :CDllDemo1()
{
x=1;
y=1;
return;
}
double CDllDemo1:: add(double,double)
{
return x+y;
}
工程dllDemo1经过编译连接之后,在它的debug或者release文件夹下可以看到dllDemo.dll和dllDemo.lib文件。这个动态连接库要输出3个东西:一个整型的变量,一个类,还有一个函数。
这里先撇去它们的具体实现过程,下面得创建一个基于对话框的MFC exe工程来调用这个dll。
在这个对话框的OK按钮的消息函数里显示dllDemo.dll所输出的那三个东西。
void CImportDllDemo1Dlg: :OnImportDll()
{
CString testStr;
CDllDemo1 DllExportClass;
DllExportClass.z=DllExportClass.add();
testStr.Format("CDllDemo.z=%f\n nDllDemo1=%d\n fnDllDemo1():%s",
DllExportClass.z,nDllDemo1,fnDllDemo1());
AfxMessageBox((LPCTSTR)testStr);
}
这个测试dllDemo.dll的对话框程序如果想调用DLL中输出的东西,那么它在被编译之前,还需要营造一个调用环境,用白话文说,就是修一条通往DLL的道路,因为前面谈及动态连接库时就讲过了,动态连接库是被放在内存的公共区域中的,要想调用它提供的函数或者变量,那么必须得有一条进入它的道路。书上常把这个过程叫做DLL的加载(load),我觉得不是加载,加载看起来象是把DLL拿到我们的进程中的意思。还是以食堂的例子来做比方,现在我们的食堂就是一个动态连接库,食堂做的饭菜,就是我们要输出的变量或者函数。现在我们应该明白是1000个人去食堂就餐,而不是人家把饭送到这1000个人的手中。食堂就在那里,饭菜就在食堂里,想吃饭就要寻找或者创建一条通往食堂的路,老老实实地走过去吧。
如果从程序员的角度来看,那么这条道路的实现有两种:
(1):政府出钱出人来修路。书上讲这就是“隐式加载”,我想叫它“隐式访问”。这就象是你在自己的房间里睡到了中午,想吃午餐的时候,一推开房门,就可以看到一条通往食堂的路了,你并没有为这条路的修建出过什么力气。是爱护民众的政府给你修的!对于WINDOWS下的程序员,自然WINDOWS系统就是政府。然后你要做的就是别管这条路是怎么来的,只管去食堂吃饭就是了。当然,政府给你修了路,不会再给你弄个车,怎么走,那是你自己的事。所以程序员在隐式加载DLL时,还需要做一点点工作!!!
在vc++6的工程 |设置(Alt+F7)对话框的Link选项卡中,将伴随dll产生的lib文件加到连接项中。还要在工程文件中#include dll的头文件!!!前面工程dllDemo1经过编译连接之后,在它的debug或者release文件夹下可以看到dllDemo.dll和dllDemo.lib文件,这里,就把dllDemo.lib加到你的MFC对话框工程的连接项中。然后在你的对话框.cpp文件中#include"dllDemo.h“..记着,还要把dll文件放到你的对话框程序所在的文件夹中,不然程序运行之后,会找不到它要使用的DLL在磁盘的哪个地方。如果不怕自己的系统变乱,把DLL放到WINDOWS/System32下也可以。
一定要注意这些文件的路径,要让对话框程序在编译时,能够找到它们!!!
进程加载并访问DLL时,Windows寻找相应DLL的次序如下:
在当前工作盘中吗?
Windows目录下?;GetWindowsDirectory( )函数可提供该目录的路径名。
Windows系统目录,即System子目录?调用GetSystemDiretory( )函数可获得这个目录的路径名。
DOS的PATH命令中罗列的所有目录?
网络中映象的目录列表中的全部目录?
隐式的访问方法还有一种,就是让VC++6自己来做。限于篇幅,不讲这个了。
(2)自己出钱去力修路,我的叫法是“显示访问”
假如你不放心政府搞的工程,那么就自立更生,把政府为你做的事情揽过来自己做,使用修路的工具,自己开拓一条通往食堂的路。表现在程序上,就是使用windows提供的3个接口函数来实现调用DLL的过程。
过程如下:
load DLL,WINDOWS32提供了这么一个API:LoadLibrary.
进入你需要的dll导出的函数。也有这么一个API:GetProcAddress.
最后不想再用DLL了,就释放掉。是这么一个API :FreeLibrary.
具体过程如下:
HANDLE hLibrary;
FARPROC lpFunc;
int PortValue;
hLibrary=LoadLibrary("ORTDLL.DLL"); //加载DLL
lpFunc=GetProcAddress(hLibrary,"ortIn"); //检取PortIn函数地址
if(lpFunc!=(FARPROC)NULL) //检取成功则调用
PortValue=(*lpFunc)(port); //读port端口的值
FreeLibrary(hLibrary); //释放占用的内存
程序代码见附件: |