windows中动态链接库的创建

windows中动态链接库的创建

创建动态链接库工程:我使用的vs2010

1. 创建win32项目,(我们命名为DllDemo)

2.在应用程序类型中选择DLL

动态链接库的入口点DllMain (在dllmain.cpp文件中)DllMain函数。库的入口函数仅供操作系统使用,Windows在库装载、卸载、进程中线程创建和结束时调用入口函数,以便动态链接库可以采取相应的动作。DllMain由工具自动生成,我们不用去修改它。

动态链接库中的函数:DLL能够定义两种函数,导出函数和内部函数。导出函数可以被其他模块调用,也可以被定义这个函数的模块调用,而内部函数只能被定义这个函数的模块调用。

动态链接库的主要功能就是向外导出函数,供进程中其他模块使用

在DllMain.cpp中定义导出函数:我们创建一个函数 void ExportFunc(LPCTSTR pszContent)

注意:函数定义完后只能在本工程中使用,要想将函数导出供其他模块调用,最简单的方法是在DllDemo.h文件中进行声明。。

声明如下:

#ifdef DLLDEMO_EXPORTS
#define DLLDEMO_API __declspec(dllexport)
#else
#define DLLDEMO_API __declspec(dllimport)
#endif

//声明要导出测函数
DLLDEMO_API void ExportFunc(LPCTSTR pszContent);

也可以这样声明导出函数:直接在返回类型前面加上_declspec(dllexport)就可以将该函数声明为导出函数

_declspec(dllexport) int Add(int a, int b){
	return b + a;
}

_declspec(dllexport) int Sub(int a, int b){
	return a - b;
}


 


使用导出函数

调用DLL中的导出函数有两种方法:

(1). 装载期间动态链接:模块可以像调用本地函数一样调用从其他模块导出的函数(API就是这样调用的)。装载期间链接必须使用DLL的导入库(.lib文件),它为系统提供了加载这个DLL和定位DLL中的导出函数所需的信息。

实现装载期间动态链接需要将DllDemo.h、DllDemo.lib、DllDemo.dll三个文件拷贝到另外一个新建的工程中。

(2).运行期间动态链接:模块可以使用LoadLibrary或者LoadLibraryEx函数在运行期间加载这个DLL。DLL被加载之后,加载模块调用GetProcAddress函数取得DLL导出函数的地址,然后通过函数地址调用DLL中的函数。

为了实现运行期间动态的导出函数,一般要在原先创建动态链接库的工程中建立一个DEF文件来指定要导出的函数。例如添加一个DllDemo.def文件。

例子: DllDemo工程:

dllmain.cpp

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
#include <stdio.h>
#include "DllDemo.h"
#include <stdexcept>

using namespace std;

HMODULE g_hModule;

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		g_hModule = (HMODULE)hModule;
		break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}


//导出函数
void ExportFunc(LPCTSTR pszContent)
{

	char sz[MAX_PATH];
	::GetModuleFileNameA(g_hModule, sz, MAX_PATH);
	::MessageBox(NULL, pszContent, strrchr(sz, '\\')+1, MB_OK);
}

namespace MathFuncs
{

	double MyMathFuncs::Add(double a, double b)
	{
		return a + b;
	}
	double MyMathFuncs::Sub(double a, double b)
	{
		return a - b;
	}
}


DllDemo.h文件

#ifdef DLLDEMO_EXPORTS
#define DLLDEMO_API __declspec(dllexport)
#else
#define DLLDEMO_API __declspec(dllimport)
#endif

//声明要导出测函数
DLLDEMO_API void ExportFunc(LPCTSTR pszContent);

namespace MathFuncs
{
	class MyMathFuncs
	{
	public:
		//return a + b
		static DLLDEMO_API double Add(double a, double b);
		//return a - b
		static DLLDEMO_API double Sub(double a, double b);
	};
}

另一个工程: 测试前面的动态链接库

#include <windows.h>
#include "DllDemo.h"
#pragma comment(lib, "DllDemo.lib")

int main()
{
	ExportFunc("大家好");
	return 0;
}


如何使用动态链接库:

引入动态链接库:#pragma comment(lib, "Dll2013.lib")

声明外部函数:利用extern来声明,

