本文要说的是动态链接库 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); }
DEC_FUNC函数指针应该不模式了吧,在【C/C++学习】之七、指向函数的指针 有介绍。
我们看一下测试代码:
//#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(); }
2012/10/4
jofranks于南昌