本文要说的是动态链接库 dll 和静态链接库 lib
动态链接库是一种资源的集合,包括函数,变量,类,资源等都可以从动态链接库中来导出!
静态链接库的代码就可以直接放到exe文件中,动态链接库是被exe文件动态的加载或者卸载。
静态链接库不能包含其他的动态链接库和静态链接库,而动态链接库是可以的。
在本文我们会用两种方式来写动态链接库文件,即:SDK API编写和 MFC 编写。
SDK中
1、静态链接库
这里看一下静态库的调用方式:
//调用静态库
#include"静态库的头文件"
#pragma comment(lib, "静态库")
ok下面我们来编写一个简单的静态链接库的文件:
/*
lib.h文件
*/
#pragma once//防止重复包含
//声明函数,加上extern “C"是为了避免VC 将add的名字改编
extern "C" int add(int a, int b);
/*
lib.cpp文件
*/
#include "stdafx.h"
#include "lib.h"
int add(int a, int b)
{
return a+b;
}
下面是调用测试的代码:
#include<iostream>
#include"../lib/lib.h"
#pragma comment(lib, "../debug/lib.lib")
using namespace std;
int main(void)
{
int m = add(2, 3);
cout << m << endl;
system("pause");
return 0;
}
2、动态链接库
动态链接库的入口点和其他的应用程序就不一样了,下面我们来比较看一下:
CUI控制台程序(不是DOS):main
GUI用户界面程序:WinMain
DLL程序入口点函数:DllMain 不过,当你的dll就是导出资源,那么可以没有DllMain
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}
第一个参数是一个句柄,第三个参数保留没有意义。
来看第二个参数,他指明了dll被调用的原因,他有如下四个值:
1、DLL_PROCESS_ATTACH:
当DLL被进程 第一次 调用时,导致DllMain函数被调用,同时ul_reason_for_call的值为DLL_PROCESS_ATTACH,如果同一个进程后来再次调用此DLL时,操作系统只会增加DLL的使用次数,不会再用DLL_PROCESS_ATTACH调用DLL的DllMain函数。
2、DLL_PROCESS_DETACH:
当DLL被从进程的地址空间解除映射时,系统调用了它的DllMain,传递的ul_reason_for_call值是DLL_PROCESS_DETACH。
★如果进程的终结是因为调用了TerminateProcess,系统就不会用DLL_PROCESS_DETACH
来调用DLL的DllMain函数。这就意味着DLL在进程结束前没有机会执行任何清理工作。
3、DLL_THREAD_ATTACH:
当进程创建一线程时,系统查看当前映射到进程地址空间中的所有DLL文件映像,并用值DLL_THREAD_ATTACH调用DLL的DllMain函数。 新创建的线程负责执行这次的DLL的DllMain函数,只有当所有的DLL都处理完这一通知后,系统才允许线程开始执行它的线程函数。
4、DLL_THREAD_DETACH:
如果线程调用了ExitThread来结束线程(线程函数返回时,系统也会自动调用ExitThread),系统查看当前映射到进程空间中的所有DLL文件映像,并用DLL_THREAD_DETACH来调用DllMain函数,通知所有的DLL去执行线程级的清理工作。
注意:如果线程的结束是因为系统中的一个线程调用了TerminateThread,系统就不会用值DLL_THREAD_DETACH来调用所有DLL的DllMain函数。
下面要说的就是dll的导出函数:
两种方式:*.def 文件或 __declspec(dllexport) 关键字:
1、def文件
看一下实例
LIBRARY "DLLTest"
EXPORTS
add @1
fun @2
其中,LIBRARY,他将def文件标识为属于dll。
EXPORTS 是列出名称 也能列出你导出函数的序号。 看上面的代码你能理解。
要注意,在这里你要加注释就不是//的方式了,而是分号: ; 。
2、_declspec(dllexport)导出
extern "C" _declspec(dllexport) int add(int a, int b);
这里的dllexport 你可以把它换成dllimport,这个意思是你要进行函数的导入。
OK,下面我们来看实例代码:
// dll.cpp : 定义 DLL 应用程序的导出函数。
//
#include "stdafx.h"
int add(int a, int b)
{
return a+b;
}
;def文件
LIBRARY "dll"
EXPORTS
add @1
或者用第二种方式:
#pragma once
extern "C" _declspec(dllexport)int add(int a, int b);
我们用depends 来查看一下这个动态链接库导出的函数
我们编写了dll文件,把函数导出了,但是我们导出的函数必须有人去调用, 现在我们来看一下 如何调用dll文件。
调用dll有两种方式: 隐式连接 显示连接
1、隐式链接
主要是由编译器对dll进行加载和卸载。如果程序结束时如果还有其他应用程序使用该DLL,那么系统会使DLL的使用计数减1,当DLL的使用计数降为0时,会将DLL从内存中删除。
使用方法:
//#include "头文件"
#pragma comment(lib, "***.lib")
_declspec(dllimport) 函数声明
看一下测试的具体代码:
#include<iostream>
//隐式链接
//#include "../dll/标头.h" //当使用dllexport时使用
#pragma comment(lib, "..\\Debug\\dll.lib")
_declspec(dllimport)int dec(int a, int b);//当使用def文件时使用
_declspec(dllimport)int add(int a, int b);
///////////////////////////////////////
using namespace std;
int main(void)
{
int m = 0;
m = dec(6, 3);
cout << m << endl;
m = add(6, 4);
cout << m << endl;
system("pause");
return 0;
}
2、显示连接
他主要是由程序员来加载和卸载。
这里我们要用到几个函数:
LoadLibrary(...):该 API 用于加载指定的DLL;
GetProcAddress(...):该 API 用于获取DLL中导出函数的指针, 即导出函数的入口点;
FreeLibrary(...):该 API 用于卸载指定的DLL。
下面看一下如何来使用它们:
typedef int(*DEC_FUNC)(int a, int b);
HMODULE hMod = LoadLibrary("链接库");
if(hMod)
{
DEC_FUNC dec_fp = (DEC_FUNC)GetProcAddress(hMod, "函数名");
if(dec_fp)
{
dec_fp();//使用函数
}
FreeLibrary(hMod);
}
我们看一下测试代码:
//#include<iostream>
//
////隐式链接
////#include "../dll/标头.h" //当使用dllexport时使用
//#pragma comment(lib, "..\\Debug\\dll.lib")
//_declspec(dllimport)int dec(int a, int b);//当使用def文件时使用
//_declspec(dllimport)int add(int a, int b);
/////////////////////////////////////////
//
//using namespace std;
//
//int main(void)
//{
// int m = 0;
// m = dec(6, 3);
// cout << m << endl;
// m = add(6, 4);
// cout << m << endl;
// system("pause");
// return 0;
//}
#include<iostream>
#include<Windows.h>
using namespace std;
typedef int(*DEC_FUNC)(int a, int b);
int main(void)
{
HMODULE hFun = LoadLibrary("..//Debug//dll.dll");
if(hFun)
{
DEC_FUNC dec_func = (DEC_FUNC)GetProcAddress(hFun, "dec"); //按照函数名称
DEC_FUNC add_func = (DEC_FUNC)GetProcAddress(hFun, MAKEINTRESOURCEA(1));//按照函数的导出序号,在def文件
if(add_func)
{
cout << add_func(2, 4) << endl;
}
if(dec_func)
{
cout << dec_func(3, 1) << endl;
}
FreeLibrary(hFun);
}
system("pause");
}
MFC中
在MFC中,DLL 的入口点也是DLLMain函数。
在MFC中,当exe文件退出的时候,dll会调用ExitInstance函数,当exe初始化调用dll的时候,dll会默认调用InitInstance函数。
1、静态链接
与mfc库静态链接,这里会将mfc类库的代码直接编译生成DLL文件中,调用这种DLL的接口的时候,MFC使用DLL的资源,这样就不需要模块状态的切换。但是用这种方式生成的dll比较大。
2、动态链接
调用DLL的exe文件同时连接到mfc库,用的时候就来链接, exe文件的资源句柄加载资源末班,当dll和exe程序中有相同的id资源的时候,必须进行模块的切换。 而静态是不需要模块状态切换的。
下面来看那一下如何进行模块的切换
有三种方法:1、AFX_MANAGE_STATE(AfxGetStaticModuleState()); //执行到这里,把资源的主句柄转换到dll中
2、HINSTANCE hSaveInst = AfxGetResourceHandle();//取得当前应用程序句柄 存到hSaveInst中
AfxSetResourceHandle(theApp.m_hInstance);//设置成在dll中找资源
... ... //执行语句;
AfxSetResourceHandle(hSaveInst);//设置回来
3、放到可执行程序中
HINSTANCE hExeInst = GetModuleHandle(NULL);//取得指定模块的句柄 传递NULL 返回当前exe的实例句柄
HINSTANCE hDLLInst = GetModuleHandle(_T("MFCDLL.dll"));//取得dll的实例句柄
ASSERT(hExeInst && hDLLInst);//断言
AfxSetResourceHandle(hDLLInst);//设置成在dll中找资源
... ... //执行语句;
AfxSetResourceHandle(hExeInst);//重新设置回来
我们的实例是在dll中弹出一个对话框。
下面的代码是我们在dll文件中弹出对话框的函数:
void showdlg()
{
//模块切换
//******************************************
AFX_MANAGE_STATE(AfxGetStaticModuleState()); //在DLL 中找资源
//******************************************
CDialog dlg(IDD_DIALOG1);
dlg.DoModal();
}
//函数的导出
; MFCDLL.def : 声明 DLL 的模块参数。
LIBRARY
EXPORTS
; 此处可以是显式导出
showdlg @1
在测试文件中:
#pragma comment(lib, "..//Debug/MFCDLL.lib")
_declspec(dllimport) void showdlg();
void CmfcceshiDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
showdlg();
}
void CmfcceshiDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
CAboutDlg dlg;
dlg.DoModal();
}