搞程序开发的朋友应该对Lib和Dll很熟悉,对于这两个东西,可谓是几家欢喜几家忧,喜欢的人觉得它可以封装代码,避免别人剽窃,不喜欢的人觉得它很麻烦,干嘛不直接用源文件。而特别是新手对于Lib和Dll的关系和使用完全搞不清楚。
Lib称为静态链接库(static link library),是在编译的链接期间使用的,他里面其实就是源文件的函数实现。
Dll成为动态链接库(Dynamic link library),是在程序运行时动态调用的,runtime时使用,它里面包含了源文件的函数实现、DllMain入口函数和.def文件。
先说说Lib库吧,相对来说大家对它比dll熟悉一些。
Lib库
Lib库有两种,一种就是常见的普通Lib(static Lib),还有一种大家经常下载的开源代码编译后,会产生Lib和dll,其中Lib只是Dll的附带品,是DLL导出的函数列表文件而已,暂且称之为Dynamic Lib。
两者都是二进制文件,两者都是在链接是调用的,使用static lib的exe可直接运行,使用dynamic lib的exe需要对应的dll才能运行。下来我们来看如何产生并使用一个static lib文件。
这里假设我们的工具是VS2005(包含)以上的版本,其他的工具都是大同小异的,就不做介绍了。
1.建立win32控制台工程
2.在应用程序设置的步骤,选择”静态库 static Library”
3.完成即可 (这里只是针对最简单的Dll,Win32 Application的方式稍有不同)
这样一个静态Lib库的工程就建好了。代码如下:
//////////////////////////////////////////////////////////////////////////
// Function.h
//////////////////////////////////////////////////////////////////////////
void Print();
//////////////////////////////////////////////////////////////////////////
// Function.cpp
//////////////////////////////////////////////////////////////////////////
#include "Function.h"
void Print()
{
std::cout << "Hello world!" << std::endl;
}
编译会生成一个以工程名作为名称的Lib文件。
在你的项目工程属性中包含这个Lib文件的头文件目录和Lib文件目录。
头目录包含方法:项目属性(Alt + F7) -> 配置属性 -> C/C++ -> 常规 -> 附加包含目录,里面包含你的Lib库的头文件,你可以使用绝对路径,也可以使用VS中宏表示的相对路径,建议使用相对路径。
Lib文件包含方法:项目属性(Alt + F7) -> 配置属性 -> 链接器 -> 常规 -> 附加库目录,在这里面填写你Lib文件的路径。项目属性(Alt + F7) -> 配置属性 -> 链接器 -> 常规 -> 输入,在这里面填写你Lib文件的名称,例如: Function.lib
这样你在你的代码里就可以这样使用了:
#include <stdio.h>
#include <stdlib.h>
#include "Function.h"
int _tmain(int argc, _TCHAR* argv[])
{
Print();
system("pause");
return 0;
}
这样就是一个完整生成并使用Lib库的例子。
当然了,你还可以使用#pragma comment(Lib, “LibPath”)的方法来调用Lib文件。
==============================================================
Dynamic Lib的调用方法与Static lib完全一致,唯一的区别就是使用Dynamic Lib编译出来的程序,运行时需要其对应的Dll文件。前面我们已经说过了。
DLL
下来我们好好谈谈Dll的问题,相对于Lib来说,Dll使用的频率应该是非常高的了,因为你的程序运行,系统运行等等都靠它,MS也是因为这个才导致操作系统封装的越来越好了。
Dll其实和Exe是几乎完全一样的,唯一的不一样就是Exe的入口函数式WinMain函数(console程序是main函数),而Dll是DllMain函数,其他完全是一样的。所以有人也戏称Dll是不能自己运行的Exe。
Dll创建的过程也比较简单,唯一麻烦的就是需要定义导出函数接口。
创建Dll工程过程很简单,建立win32控制台工程,在应用程序设置的步骤,选择”动态库 Dynamic Library”,完成即可。(这里只是针对最简单的Dll,Win32 Application的方式稍有不同)
定义导出函数接口有两种方式:
1.使用__declspec宏
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
//////////////////////////////////////////////////////////////////////////
// Fucntion.h
//////////////////////////////////////////////////////////////////////////
extern "C" __declspec(dllexport) void Print(void);
//////////////////////////////////////////////////////////////////////////
// Function.cpp
//////////////////////////////////////////////////////////////////////////
#include "Function.h"
void Print(void)
{
std::cout << "[Dll] Hello world!" << std::endl;
}
这里假设我们的工程名叫Function,那么 编译后会生成一个Function.dll和一个Function.lib(Dynamic lib),Dynamic lib前面已经说过,此处不再赘述了。
Function.h头文件中的
extern "C" __declspec(dllexport) void Print(void);
我们只需要清楚其中函数的名称,返回值,参数就可以了。
extern “C”表示我们要按照C语言的方式编译该函数,防止在C++工程中编译出现函数名错误,因为C++中有函数重载,所以函数名编译后可能会出现Print@1的形式;而且这样也可以让C调用C++的动态链接库;__declspec(dllexport)表示下来的函数是dll的导出函数接口。
2.使用def文件,类似于声明导出接口的方式,不过却不需要声明了,因为它专门定义了一个def文件来说明
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
//////////////////////////////////////////////////////////////////////////
// Fucntion.h
//////////////////////////////////////////////////////////////////////////
void Print(void);
//////////////////////////////////////////////////////////////////////////
// Function.cpp
//////////////////////////////////////////////////////////////////////////
#include "Function.h"
void Print(void)
{
std::cout << "[Dll] Hello world!" << std::endl;
}
//////////////////////////////////////////////////////////////////////////
// Fucntion.def
//////////////////////////////////////////////////////////////////////////
EXPORTS
Print
同样,类似于Static Lib的显式调用方法,dll也可以显式调用,前提是我们很清楚函数名、返回值、参数列表。
#include <stdlib.h>
#include "Function.h"
#include <windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
HINSTANCE hInstance = LoadLibrary("Function.dll");
typedef void(*_Print)(void);
_Print printFunction;
if (hInstance != NULL)
{
printFunction = (_Print)GetProcAddress(hInstance, "Print");
}
printFunction();
FreeLibrary(hInstance);
system("pause");
return 0;
}
当然了,有显式自然还有隐式调用了,这个时候Function.dll的伴生产物Function.lib就可以派上用场了,其使用方法和静态lib完全相同。
DLL调用的两种方法各有利弊:采用寻找DLL中函数地址的方法,优点是只要函数形参没变化,那么修改了函数实现也没关系,不需要重新编译Exe,只需要将新的DLL文件拷贝过来即可,大型项目上使用比较灵活;缺点是比较麻烦,需要定义实例,函数指针,加载DLL,释放DLL等过程。而采用Dynamic Lib的方法,优点是容易理解和接受(因为他跟静态库的调用方法类似);缺点是修改了DLL工程的任何东西都需要使用最新的Dynamic Lib重新能编译Exe。
总结:
DLL和Lib是各有千秋,使用的情况也是各不相同,不过最终还是需要大家在项目中实践到底哪种方法好,到底采用哪种类型的库,总之,一切都要按需求最优。