extern int Add(int a, int b);
extern int Sub(int a, int b);

或者直接利用_declspec(dllimport)来直接声明函数是从动态链接库中引入的。

_declspec(dllimport) int Add(int a, int b);
_declspec(dllimport) int Sub(int a, int b);

_declspec(dllimport) 与 extern相比,使用_declspec(dllimport)标识符声明外部函数时,它将告诉编译器该函数是从动态链接库中引入的,编译器可以运行效率更高的代码。因此,如果调用的函数来自于动态链接库,应该采用这种方式声明外部函数。

dumpbin命令:

在VS2010中,通过“工具->visual studio 2010命令提示”就可以打开命令窗口。输入dumpbin可以查看该命令的使用方法。

输入dumpbin -exports [动态链接库的路径]就可以查看动态链接库的信息。

输入dumpbin -imports [应用程序的路径] 可以查看应用程序的导入信息。

 

从DLL中导出C++类:

不仅可以从动态链接库中导出函数,还可以导出一个C++类。为了导出一个C++类,我们可以在DLL的头文件中添加类似下面的代码

class DLL2013_API Point{
public:
	void Output(int x, int y);
};

然后再DLL的cpp文件中添加Point类对应的函数实现

void Point::Output(int x, int y){
	HWND hwnd = ::GetForegroundWindow();
	HDC hdc = ::GetDC(hwnd);
	char buf[20];
	memset(buf, 0, 20);
	sprintf(buf, "x=%d, y=%d", x, y);
	::TextOut(hdc, 0, 0, buf, strlen(buf));
	::ReleaseDC(hwnd, hdc);
}

完成上面两步,就可以将Point类导出了。

我们也可以不导出整个类,而只是导出该类中的某些函数。为了只导出类中的部分函数,我们要将类前面使用的DLL2013_API注释掉,然后在导出函数前面放置DLL2013_API宏。这样就只有使用了DLL2013_API宏的函数才能被导出。

class /*DLL2013_API*/ Point{
public:
	void DLL2013_API Output(int x, int y);
	void Test();
};

上面的代码我们导出了函数Output,而函数Test没有导出。可以用工具dumpbin来查看。另外,在导出类的成员函数时需要注意,该函数必须具有public类型的访问权限,否则,该函数即使能够被导出,也不能被其它程序访问。

解决名字改编问题:

我们知道,C++编译器在生成DLL时,会对导出函数进行名字改编,并且不同的编译器使用的改编规则不一样,因此改编后的名字是不一样的。这样,如果利用不同的编译器分别生成DLL和访问DLL的客户端程序的话,后者在访问该DLL的导出函数时就会出现问题。例如C++编译器和C编译器,C编译器不会对名字进行改编。

为了解决上面的问题,我们希望动态链接库文件在编译时,导出函数的名称不要发生改变。为了实现这一目的,在定义导出函数是,需要加上限定符:extern "C"。注意双引号中的字母C一定要大写。看下面的例子:

在Dll的头文件(我的头文件是Dll2013.h)中添加如下代码:

#ifdef DLL2013_API
#else
#define DLL2013_API extern "C" _declspec(dllimport)
#endif

DLL2013_API int Add(int a, int b);
DLL2013_API int Sub(int a, int b);

在Dll的源文件(我的源文件是dllmain.cpp)中添加如下代码:

#define DLL2013_API extern "C" _declspec(dllexport)
#include <Windows.h>
#include <stdio.h>

#include "stdafx.h"
#include "Dll2013.h"

// dllmain.cpp : 定义 DLL 应用程序的入口点。
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

int Add(int a, int b){
	return b + a;
}

int Sub(int a, int b){
	return a - b;
}


重新生成DLL2013.dll之后,用dumpbin命令查看导出函数信息,可以发现DLL导出的Add和Sun函数的名字没有发生改编。

需要注意的是,extern "C" 可以解决C++和C语言之间相互调用是函数命名的问题,但是这种方法有一个缺陷,就是不能用于导出一个类的成员函数,只能用于导出全局函数这种情况。另外,如果导出函数的调用约定发生了改变,那么即使使用了限定符:extern "C" ,该函数的名字仍会发生改编

