一、Lib与Dll的比较
静态链接库即Lib,动态链接库即Dll。他们都是代码共享的方式。所谓“静态”,就是Link的时候把Lib里
面需要的信息抽取出来安排到exe文件中,以后运行exe文件时不再需要Lib;所谓“动态”,就是exe运行的时候
依赖于Dll提供的功能。
Lib与Dll的比较如下:
(1)如果采用Lib,Lib的指令都被直接包含在最终生成的exe文件中;如果采用Dll,该Dll不必包含在最终
生成的exe文件中,exe文件执行的时候动态引用和卸载这个与exe文件独立的Dll。
(2)Lib不能再包含其他的Lib和Dll,而Dll可以再包含其他的Lib和Dll。
(3)有Dll,则一定有对应的Lib;有Lib,不一定有Dll。
Lib、Dll和exe文件是最终的目标文件,源代码和最终目标文件中过度的就是中间obj文件。实际上之所以需
要中间代码,是你不可能一次得到目标文件。比如说一个exe需要很多的cpp文件生成。而编译器一次只能编译一个
cpp文件。这样编译器编译好一个cpp以后会将其编译成obj,当所有必须要的cpp都编译成obj以后,再统一link
成所需要的exe,应该说缺少任意一个obj都会导致exe的链接失败。
二、Lib的说明
有两种lib文件:
(1)静态库lib。它包含函数的二进制代码.程序link时,被复制到output文件。这个lib文件是静态编译出
来的,索引和实现都在其中。这时不需要dll。静态编译的lib文件有好处:给用户安装时就不需要再挂动态库了。
但也有缺点,就是导致应用程序比较大,而且失去了动态库的灵活性,在版本升级时,同时要发布新的应用程序才行。
(2)动态库lib。它包含函数的描述和在DLL中的位置,也就是说,它为存放函数实现的dll提供索引功能。在
编译一个Dll工程时,可生成这个Lib。在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所
要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省
了内存资源。若要使用Lib,这是只需要一个头文件和Lib文件即可。
例如,在vs2005中建立一个静态库LibProject:
// Lib.h
#ifndef _LIB_H_
#define _LIB_H_
void PrintfHello(); // 这里无需声明为导出函数
#endif
// Lib.cpp
#include "stdafx.h"
#include "Lib.h"
#include <iostream>
using namespace std;
void PrintfHello()
{
cout << "Hello,Lib!!" << endl;
}
编译后,会生成一个LibProject.lib文件,这个文件就是上面所说的静态库Lib。当需要用到该文件时,就必须:
(1)提供一个对应的头文件告知编译器Lib里面的具体内容。
(2)允许编译器能链接Lib文件。
例如,建立另外一个工程MainProject,将Lib.h和LibProject.lib拷到当前工程目录。
// Main.cpp
#include "Lib.h"
#pragma comment (lib, "LibProject.lib") // 告知当前cpp文件链接到LibProject.lib
void main()
{
PrintfHello();
}
三、Dll的说明
Visual C++支持三种DLL,它们分别是Non-MFC DLL(非MFC动态库)、MFC Regular DLL(MFC规则
DLL)、MFC Extension DLL(MFC扩展DLL)。
非MFC动态库不采用MFC类库结构,其导出函数为标准的C接口,能被非MFC或MFC编写的应用程序所调用;MFC
规则DLL 包含一个继承自CWinApp的类,但其无消息循环;MFC扩展DLL采用MFC的动态链接版本创建,它只能被
用MFC类库所编写的应用程序所调用。本文所讲的Dll是Non-MFC DLL。
要生成Dll文件,必须先建立一个Dll工程。Dll内的函数(类)分为两种:导出函数(类)和内部函数(类)。当
将某函数(类)声明为导出函数时,会额外生成一个Lib文件(即动态库Lib)。通过Depends工具能看到Dll里面的
导出函数。
例如:在vs2005中建立一个Dll工程DllProject。
// Dll.h
#ifndef _DLL_H_
#define _DLL_H_
extern "C" void_declspec(dllexport) PrintfHello(); // 这里必须将其声明为导出函数
#endif
// Dll.cpp
#include "stdafx.h"
#include "Dll.h"
#include "iostream"
using namespace std;
void PrintfHello()
{
cout << "Hello,Dll!!" << endl;
}
编译后,会生成DllProject.lib和DllProject.dll文件。关于Dll的调用,有两种方式:
(1)需要头文件和Lib文件,由于这时候Lib是一个动态库,它提供的只是Dll文件的一些索引功能,因而调用
Dll时,不能没有Dll文件;这一点与建立Lib工程时不一样。
(2)利用API函数调用Dll。例如,建立一个MainProject工程,将DllProject.dll文件拷到当前工程目
录,这时不需要头文件和Lib文件。
// Main.cpp
#include "Windows.h"
typedef void(*pPrintfHello)(); // 宏定义函数指针
void main()
{
HMODULE hDll = LoadLibraryA("DllProject.dll");
pPrintfHello printf = (pPrintfHello )GetProcAddress(hDll, "PrintfHello");
printf ();
Sleep(INFINITE);
}
四、关于DllMain函数
建立一个Dll函数工程时,会有一个DllMain()函数,它是Dll文件被加载时的入口函数,类似于控制台的
main()、Win32的WinMain()。这个函数并不属于导出函数,而是DLL的内部函数。这意味着不能直接在应用工程
中引用DllMain函数,DllMain是自动被调用的。在前面的例子中,DLL并没有提供DllMain函数,应用程序也能
成功引用 DLL,这是因为Windows在找不到DllMain的时候,系统会从其它运行库中引入一个不做任何操作的缺省
DllMain函数版本,并不意味着 DLL可以放弃DllMain函数。
例如,建立一个Dll工程DllProject,在DllProject.cpp中的DllMian()添加代码:
// DllProject.cpp
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call) // 调用 {
case DLL_PROCESS_ATTACH :
cout << "DLL_PROCESS_ATTACH" << endl;
break;
case DLL_THREAD_ATTACH :
cout << "DLL_THREAD_ATTACH" << endl;
break;
case DLL_PROCESS_DETACH :
cout << "DLL_PROCESS_DETACH" << endl;
break;
case DLL_THREAD_DETACH :
cout << "DLL_THREAD_DETACH" << endl;
break;
}
return TRUE;
}
编译后生产DllProject.dll文件,DllMain函数在DLL被加载和卸载时被调用,在单个线程启动和终止时,
DLLMain函数也被调用,ul_reason_for_call指明了 被调用的原因。原因共有4种,即PROCESS_ATTACH、
PROCESS_DETACH、THREAD_ATTACH和 THREAD_DETACH。在MainProject工程中调用该文件:
// Main.cpp
#include <Windows.h>
typedef BOOL (*pDllMain)( HMODULE, DWORD, LPVOID);
void main()
{
HINSTANCE hDll = LoadLibraryA("DllProject.dll");
pDllMain p = (pDllMain)GetProcAddress(hDll,MAKEINTRESOURCE(1));
}
运行程序输出:
DLL_PROCESS_ATTACH
DLL_PROCESS_DETACH
五、总结
应用工程中几乎可以看到DLL中的一切,包括函数、变量以及类,这就是DLL所要提供的强大能力。只要DLL释
放这些接口,应用程序使用它就将如同使用本工程中的程序一样!