静态库与动态库
一、介绍
库:库从本质上来说是一种代码重用的方式,即预先编译为可执行代码的二进制格式,可以被载入内存中执行。比如我们熟悉的c运行库,里面就实现了很多基本的函数,我们无需再自己写一遍,直接调用接口使用即可,库分静态库和动态库两种。
静态函数库:这类库的名字一般是xxx.lib;利用静态函数库编译成的文件比较大,因为整个函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了。当然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译。
动态函数库:这类库的名字一般是xxx.dll(也可以包含xxx.lib用于编译时候的链接处理,也可以不包含,直接动态调用,后面会讲区别);相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。
二、静态函数库
建立静态库工程
新建工程->选择WIN32项目->输入工程名testlib-》“win32应用程序向导”的“应用程序类型”选择选择静态库,勾选上“预编译头”
说明:勾选上预编译头会生成stdafx.h/stdafx.cpp,预编译头是为了能够让编译器更加快速的完成编译。
添加静态库类
菜单“项目”-》“添加类”-》类名称CTestLib
头文件
1 #pragma once 2 class CTestLib 3 { 4 public: 5 CTestLib(void); 6 ~CTestLib(void); 7 8 public: 9 int Add(int a, int b); 10 }; 11 12 //extern "C" int Sub(int a, int b); 或者 13 extern "C" 14 { 15 int Sub(int a, int b); 16 };
源文件
1 #include "StdAfx.h" 2 #include "TestLib.h" 3 4 5 CTestLib::CTestLib(void) 6 { 7 } 8 9 10 CTestLib::~CTestLib(void) 11 { 12 } 13 14 int CTestLib::Add(int a, int b) 15 { 16 return a + b; 17 } 18 19 int Sub(int a, int b) 20 { 21 return a - b; 22 }
使用lib库
有多种方式使用这样的Lib,如果我们在同一个解决方案来使用这个库,那么我们就可以在新建的工程里面设置依赖项目,这样系统会自动的为我们连接这个库文件,我们只需要包含一下提供静态库函数接口的.h文件即可。但是如果这个静态库是单独发布给其他人用的,那么我们可以设置静态库和.h的目录,添加到VC工程的目录配置,这样就可以和使用系统库一样的使用了。
2、3行 使用lib库。也可以右键点击testexe工程属性,打开属性页面板,选到链接器选项,点开链接器左边的加号,选到输入,附加依赖项里面我们输入TestLib.lib
1 #include "stdafx.h" 2 #include "TestLib.h" 3 #pragma comment(lib, "testlib.lib") 4 5 int _tmain(int argc, _TCHAR* argv[]) 6 { 7 int a = 5, b = 2; 8 CTestLib test; 9 10 printf("a + b = %d\n", test.Add(a, b)); 11 printf("a - b = %d\n", Sub(a, b)); 12 13 return 0; 14 }
三、动态库
相对于静态库,动态库的形式更灵活一些,大多情况下,我们编译动态库的时候,除了获得dll文件,依然会得到.lib文件,大多情况下,接口的定义还是需要通过.h来做。
建立动态库工程
新建工程->选择WIN32项目->输入工程名TestDll-》“win32应用程序向导”的“应用程序类型”选择选择DLL。
DllMain
动态库dll和静态库最大的区别就是,动态库是可以独立运行的文件,通俗一点讲,和可执行文件没有太大的本质区别,所以当其他可执行文件(exe或者其他dll)调用到该dll的时候,系统会执行一个入口函数,做一些初始化之类的工作,当然这个入口函数和可执行文件exe有一个最大的区别就是这个入口函数不是必须的,也就是说没有这个入口函数依然能编译dll。
DllMain的第二个参数fdwReason指明了系统调用Dll的原因,它可能是
DLL_PROCESS_ATTACH、
DLL_PROCESS_DETACH、
DLL_THREAD_ATTACH、
DLL_THREAD_DETACH。
以下从这四种情况来分析系统何时调用了DllMain。
右键对工程点击属性->配置属性->C/C++->预处理器,有一个我们关心的值:TESTDLL_EXPORTS,这个值是建立工程时候系统自动生成的,我们也可以改成自己喜欢的值,作为区分dll是导入还是导出的区别,我们先记录一下这个值。新建一个类CMyTestDll,
头文件:
1 #pragma once 2 3 #ifdef TESTDLL_EXPORTS 4 #define MY_TEST_API __declspec(dllexport) 5 #else 6 #define MY_TEST_API __declspec(dllimport) 7 #endif 8 9 class MY_TEST_API CMyTestDll 10 { 11 public: 12 CMyTestDll(void); 13 ~CMyTestDll(void); 14 15 int Add(int a, int b); 16 }; 17 18 extern "C" 19 { 20 MY_TEST_API int Sub(int a, int b); 21 };
源文件:
1 #include "StdAfx.h" 2 #include "MyTestDll.h" 3 4 5 CMyTestDll::CMyTestDll(void) 6 { 7 } 8 9 10 CMyTestDll::~CMyTestDll(void) 11 { 12 } 13 14 int CMyTestDll::Add(int a, int b) 15 { 16 return a + b; 17 } 18 19 int Sub(int a, int b) 20 { 21 return a - b; 22 }
动态库的使用
静态链接
编译会生成一个testdll.dll, testdll.lib。类似静态库的例子,我们创建一个“控制台CanDelete”,所有的操作都一样,唯一的区别就是我们多了个dll,把dll放到编译出来的exe目录,不然会提示找不到dll。
1 #include <iostream> 2 #include "MyTestDll.h" 3 using namespace std; 4 5 #pragma comment(lib, "TestDll.lib") 6 7 void main() 8 { 9 CMyTestDll test; 10 cout << test.Add(1, 3) << endl; 11 }
动态链接
动态库和静态库的编译过程都差不多,静态链接的使用方式也大同小异,但是和静态库有所区别,动态库Dll还有另外一种动态链接的方式:LoadLibrary,LoadLibraryEx,功能都一样,我们默认使用LoadLibrary来讲一下,所谓的动态载入,就是程序运行时候不必一开始就载入dll,在程序运行过程中,在需要的时候,再动态加入进来,这样我们可以不再使用.lib来做静态链接,甚至可以连.h都不用了,直接用GetProcAddress来使用,但是这样的方式一般来说,都最好只用extern“C”开头的接口.我们可以通过Microsoft Visual Studio 8\Common7\Tools\Bin里面的一个工具Depends.Exe来看看我们刚才编译出来的dll有什么内容。
#include <iostream> #include <Windows.h> using namespace std;; typedef int (*FUNC_SUB)(int a, int b); void main() { HMODULE bDll = ::LoadLibrary("TestDll.dll"); if (!bDll) { cout << "load fail\n"; } FUNC_SUB func = (FUNC_SUB)::GetProcAddress(bDll, "Sub"); if(func) { cout << "ret = " << func(1, 2); } FreeLibrary(bDll); }
在大多情况下,我们一般都是使用.h + .lib + .dll的方式来开发,这样是一个最方便的方式,但是也有类似刚才说的我们使用LoadLibrary的方式来开发,一般作为什么情况才使用呢,比如我们要做一个程序,支持外部插件,这种时候就需要LoadLibrary这种方式了,我们只需要提供插件规范(插件必须要导出某些名字的函数),供主程序使用,这样对于插件开发者来说,只需要按照规范,写dll导出相关函数,就可以实现扩展程序功能的作用了。