例如:上面的程序中,使导出函数Add和Sub使用标准调用约定(标准调用约定就是WINAPI约定,也就是pascal调用约定,这种约定方式与C调用约定不一样):即在声明这些函数时添加_stdcall 关键字。

在Dll2013.h中:

DLL2013_API int _stdcall Add(int a, int b);
DLL2013_API int _stdcall Sub(int a, int b);

在dllmain.cpp中:

int _stdcall Add(int a, int b){
	return b + a;
}

int _stdcall Sub(int a, int b){
	return a - b;
}

上面的代码中,如果没有添加_stdcall关键字,那么函数的调用就是C调用约定。

还有一种方法来解决名字改编问题:使用模块定义文件(DEF)

LIBRARY DLL2013
EXPORT
Add
Sub

其中LIBRARY语句用来指定动态链接库的内部名称,该名称与生成的动态链接库的名称一定要匹配,不过这句代码并不是必须的。

EXPORT语句的作用是表明DLL将要导出的函数,以及为这些函数指定的符号名。当连接器在链接时,会分析这个DEF文件,当发现在EXPORT语句下面有Add和Sub这两个符号名,并且它们与源文件中定义的Add和Sub函数的名字一致时,它就会以Add和Su这两个符号导出相应的函数,如果不一致,就按照下述语法指定导出函数: entryname = internalname 。
其中entername是导出的符号名,interalname是DLL中将要导出的函数的名字。


显式加载DLL

使用动态方式加载动态链接库时,需要用到LoadLibrary函数,该函数的作用是将指定的可执行模块映射到调用进程的地址空间。Loadlibrary函数不仅能够加载DLL(.dll),还可以加载可执行模块(.exe)。

当获取到动态链接库的句柄后,接下来就是要获取动态链接库中导出函数的地址,这可以通过GetProcAddress函数来实现。

使用动态加载方式要注意的几个地方:

1.创建DLL时,一定要解决名字改变问题,例如可以利用extern "C" ,是编译器不对名字进行改编。

2.动态加载DLL时,客户端程序不再需要包含导出函数声明的头文件(.h)和引入库文件(.lib),只需要.dll文件即可

3.当采用动态方式加载DLL时,在客户端将不能看到调用该DLL的输入信息。可以用dumpbin -imports [.exe文件路径]来查看输入信息。

例子:

HINSTANCE hInst;
	//动态加载DLL
	hInst = ::LoadLibrary("F:\\windows\\000MFC\\Dll2013\\Debug\\Dll2013.dll");
	if(!hInst){
		MessageBox("加载失败");
		return;
	}
	//定义函数指针类型
	typedef int (*ADDPROC)(int a, int b);
	//获取DLL的导出函数
	ADDPROC add = (ADDPROC)GetProcAddress(hInst, "Add");
	if(!add){
		MessageBox("获取函数地址失败");
		return;
	}
	CString str;
	str.Format("5 + 3 = %d", add(5, 3));
	MessageBox(str);

调用约定:

如果我们将DLL工程中的导出函数的调用约定改为标准约定(_stdcall),那么生成的DLL,其导出函数名字会被改编吗???自己可以试验一下,结果是不会发生名字改编。

还要注意的地方:当DLL中导出函数采用的是标准调用约定时,放完该DLL的客户端程序也应该采用该约定类型来访问相应的导出函数。

DLL中:

int _stdcall Add(int a, int b){
	return b + a;
}

客户端程序中:

typedef int (_stdcall *ADDPROC)(int a, int b);


根据序号访问DLL中的导出函数

当我们用dumpbin工具查看DLL文件是时,可以看到每个导出函数都进行了编号,而我们就可以利用这些编号来调用导出函数。在这里我们要解决的问题是,如何把int类型的序号转换为LPCSTR类型的变量???使用宏 MAKEINTRESOURCE ,该宏可以把指定的函数序号转换为相应的函数名字字符串。

	//获取DLL的导出函数
	//ADDPROC add = (ADDPROC)GetProcAddress(hInst, "Add");
	ADDPROC add = (ADDPROC)GetProcAddress(hInst, MAKEINTRESOURCE(1));

 

最后,当不在需要访问该DLL时,调用FreeLibrary函数释放对该DLL的引用。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值