【VC++积累】之四、动态链接库

本文要说的是动态链接库  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于南昌


原文地址:https://www.cnblogs.com/java20130723/p/3211393.html