一、概论
先来阐述一下DLL(Dynamic Linkable Library)的概念,你可以简单的把DLL看成一种仓库,它提供给你一些可以直接拿来用的变量、函数或类。在仓库的发展史上经历了“无库-静态链接库-动态链接库”的时代。
静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib中的指令都被直接包含在最终生成的EXE文件中了。但是若使用DLL,该DLL不必被包含在最终EXE文件中,EXE文件执行时可以“动态”地引用和卸载这个与EXE独立的DLL文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。
(1)DLL 的调用与具体的编程语言及编译器无关
只要遵循约定的DLL接口规范和调用方式,用各种语言编写的DLL都可以相互调用。譬如Windows提供的系统DLL(其中包括了Windows的API),在任何开发环境中都能被调用,不在乎其是Visual Basic、Visual C++还是Delphi。
(2)动态链接库随处可见
我们在Windows目录下的system32文件夹中会看到kernel32.dll、user32.dll和gdi32.dll,windows的大多数API都包含在这三个DLL中。kernel32.dll中的函数主要处理内存管理和进程调度;user32.dll中的函数主要控制用户界面; gdi32.dll中的函数则负责图形方面的操作。
(3)VC动态链接库的分类
Visual C++支持三种DLL,它们分别是Non-MFC DLL(非MFC动态库)、MFC Regular DLL(MFC规则DLL)、MFC Extension DLL(MFC扩展DLL)。本文只介绍非MFC动态库。
非MFC动态库不采用MFC类库结构,其导出函数为标准的C接口,能被非MFC或MFC编写的应用程序所调用;MFC规则DLL包含一个继承自CWinApp的类,但其无消息循环;MFC扩展DLL采用MFC的动态链接版本创建,它只能被用MFC类库所编写的应用程序所调用。
b.DLL内部函数,只能在DLL程序使用,应用程序无法调用它们。
二、操作步骤
I、导出库函数
a.创建动态链接库项目:
1、打开Microsoft Visual Studio 2010,选择File->New->Project。
2、在New Project中选择Installed Templates->Visual C++->Win32。
3、选择Win32 Console Application,设置名称:lib_dll,设置解决方案名:test_dll。
4、单击OK,在出现的Win32 Application Wizard的Overview对话框中点击Next。
5、在Application Settings中,选择Application type下的DLL。
6、勾选Additional options下的Empty project。
7、单击Finish创建项目。
b.添加函数
1.创建头文件lib.h
#ifndef LIB_H
#define LIB_H
extern "C"
#endif
2.创建源文件lib.cpp
#include "lib.h"
int add(int x, int y)
{
}
完成后对lib_dll进行生成(右键点击lib_dll->生成)。
c.在test_dll中创建一个新项目use_dll(win32控制台应用程序)
显式加载:
1.创建源文件main.cpp
#include
#include
#include
using namespace std;
typedef int(*lpAddFun)(int, int); //宏定义函数指针类型
int main(char argc, char**argv)
{
}
完成后运行即可。
隐式加载:
将main.cpp的内容改为如下:
#pragma comment(lib,"dllTest.lib")
//.lib文件中仅仅是关于其对应DLL文件中函数的重定位信息
extern "C" __declspec(dllimport) add(int x,int y);
int main(int argc, char* argv[])
{
int result = add(2,3);
printf("%d",result);
return 0;
}
--------------------------
II、导出类
a.创建动态链接库项目:
b.向动态链接库添加类:
1、添加新类头文件simpledll.h
//------------------ simpledll.h ----------------
#pragma once;
#ifdef DLL_IMPLEMENT
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif
//导出类
class DLL_API SimpleDll
2、添加新类源文件simpledll.cpp
//------------------ simpledll.cpp ----------------
#define DLL_IMPLEMENT
#include "simpledll.h"
SimpleDll::SimpleDll()
{
}
SimpleDll::~SimpleDll()
{
}
int SimpleDll::add(int x, int y)
{
}
完成后点击生成。
c.创建一个win32控制台应用程序。
1.添加main.cpp
//------------------ main.cpp -------------------
#include "simpledll.h"
#include
using namespace std;
int main(char argc, char**argv)
{
}
2、引用simpledll项目。右键单击usesimpledll项目,选择Properties->Common Properties->Framework and References。点击Add New Reference,选择simpledll项目,单击OK。
3、设置头文件路径。选择Properties->Configuration Properties->VC++ Directories。在Include Directories项添加$(SolutionDir)\simpledll\,选择应用,确定。
4、设置usesimpledll项目为活动项目。右键单击usesimpledll项目,选择Set up StartUp Project。
5、生成解决方案。
附:
下面对涉及到的一些知识点进行问答式解说:
1.导出函数的头文件中为什么要加:extern "C"
答:
extern"C"的意思是将其后声明的函数按照C语言的语法进行编译,也就是不改变函数的名称。否则在c++下编译会改变函数的名称。__declspec(dllexport)的意思是其后声明的函数是一个DLL中要导出的函数,要将该函数的地址信息放入lib文件中,供其它应用程序调用。
2. HMODULE是什么?
答:HMODULE 是代表应用程序载入的模块,win32系统下通常是被载入模块的线性地址。也可以用HINSTANCE代替,HINSTANCE 在win32下与HMODULE是相同的东西。使用HMODULE必须包含windows.h头文件。
在头文件中HMODULE定义如下:
typedef HINSTANCE HMODULE;
再看看HINSTANCE定义,
typedef HANDLE HINSTANCE;
再看看HANDLE定义,
typedef PVOID HANDLE;
再看看PVOID定义,
typedef void *PVOID;
其实这些都可以称为句柄,为了表述的方便,所以对于不同类型的句柄都用不同样式的typedef,比如说HINSTANCE表示实例句柄,HMODULE是模块句柄,实际上他们本质上都是VOID指针,是可以指向任何类型的指针。
3.loadlibrary,GetProcAddress,FreeLibrary是什么?
答:由“LoadLibrary-GetProcAddress-FreeLibrary”系统Api提供的三位一体“DLL加载-DLL函数地址获取-DLL释放”方式,这种调用方式称为DLL的动态调用。动态调用方式的特点是完全由编程者用 API 函数加载和卸载 DLL,程序员可以决定 DLL 文件何时加载或不加载,显式链接在运行时决定加载哪个 DLL 文件。使用这三个API必须包含windows.h头文件。
hDll = LoadLibrary(_T("lib.dll"));//加载动态链接库lib.dll给模块句柄dDll.
addFun = (lpAddFun)GetProcAddress(hDll, "add");//获取模块中函数指针。
FreeLibrary(hDll);//卸载DLL库
4._T("")是什么?
答:它是一个宏,定义于tchar.h下,hDll = LoadLibrary(_T("lib.dll"));VS2010采用UNICODE编码,他的作用是让你的程序支持Unicode编码方式。
如果不加会报如下错误:IntelliSense: "const char *" 类型的实参与 "LPCWSTR" 类型的形参不兼容。
5.#ifdef DLL_IMPLEMENT
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif
的作用是什么?
6.隐式加载和显式加载的步骤如何?
显式加载参考问题3.
隐式加载:
a.告诉编译器与DLL相对应的.lib文件所在的路径及文件名,#pragma comment(lib,"simpledll.lib")就是起这个作用。当然首先要将生成的dll和lib文件放在使用它的项目文件夹下。
b.声明导入函数,extern "C" __declspec(dllimport) int add(int x,int y)语句中的__declspec(dllimport)发挥这个作用.静态调用方式不再需要使用系统API来加载、卸载DLL以及获取DLL中导出函数的地址。这是因为,当程序员通过静态链接方式编译生成应用程序时,应用程序中调用的与.lib文件中导出符号相匹配的函数符号将进入到生成的EXE 文件中,.lib文件中所包含的与之对应的DLL文件的文件名也被编译器存储在 EXE文件内部。当应用程序运行过程中需要加载DLL文件时,Windows将根据这些信息发现并加载DLL,然后通过符号名实现对DLL 函数的动态链接。这样,EXE将能直接通过函数名调用DLL的输出函数,就象调用程序内部的其他函数一样