静态连接库在程序编译连接过程当中就导入lib文件而且包含在生成的exe文件里,而动态连接库DLL是在程序运行中由程序加载和卸载的,也就是说它是动态的,固然动态连接库DLL也能够静态加载当作静态来用;windows
静态连接库使用方便直接,但程序内存占用大、使用不灵活,而动态连接库使用灵活但加载须要时间。ide
静态连接库函数
本文使用VC++6.0集成工具,新建一个Win32 Static Library工程,再新建一个以下cpp文件,完成构建以后能够获得一个lib文件,这就是以后的应用程序要用到的文件;工具
//--********* libTest.cpp **********--//
//----------- 声 明 一 个 函 数,并 按 C 方 式 编 译 连 接 ---------
extern "C" int getNumber();
int getNumber() { return 23; }
构建以后,静态链接库就算建立完成了,在lib文件中能够找到"_getNumber",这就是"C"编译方式的函数名;若是声明的函数名以前加上__stdcall",spa
即声明:int __stdcall getNumber();那么lib文件中能够见到"?getNumber@@YGHHH@Z",这就是C++编译方式的函数名(方便函数重载)。另外的以__cdecl(缺省)和__fastcall编译以后的命名方式也相似。设计
而后新建一个工程,把头文件libTest.h和库文件libTest.lib复制到新工程文件夹下,主程序代码以下:3d
#include <stdio.h> #include "libTest.h" //--------- 把 要 用 到 的 外 部 链 接 文 件 和 本 地 obj 文 件 一 同 构 建 ------
//--------- 如 果 不 复 制 ,则 这 里 使 用 全 路 径 指 向 库 工 程 的 lib --- #pragma comment(lib,"libTest.lib") int main(int argc, char* argv[]) { printf("Number: %d\n", getNumber()); return 0; }
程序运行结果以下:指针
动态连接库code
/*** 新建 一 个 dynamic~link Libbrary 工 程 dllTest *******
**** 添 加 一 个 dllTest.cpp 文 件 *****
*/
//-------------- 声 明 并 导 出 函 数 -------
extern "C" __declspec(dllexport) int add(int x, int y);
//__declspec(dllexport)int __stdcall add(int,int);
int add(int x, int y) { return x + y; }
建立一个动态连接库时,须要把其中的方法导出,并生成dll文件,上述的__declspec(dllexport)是一种导出方式;对象
或者能够用def文件导出,新建一def文件,并添加文件到工程就好了,其所有的代码以下:
LIBRARY dllTest EXPORTS add @ 1
其中add是函数名,1是序号;这样构建以后也能够获得一个dll文件。
新建一个应用工程,把生成的dll文件复制到工程文件夹下,主函数以下:
#include <stdio.h> #include <windows.h> //------------- 根 据 之 前 导 出 的 形 式 定 义 一 个 函 数 指 针 类 型 ----------- typedef int (*lpAddFun)(int,int); //typedef int (__stdcall *lpAddFun)(int,int);
int main(int argc, char* argv[]) {
//-------------- 用 定 义 的 函 数 指 针 类 型 创 建 一 个 函 数 ------ lpAddFun add;
//--------------- 加 载 动 态 链 接 库 --------- HINSTANCE hDll = LoadLibrary("dllTest.dll"); if(hDll) {
//----------------- 可 以 用 函 数 名 导 入,也 可 以 用 序 号 来 导 入--------
add = (lpAddFun)GetProcAddress(hDll, "add");
//add = (lpAddFun)GetProcAddress(hDll, MAKEINTRESOURCE(1)); if(add) printf("5 + 7 = %d\n", add(5, 7)); //----------------- 卸 载 链 接 库 ----------- FreeLibrary(hDll); } return 0; }
程序运行结果以下:
dll的静态调用方式
动态连接库的建立方式相同,可是使用时,须要复制dll文件、lib文件两个文件到应用工程文件夹下,主函数代码以下:
#include <stdio.h>
#pragma comment(lib, "dllTest.lib")
//-------------- 以 "C" 方 式 导 出 的 函 数 ,这 里 就 要 以 "C" 的 方 式 导 入 ---------- //-------------- 或 者 导 出 时 不 加 extern "C" ,这 里 也 不 加( 缺 省 __cdecl ) ------------- //-------------- 也 就 是 说 导 入 和 导 出 的 方 式 必 须 一 致(注意!) ------------------ extern "C" __declspec(dllimport) int add(int, int);
//__declspec(dllimport)int __stdcall add(int,int);
int main() { printf("12 + 25 = %d\n", add(12, 25)); return 0; }
两种调用方式的对比:
(1) 静态调用的add(),是经过dllTest.lib文件索引到dllTest.dll文件中的add();
(2) 动态调用方式是在程序运行时,直接加载dll文件,导出其中的add()方法并给函数指针赋值,经过函数指针调用函数。
函数DllMain
Windows加载dll时须要一个入口函数DllMain(),当dll文件中没有DllMain()函数时,会调用一个缺省的无任何操做的DllMain(),它是由系统调用的,不能手动调用。
#include "dllTest.h" #include <stdio.h> #include <windows.h> BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch(ul_reason_for_call) { case DLL_PROCESS_ATTACH: printf("dll process attach\n"); break; case DLL_THREAD_ATTACH: printf("dll thread attach\n"); break; case DLL_PROCESS_DETACH: printf("dll process detach\n"); break; case DLL_THREAD_DETACH: printf("dll process detach\n"); break; } return TRUE; } int add(int x, int y) { return x + y; }
这是一个DllMain()使用实例,其中ul_reason_for_call表明dll的四种调用方式,包括两种加载和两种卸载;
用前面的应用程序实验以后,结果以下:
导出类class
class CPoint { public: float x; float y; virtual void add(float, float) = 0; virtual float getX() = 0; virtual float getY() = 0; }; class CMyPoint : public CPoint { public: CMyPoint(float, float); virtual void add(float, float); virtual float getX(); virtual float getY(); };
class __declspec(dllexport)CMyPoint;
设计一对父类——子类,父类中的方法设计成纯虚函数,而它的子类就是咱们要用的类了,在dll中工程中导出的也是子类CMyPoint,上面最后一句代码导出这个子类。
这是子类的实现代码:
CMyPoint::CMyPoint(float x0, float y0) { var = 12; x = x0; y = y0; } void CMyPoint::add(float dx, float dy) { x = x + dx; y = y + dy; } float CMyPoint::getX() { return x; } float CMyPoint::getY() { return y; }
类CMyPoint的实现还包含一个Create函数,是用来建立类对象的。这个方法也应该用C方式导出,不然使用起来有些麻烦。
extern "C" __declspec(dllexport)CMyPoint* CreatePoint(float,float); CMyPoint* CreatePoint(float x0, float y0) { return (new CMyPoint(x0, y0)); }
动态加载方式
新建应用工程,须要复制.h头文件、dll文件到新的工程文件夹下,程序先获取CreatePoint()方法,再经过它建立类CMyPoint并返回一个对象指针,接下来即可以操做这个类对象;
注意:应用工程文件夹下的dllTest.h中不能有多余的导出或者导入语句,能够用宏屏蔽,也能够手动删除。
#include "dllTest.h"
typedef CMyPoint*(*lpCreatePoint)(float,float); int main(int argc, char* argv[]) { lpCreatePoint CreatePoint; HINSTANCE hDll = LoadLibrary("dllTest.dll"); if(hDll) { CreatePoint = (lpCreatePoint)GetProcAddress(hDll, "CreatePoint"); if(CreatePoint) { CMyPoint* p0 = CreatePoint(12, 14); printf("x = %.2f, y = %.2f\n", p0->getX(), p0->getY()); } FreeLibrary(hDll); } return 0; }
静态加载方式
静态使用方式很简单,一样应用建立函数CreatePoint(),并且是导出后直接使用,固然类界面仍是必须inlclude;
#include "dllTest.h"
#pragma comment(lib, "E:\\VC6\\dllTest\\Debug\\dllTest.lib")
extern "C" __declspec(dllimport)CreatePoint(float,float); int main() { CMyPoint* p0 = CreatePoint(3, 4); p0->add(2, 3); printf("x = %.2f, y = %.2f\n", p0->getX(), p0->getY()); return 0; }
静态导入方式构建生成的EXE文件可移植,lib文件和dll文件在构建时都固化在其中,因此这里生成的EXE不管到哪儿,只要系统还兼容,程序就能够运行;
而以前的动态导入方式,dll文件须要随着EXE文件移动,否则程序运行时会找不到dll文件而出错,因此咱们通常的见到的软件安装目录下都有一大堆dll文件。
导出变量
在dll工程中,导出:int __declspec(dllexport)varName;
在应用工程中,导入:int __declspec(dllimport)varName;
其中,int是变量类型,varName是变量名,