之前一直在纠结关于dll和lib的问题,花点时间总结一下,以后肯定会有用的。
一.简介
在编程的时候,最简单的是将源码给我们,在编程的时候,包含头文件和实现文件,但是这样做比较麻烦,而且源码也暴露了。所以,一种更加人性化的方式就是将代码编译成库函数,供我们调用。库里面是木有代码的,换句话说,库里面的代码是已经编译好了的东东,我们肉眼是看不懂的。只能通过头文件,知道库中有神马函数,然后根据头文件调用库中的函数,再直白一点,库就是封装好的黑盒子,我们只管用就好。库函数分为两种,一种是lib库,一种是dll库。
下面分别看一下两种库,并学习一下怎么编译出两种库,并且使用。
二.lib库
lib库,一般称之为静态链接库,其实lib是我们编译过程产生的obj的合集,加上了一些辅助信息,便于我们定位到函数。不过lib也分为两种,一种为纯静态链接的库(静态lib),里面有完整的函数实现(声明和实现),我们包含lib之后,编译链接的过程会将函数的代码加入目标模块,所以链接好了这种lib就没有用了。而还有一种lib是配合着dll(动态链接库)一起使用的(动态lib),这种lib里面木有代码,只是有dll相关定位信息(对实现部分的部分声明),配合链接dll的,链接完也就木有用了,而如果动态载入dll的话,则不需要这种lib配合了。
1.编译lib库
下面看一下怎么编译粗来一个lib库,一个比较简单的例子,只包含了一个类和一个全局的函数。
.h文件:
/*!
* \file LibTest.h
*
* \author puppet_master
* \date 八月 2015
*
* 测试Lib
*/
#pragma once
//写一个全局函数测试
void FuncTest();
//写一个类测试
class LibTest
{
public:
LibTest(void);
~LibTest(void);
void Show();
};
.cpp文件:
#include "stdafx.h"
#include "LibTest.h"
#include <iostream>
using namespace std;
LibTest::LibTest(void)
{
}
LibTest::~LibTest(void)
{
}
void LibTest::Show()
{
cout<<"LibTest Obj show!"<<endl;
}
void FuncTest()
{
cout<<"Func Test Show!"<<endl;
}
编译的之前我们需要改一下编译的选项,使之生成lib,而不是一贯的exe。
然后,就可以放心大胆的编译啦!在debug目录会生出来一个LibTest.lib文件。
2.使用lib库
下面使用一下刚才我们编译好的lib文件。有时候lib库或者dll库和最后我们要编译出来的exe是属于同一个解决方案的不同项目的,这种比较简单,但是有时候我们需要不同项目使用lib库或者dll,所以这次,新建一个解决方案,使用我们上面编译好的lib库。
我们新建一个解决方案,在其中新建两个文件夹,一个叫Include(包含头文件目录),一个叫Lib(库文件目录),将刚才的那个项目中的LibTest.h拷贝到Include目录中,将生成的LibTest.lib拷贝到Lib目录中。
然后设置一下这个新项目的包含目录和库目录,包含目录设定为刚才的Include目录,库目录设定为刚才的Lib目录:
注:1.为了能够整体移植,将项目拷贝到其他地方也不需要再配置环境,还是将包含的头文件和Lib文件放到项目目录下比较靠谱(前提是比较小的情况)。当然,放到外面也是可以的。VC的自带的库也就不在项目中,系统也可以找到这些系统库的位置。再比如之前的DX配置环境的时候,就是将包含目录设置到其他位置的。
2.如果直接将Lib和.h文件放到项目中也是可以的,这样就不用设置上面那步,不过不太推荐这样做,尤其是在使用第三方库的时候,这些东西还是单独放到一个目录中比较好。
下面就是使用我们的库文件啦。使用库文件需要有两样东东,第一个是库的.h文件,第二个才是lib库本身。
包含库有两种方式:
第一种是通过 项目->属性->配置属性->连接器->附加依赖项来添加。
第二种是直接通过一句话,在项目中就可以添加相应的库,如:
#pragma comment(lib, "LibTest.lib")
// Test.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "LibTest.h"
#include <windows.h>
//如果没有直接通过设置添加,可以通过这句添加库
//#pragma comment(lib, "LibTest.lib")
int _tmain(int argc, _TCHAR* argv[])
{
//测试全局函数
FuncTest();
//测试LibTest类
LibTest obj;
obj.Show();
system("pause");
return 0;
}
结果如下:
Func Test Show!
LibTest Obj show!
请按任意键继续. . .
LibTest Obj show!
请按任意键继续. . .
三.dll库
其实dll库才是比较麻烦的东东,记得小时候,从网上好不容易下载了个大型游戏,一打开,蹦出一个缺少XXX.dll的提示,然后又费了半天劲,下了个dll,塞到目录下,本以为能玩了,一打开,又蹦个框,缺另一个dll,于是一怒之下就删了游戏。
dll库,全称为dynamic link library,即动态链接库,又称为应用程序扩展,是一种更高级,更先进的库,优点很多。dll的作用其实与lib的基本作用是一样的,都是共享代码,lib比较死板,只要包含了lib,那么这个lib就被包含在最终生成的exe应用程序中了。而使用dll的话,这些东东不必被包含在exe中,而是exe可以动态的加载这些dll。这样,我们生成的exe会比较小,而且可以修改dll而不必修改整个exe。
动态链接库可以被各个程序所共享,如果有多个程序都使用这个dll的话,内存中只会有一份dll的拷贝。而且dll与编程语言和编译器无关,只需要遵循dll的接口规范和调用方式就可以相互调用。当然,更爽的是,dll本身还可以包含dll或者lib,而lib却没有这个特性。
下面看一下dll的编译和使用
1.编译dll
上面提到lib有两种,一种是静态的lib即我们上面所说的那种,而另一种lib(动态lib)则是我们在编译dll产生的附加产物。编译dll时,会有两种情况,一种情况是定义导出dll的情况,这种情况会生成lib和dll两个生成文件,而另一种是不定义这种,则只会生成一个dll文件。区别的话,在于使用,如果我们编译dll时还有lib的话,将lib也包含在要用的项目中,就可以隐式加载dll了,不需要自己写LoadLibaray来显示加载,这种情况可以像使用lib一样简单的使用dll。而另一种情况,只有dll时,我们在调用时就比较麻烦了,由于没有lib文件,就需要写一些东东,来显示加载和调用dll了。
还是上面的那个例子:
.h文件
/*!
* \file LibTest.h
*
* \author puppet_master
* \date 八月 2015
*
* 测试dll
*/
#pragma once
//通过一个编译宏控制编译流,如果定义了DLLANDLIB就按照导出dll的方式编译(生成dll和lib),否则只生成dll
#ifdef DLLANDLIB
#define DLLEXPORT _declspec(dllexport) //生成dll和lib
#else
#define DLLEXPORT //不定义这项,则默认只生成dll
#endif //
//写一个C风格的函数
extern "C" DLLEXPORT void CFuncTest();
//写一个全局函数测试
DLLEXPORT void FuncTest();
//写一个类测试
class DLLEXPORT LibTest
{
public:
LibTest(void);
~LibTest(void);
void Show();
};
.cpp文件
#include "stdafx.h"
#include "LibTest.h"
#include <iostream>
using namespace std;
LibTest::LibTest(void)
{
}
LibTest::~LibTest(void)
{
}
void LibTest::Show()
{
cout<<"LibTest Obj show!"<<endl;
}
void FuncTest()
{
cout<<"Func Test Show!"<<endl;
}
void CFuncTest()
{
cout<<"C Func Test Show"<<endl;
}
这次,我们修改一下编译属性,设置生成dll,并且暂时不添加编译宏。
然后编译,就会在debug目录下生成一个dll
看一下:
两种方法都可以,不过建议使用dllexport的方式。据说如果不用这个,可能会导致类中static变量找不到的情况。
2.使用dll
学会了编译dll,下面看一下dll怎么用。还是刚才那个Test的项目,使用dll的话有两种情况,第一种是有.h,.dll,.lib三个文件的情况,这种使用起来比较简单,除了需要将dll放到程序可以找到的目录下,其余与使用lib没有太大区别。而另一种就比较麻烦了,在没有lib的情况下,直接动态导入dll进入程序。
有lib,.h,.dll三个文件时:
首先,和上面的方法一样,将.h文件添加到包含目录,将.lib文件添加到库目录,然后将刚才编译好的dll文件放在debug目录下(项目生成exe文件的地方即可),然后按照和上面一样的方法添加头文件和lib引入。
// Test.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "LibTest.h"
#include <windows.h>
//如果没有直接通过设置添加,可以通过这句添加库
//#pragma comment(lib, "LibTest.lib")
int _tmain(int argc, _TCHAR* argv[])
{
//测试C风格函数
CFuncTest();
//测试全局函数
FuncTest();
//测试LibTest类
LibTest obj;
obj.Show();
system("pause");
return 0;
}
编译,运行,结果如下:
C Func Test Show
Func Test Show!
LibTest Obj show!
请按任意键继续. . .
Func Test Show!
LibTest Obj show!
请按任意键继续. . .
如果我们不把dll添加到exe文件所在的目录,就会出现我们下载完游戏经常出现的那种情况了:
直接使用dll:
另一种方式是动态导入dll,这种情况不需要lib和.h文件,有dll就可以。不过我们需要知道dll里面有什么函数,通过函数名找到函数。上例子:
dll文件:
/*!
* \file LibTest.h
*
* \author puppet_master
* \date 九月 2015
*
* 测试dll
*/
#pragma once
//通过一个编译宏控制编译流,如果定义了DLLANDLIB就按照导出dll的方式编译(生成dll和lib),否则只生成dll
#ifdef DLLANDLIB
#define DLLEXPORT _declspec(dllexport) //生成dll和lib
#else
#define DLLEXPORT //不定义这项,则默认只生成dll
#endif //
//写一个C风格的函数
extern "C" DLLEXPORT void CFuncTest();
cpp:
#include "stdafx.h"
#include "LibTest.h"
#include <iostream>
using namespace std;
void CFuncTest()
{
cout<<"C Func Test Show"<<endl;
}
Test文件:
// Test.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <windows.h>
using namespace std;
//定义函数指针
typedef void (*Func)(void);
int _tmain(int argc, _TCHAR* argv[])
{
//导入DLL
HINSTANCE hDll;
hDll = LoadLibrary("LibTest.dll");
if (hDll)
cout<<"Dll import successfully!"<<endl;
else
cout<<"Dll import failed!"<<endl;
//定位函数
Func cFunc = (Func)GetProcAddress(hDll, "CFuncTest");
//测试函数
if (cFunc)
cFunc();
else
cout<<"C func load failed"<<endl;
system("pause");
return 0;
}
结果:
Dll import successfully!
C Func Test Show
请按任意键继续. . .
C Func Test Show
请按任意键继续. . .
还有一个问题,应用程序如何找到DLL文件?
使用LoadLibrary显式链接,那么在函数的参数中可以指定DLL文件的完整路径;如果不指定路径,或者进行隐式链接,Windows将遵循下面的搜索顺序来定位DLL:
(1)包含EXE文件的目录
(2)工程目录
(3)Windows系统目录
(4)Windows目录
(5)列在Path环境变量中的一系列目录
使用LoadLibrary显式链接,那么在函数的参数中可以指定DLL文件的完整路径;如果不指定路径,或者进行隐式链接,Windows将遵循下面的搜索顺序来定位DLL:
(1)包含EXE文件的目录
(2)工程目录
(3)Windows系统目录
(4)Windows目录
(5)列在Path环境变量中的一系列目录
关于怎么动态加载一个含有类的dll,暂时还是不会。不过看到网上有文章说把类封装到函数中,提供各种接口。感觉好麻烦,目前打算还是使用.h文件静态加载dll吧。
参考链接: