1. 动态链接库简介
动态链接库包含其他程序来完成工作的需要调用的函数,不能直接运行,也不能接收消息。
静态链接库与动态链接库
对于静态链接库lib中的指令都被直接包含在最终生成的EXE文件中了。但是若使用DLL,该DLL不必被包含在最终EXE文件中,EXE文件执行时可以“动态”地引用和卸载这个与EXE独立的DLL文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。
2. win32动态链接库举例
创建一个动态链接库工程,工程名为dll1,并建立dll1.h和dll1.cpp
dll1.h
//让DLL文件导出函数,需要添加标识符_declspec(dllimport)
#ifdef dll1_api
#else
#define dll1_api _declspec(dllimport)
#endif
dll1_api int add(int a,int b);
dll1_api int subtract(int a,int b);
//在声明类时指定了导出标志,则整个类中的所有函数都能导出
//否则只能是那些指定导出标志的函数可以导出
class /*dll1_api*/ point
{
public :
void dll1_api output(int x,int y);//只导出point类中的成员函数
void test();
};
dll1.cpp
#include "stdafx.h"
#define dll1_api _declspec(dllexport) //宏定义
#include"dll1.h"
#include<windows.h>
#include <stdio.h>
int add(int a,int b)
{
return a+b;
}
int subtract(int a,int b)
{
return a-b;
}
void point ::output (int x,int y)
{
HWND hwnd=GetForegroundWindow();
HDC hdc=GetDC(hwnd);
char buf[20];
memset(buf,0,20);
sprintf_s(buf,"x=%d,y=%d",x,y);
TextOut(hdc,0,0,buf,strlen(buf));
ReleaseDC(hwnd ,hdc);
}
void point ::test()
{
}
3. 编译器名字改编问题
c++编译器在生成DLL时会对导出的函数进行名字改编,和c语言编译函数命名规则不同,并且不同的编译器使用的规则不同,因此改变的名字是不一样的,所以DLL和访问DLL的客户端程序用不同的编译器编译,就会出现问题。因此我们希望动态链接库文件在编译时导出的名称不发生改编,方法是在定义导出函数时需要加上限定符:extern “C”.即:
#ifdef dll1_api
#else
#define dll1_api extern "C" _declspec(dllimport)
#endif
但是不能用于导出一个类的成员函数,只能用于导出全局函数这种情况。 extern “C”可以解决C++和C语言之间相互调用时函数命名的问题。另外如果导出函数的调用约定发生改变那么及时使用了 extern “C”该函数的名字依然发生改变。解决方法:使用模块定义文件(DEF)
4. C++ 调用 *.lib 的方法:
- 隐式的加载lib,有三种方法:
a.LIB 文件直接加入到工程文件列表中:
在 VC 中打开 File View 一页,选中工程名,单击鼠标右键,然后选中 “Add Files to Project” 菜单,在弹出的文件对话框中选中要加入 DLL 的 LIB 文件。然后在首先要使用该函数的地方加上该 LIB 的头文件,如 #include “..\lib.h” 即可(没有头文件当然就不用了)。
b. 设置工程的 Project Settings 来加载 DLL 的 LIB 文件:
打开工程的 Project Settings 菜单,选中 Link ,然后在 Object/library modules 下的文本框中输入 DLL 的 LIB 文件,如 you.lib (或者 lib 文件的路径,包括文件名)。然后在首先要使用该函数的地方加上该 LIB 的头文件,如 #include “..\lib.h” 即可(没有头文件当然就不用了)。
c. 通过程序代码的方式
加入预编译指令 #pragma comment (lib, “*.lib”) ,这种方法优点是可以利用条件预编译指令链接不同版本的 LIB 文件。因为,在 Debug 方式下,产生的 LIB 文件是 Debug 版本,如 Regd.lib ;在 Release 方式下,产生的 LIB 文件是 Release 版本,如 Regr.lib 。然后在首先要使用该函数的地方加上该 LIB 的头文件,如 #include “..\lib.h” 即可(没有头文件当然就不用了)。
当应用程序对 DLL 的 LIB 文件加载后,还需要把 DLL 对应的头文件( *.h ),DLL文件包含到其中,在这个头文件中给出了 DLL 中定义的函数原型声明。
#include <stdio.h>
#include <Windows.h>
#include <tchar.h>
// 先把 lib 链接进来
#pragma comment (lib , "..//Debug//FuncDll.lib" )
// 外部声明的 add 函数
extern "C" _declspec (dllimport ) int add(int a, char b);
int main()
{
// 直接调用 add 函数
printf("%d/n" , add(5, 2));
return 0;
}
- 显式的运行时链接
隐式链接虽然实现较简单,但除了必须的 .dll 文件外还需要 DLL 的 .h 文件和 .lib 文件,在那些只提供 .dll 文件的场合就无法使用,而只能采用显式链接的方式。这种方式通过调用 API 函数来完成对 DLL 的加载与卸载,能更加有效地使用内存,在编写大型应用程序时往往采用此方式。该方法不需要.lib库和.h文件,这种方法编程具体实现步骤如下:
1.使用 Windows API 函数 Load Library 或者 MFC 提供的 AfxLoadLibrary 将 DLL 模块映像到进程的内存空间,对 DLL 模块进行动态加载。
2.使用 GetProcAddress 函数得到要调用 DLL 中的函数的指针。
3.不用 DLL 时,用 Free Library 函数或者 AfxFreeLibrary 函数从进程的地址空间显式卸载 DLL 。
#include
#include
void main(void)
{
typedef int(*pMax)(int a,int b);
typedef int(*pMin)(int a,int b);
HINSTANCE hDLL;
PMax Max
HDLL=LoadLibrary("MyDll.dll");//加载动态链接库MyDll.dll文件;
Max=(pMax)GetProcAddress(hDLL,"Max");
A=Max(5,8);
Printf("比较的结果为%d\n",a);
FreeLibrary(hDLL);//卸载MyDll.dll文件;
}
typedef 自定义类型,pMax 是个函数指针类型,用pMax类型定义出的函数指针只能指向具有两个int*参数,返回值为int的函数,然后通过LoadLibray()将DLL加载到当前的应用程序中并返回当前DLL文件的句柄,然后通过GetProcAddress()函数获取导入到应用程序中的函数指针,函数调用完毕后,使用FreeLibrary()卸载DLL文件。在编译程序之前,首先要将DLL文件拷贝到工程所在的目录或Windows系统目录下。
动态装载 *.dll ,未加路径,将在三个默认路径中寻找
(1) windows 的系统目录 :\windows\system;
(2) dos 中 path 所指出的任何目录 ;
(3) 程序所在的目录 ;