转:http://blog.csdn.net/galaxy_li/article/details/7411956git
1:神马是Dll和Lib,神马是静态连接和动态连接安全
你们都懂的,DLL就是动态连接库,LIB是静态连接库。DLL其实就是EXE,只不过没main。函数
动态连接是相对于静态连接而言的。所谓静态连接就是把函数或过程直接连接到可执行文件中,成为可执行程序中的一部分,当多个程序调用一样的函数时,内存里就会有这个函数的多个拷贝,浪费内存资源。而动态连接则是提供了一个函数的描述信息给可执行文件(并无内存拷贝),当程序被夹在到内存里开始运行的时候,系统会在底层建立DLL和应用程序之间的链接关系,当执行期间须要调用DLL函数时,系统才会真正根据连接的定位信息去执行DLL中的函数代码。ui
在WINDOWS32系统底下,每一个进程有本身的32位的线性地址空间,若一个DLL被进程使用,则该DLL首先会被调入WIN32系统的全局堆栈,而后经过内存映射文件方式映射到这个DLL的进程地址空间。若一个DLL被多个进程调用,则每一个进程都会接收到该DLL的一个映像,而非多份的拷贝。但,在WIN16系统下,每一个进程须要拥有本身的一份DLL空间,能够理解为什么静态连接没啥区别。.net
2:DLL和LIB区别和联系。线程
DLL是程序在运行阶段才须要的文件。3d
LIB是程序编译时须要连接的文件。版本控制
DLL只有一种,其中必定是函数和过程的实现。指针
LIB是有两种。若只生成LIB的话,则这个LIB是静态编译出来的,它内部包含了函数索引以及实现,这个LIB会比较大。若生成DLL的话,则也会生成一个LIB,这个LIB和刚才那个LIB不一样,它是只有函数索引,没有实现的,它很小。可是这俩LIB依然遵循上个原则,是在编译时候是须要被连接的。若不连接第一个LIB的话,在程序运行时会没法找到函数实现,当掉。若不连接第二个LIB的话,在程序运行时依然会没法找到函数实现。但第二种LIB有一种替代方式,就是在程序里,使用LoadLibrary,GetProcAddress替代第二个LIB的功能。第一种LIB生成的EXE文件会很大,由于LIB全部信息被静态连接进EXE里了。第二种LIB生成的EXE文件会比较小,由于函数过程实现依旧在DLL内。调试
(啰嗦了一堆,某志但愿你们可以明白两个LIB的区别。要再不行的话,咱们能够将静态编译的LIB称为 静态连接库。但动态编译的LIB称为 引入库。可能会比较好一些。)
静态连接LIB的优势是免除挂接动态连接库,缺点是EXE大,版本控制麻烦些。
动态连接DLL的优势是文件小,版本更换时换DLL就行了,缺点是多了点文件。动态连接如果被多个进程使用,会更加方便和节省内存。
3:为何编译DLL时总会同时生成一个LIB?这个LIB有用吗?
若咱们不是用静态连接,而使用DLL,那么咱们也须要一个LIB,这个LIB的做用是被连接到程序里,在程序运行时告诉系统你须要什么DLL文件。这个LIB里保存的是DLL的名字和输出函数入口的顺序表。它是有意义的。
固然,若咱们的应用程序里不连接这个LIB,则可使用LoadLibrary,GetProcAddress来告诉系统咱们在运行时须要怎么着DLL以及其内的函数。
4:DLL意义。
1:DLL真正实现了跨语言。各类语言均可以生成DLL,而对系统以及应用程序来讲,哪一种语言生成的DLL是没有区别的。
2:DLL有足够的封装性,对于版本更新有很大好处。由于DLL是运行期间才会使用,因此,即便DLL内函数实现有变化(只要参数和返回值不发生变化),程序是不须要进行编译的。大大提升了软件开发和维护的效率。
3:DLL被多个进程使用,由于有内存映射机制,无需占用更多内存。
5:建立DLL。(注意:某志就再也不讲解使用MFC AppWizard[dll] 方式建立DLL了。有兴趣的本身去百度。这里建立DLL只指使用Win32 Dynamic-link Library建立Non-MFC DLL。呃,DLL的三种类型就不解释了,依旧那句话:百度一下你就知道。)
每一个应用程序必须有一个main或者winmain函数做为入口,DLL同样,有本身的缺省的入口函数,就是DllMain。函数以下
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;
}
通常状况下,咱们不须要对这个缺省的入口函数进行什么修改,它就会使动态连接库获得正确的初始化。可是,当咱们的DLL须要额外分配内存或者资源的时候,或者,DLL但愿对调用本身的进程或线程进行初始化或清除的额外操做时,能够在上述代码case中加一些本身感冒的东东。(懒……不想细写了- -Orz,如今是晚上2点了,明天还一堆的事情)
DLL对于导出类和导出函数没啥不一样。只要加上 __declspec( dllexport ) 修饰函数或者类就行了。
可是有查看过DLL代码的人员都会常常见到这么一段代码
#ifdef FK_DLL_EXPORTS
#define FK_DLL __declspec( dllexport )
#else
#define FK_DLL __declspec( dllimport )
#endif
意义很明显,可是,问题是 FK_DLL_EXPORTS 这个宏是应该在哪儿定义呢?在DLL项目内,仍是在使用DLL的应用程序内?
这点某志曾迷糊好久,呵呵~其实后来想一想,仍是蛮明确的。export是导出。import是导入。对于DLL来讲,是要导出这些函数给其余应用程序使用的,因此应当定义 FK_DLL_EXPORTS 宏。对于使用DLL的应用程序来讲,是导入,是无需定义的。
使用时候也很简单。
class FK_DLL CMyDllClass{} ;
则整个类被导出。
FK_DLL void MyTestFun( int a );
则该函数被导出。
可是有时咱们能够见到这样的代码
extern "C" FK_DLL void MyTestFun2( float b );
其中extern "C"的原理就是标示该函数要求以C形式去进行编译,不要以C++形式去编译。具体的编译原理就不罗嗦了,简而言之,被extern "C"定义函数,能够被C以及其余语言进行DLL调用,而未被extern "C"定义的函数,C是没法访问DLL中这个函数的。
在VS中开发DLL还有一种方式,使用.def文件。
新建个文本文档,改后缀为FKDll.def,加入到工程里。
FKDll.def里加入如下代码
LIBRARY FKDll
EXPORTS
就能够了。其中,LIBRARY语句是说明.def文件是属于FKDll这个Dll的。EXPORTS下面是咱们须要导出的函数名。后面加的@+数字,是表示导出函数的顺序编号。这样就足够了。(详细的本身百度,好困,zzzZZZ)
6:使用DLL
使用DLL有两种方式。显式连接和隐式连接。
隐式连接很容易。直接#progam comment(lib, "FKDll.lib") 就能够。固然,也能够在项目工程->属性->连接库里加上库和路径(相对路径和绝对路径均可以)。
显式连接则麻烦些。在程序中使用LoadLibrary加载DLL,再GetProcAddress获取函数实现,在程序退出以前,调用FreeLibrary来动态释放掉连接库。
例如:
void Main()
{
typedef void (*FKDllFun1)(int a);
FKDllFun1 pFun1;
HINSTANCE hDLL = LoadLibrary("FKDll.dll"); // 若hDll为空则读取Dll失败。
pFun1 = (pFun1)GetProcAddress(hDll, "MyTestFun1" ); // 从应用程序中的DLL镜像中获取名为 MyTestFun1 的函数指针
pFun1( 100 );
FreeLibrary(hDll);
}
固然,咱们刚才.def里面还指定了导出函数的导出顺序,那么咱们能够修改里面获取函数指针那一段为
pFun1 = (pFun1)GetProcAddress(hDll, MAKEINTERSOURCE(1) ); // 1 是刚才指定的MyTestFun1函数导出顺序编号。
这样能够更快,可是别将编号记混了,会致使诡异的错误。
7:比较显式连接和隐式连接。
可能的话,尽可能使用显式连接。
显式连接能够在程序执行时动态的加载DLL和卸载DLL文件,隐式连接是作不到的。
显式连接LoadLibrary,GetProcAddress时能获知是否加载失败,咱们能够对其进行检查错误处理。而显式连接多是一个很恶劣的提示或是程序崩溃的结果。
对于有些Ex类型的增强函数,显式连接能够容许咱们找到替代方案。也包括选择D3d9.dll和OpenGL.dll时也可采用一样处理。
例如:
if( GetProcAddress( hDll, "FKDllFunEx") == NULL )
{
pFun = GetProcAddress( hDll, "FKDllFun"); // 而后使用pFun进行处理
}
8:导出类和导出函数
类和函数的导出方式上面给出了说明,本来极其相似的。
咱们说下使用导出类。
若咱们隐式的使用了一个导出类,则咱们在应用程序里继承它的时候,就如同该类就在应用程序代码里同样,无需任何处理。
例如:
class FK_DLL CMyDllClass{} ; // Dll文件内的代码
-----------------------
class CAppClass : public CMyDllClass // 应用程序内代码,无需作任何处理。
{
....
}
也能够直接使用DLL导出类
void main
{
CMyDllClass* pClass = new CMyDllClass ();
}
可是,若应用程序声明或者分类一个DLL中导出类的对象时会存在一个很讨厌的问题:这个操做会使内存跟踪系统失效,使其错误的报告内存分配和释放状况。
为解决这个问题,咱们能够给出两个接口函数对DLL导出类进行建立销毁支持,就可使内存跟踪系统正常了。例如
class FK_DLL CMyDllClass{} ;
额外增长俩函数
FK_DLL CMyDllClass* CreateMyDllClass(){ return new CMyDllClass(); }
FK_DLL void DestoryMyDllClass( CMyDllClass* p_pClass ){ delete p_pClass; }
-----------------------------------------------
上面的方法能够正确进行内存跟踪了,可是,由于DLL导出类CMyDllClass依旧是导出的状态,用户一样能够跳过咱们提供的接口直接使用。那么怎么办呢。方法是再也不对类进行DLL导出,而对类内的函数所有进行DLL导出便可,
-----------------------------------------------
可是若仅仅提供上面两个接口函数以及类内所有函数,的确功能能够实现,却没法进行类继承了。若这个类继承很重要,必须开放,那么就须要使用新的内存跟踪程序替换应用程序内的原有内存跟踪程序。或者使用下面的一个方法。(见模块9:复杂问题)
-----------------------------------------------
一样,咱们也能够发现,在不导出DLL类自己,而只导出DLL类内函数也有一些好处,一些咱们不但愿外界知道的函数能够不设置导出标记,这进一步保护了DLL内函数的安全性。
9:复杂问题。
若咱们使用LoadLibrary显式加载一个DLL,并尝试在应用程序中调用一个类内成员函数的话,不管该函数是否在头文件中有声明,VS会给出一个"unresolved external symbol(未解析的外部符号)"的错误。咱们此时能够将项目属性中的内联函数扩展选项修改成"Only __inline"或"Any Suitable"便可。但,咱们可能在调试连编的时候指望关闭内联函数扩展,那么另外一种解决方案是,将但愿导出的函数声明为虚函数,例如
class CMyDllClass
{
FK_DLL virtual void MyTestFun( int a ){ dosth(); }
// 用上面代码替换 FK_DLL void MyTestFun( int a ){ dosth(); }
}
这样作还有一个额外的好处。将导出的类成员函数设置为虚函数以后,该虚函数所在的类在应用程序中也如同被声明同样,能够接受继承。
例如如果上面的作法,应用程序就能够进行顺利继承,而没必要要求CMyDllClass 被标示为导出。(原理不知,但愿精通底层的高手协助解释。)
class CAppClass : public CMyDllClass // 应用程序内代码,无需作任何处理。
{
....
}