windows动态链接库

1、

动态链接库是一些独立的文件,其中包含能被可执行文件或其它动态链接库调用的函数。

静态库:扩展名.lib,程序编译链接时,链接器从库中复制这些函数和数据同其它模块组合,以生成可执行文件。程序运行时不再需要该库文件。

动态链接库:通常包含一个引入库文件.lib和DLL文件.dll,引入库文件包含DLL文件导出的函数和变量的符号名。程序编译链接时,链接器只链接引入库文件,不去复制DLL文件中的函数代码和数据,直到程序运行时才去加载所需的DLL文件,访问DLL文件中的函数和数据。故程序运行时,必须有DLL文件。

动态链接库有两种加载方式:隐式链接和显式加载。

2、Win32 DLL的创建

创建DLL工程方法:新建项目->选择Win32项目->应用程序类型选择DLL->选择空项目->为工程添加一个CPP源文件->在该源文件中编写相应函数->生成。

要想访问DLL中的函数,该函数必须是已经被导出的函数,为了让DLL导出函数,可以在定义该函数前加上_declspec(dllexport),编译后可以看到生成了一个DLL文件和一个lib文件。

eg:

/*Dll1.cpp*/

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

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

class _declspec(dllexport) CFoo
{
public:
	CFoo(int n){}
	void func(){}
};

 在程序中调用DLL中的函数时,应先用extern声明该函数为外部函数(或用_declspec(dllimport)声明该函数为DLL中的外部函数)。所以我们还需要添加一个头文件,这个头文件DLL本身并不需要,而是给使用DLL的人来使用:

/*Dll1.h*/

extern int add(int a, int b);

_declspec(dllimport) int sub(int a, int b);

class _declspec(dllimport) CFoo
{
public:
	CFoo(int n);
	void func();
};

想要查看一个DLL中有哪些导出函数,可以利用VS提供的命令行工具Dumpbin来实现。

3、Win32 DLL的使用:隐式方式加载DLL

/*DLLTestDlg.cpp*/

#include "Dll1.h"

void CDLLTestDlg::OnBnClickedButton1()
{
	// TODO: 在此添加控件通知处理程序代码
	CString str;
	str.Format(L"5+3=%d", add(5,3));
	MessageBox(str);
}

void CDLLTestDlg::OnBnClickedButton2()
{
	// TODO: 在此添加控件通知处理程序代码
	CString str;
	str.Format(L"5-3=%d", sub(5,3));
	MessageBox(str);
}

然后将DLL文件放至可执行文件目录下或系统目录C:\WINDOWS\system32、C:\WINDOWS下或path环境变量的路径中,添加对lib文件的链接:方法一,属性->链接器->附加库目录->输入.lib所在目录,属性->连接器->输入->附加依赖项->输入lib文件名;方法二,将lib文件放至当前工程目录下,属性->链接器->命令行->附加选项->添加lib文件名;方法三,将lib文件放至当前工程目录下,在代码中添加#pragma comment预处理指令,如#pragma comment(lib, "Ws2_32.lib")。

5、从DLL中导出C++类
还可以从动态链接库中导出C++类:在DLL程序头文件中声明这个类,还需在class后添加_declspec(dllimport)关键字,在DLL程序源文件中添加类中成员函数的实现,而且此时,在源文件中需要添加对于头文件的包含,所以源文件中函数定义前的_declspec(dllimport)关键字应该去掉:

/*Dll1.h*/
_declspec(dllimport) int add(int a, int b);
_declspec(dllimport) int sub(int a, int b);

class _declspec(dllimport) CMyPoint
{
public:
	void output(int x, int y);
};

/*Dll1.cpp*/
#include <stdio.h>
#include "Dll1.h"

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

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

void CMyPoint::output(int x, int y)
{
	HWND hWnd = GetForegroundWindow();
	HDC hdc = GetDC(hWnd);

	wchar_t buf[20];
	memset(buf, 0, sizeof(buf));
	swprintf(buf, L"x=%d,y=%d", x, y);

	TextOut(hdc, 0, 0, buf, wcslen(buf));

	ReleaseDC(hWnd, hdc);
}

GetForegroundWindow()函数的作用是返回前景窗口的句柄,前景窗口即当前用户正在使用的那个窗口。类成员函数的作用即为在当前使用窗口的(0,0)处输出x,y的坐标值。

/*DLLTestDlg.cpp*/
#include "Dll1.h"

void CDLLTestDlg::OnBnClickedButton1()
{
	// TODO: 在此添加控件通知处理程序代码
	CString str;
	str.Format(L"5+3=%d", add(5,3));
	MessageBox(str);
}

void CDLLTestDlg::OnBnClickedButton2()
{
	// TODO: 在此添加控件通知处理程序代码
	CString str;
	str.Format(L"5-3=%d", sub(5,3));
	MessageBox(str);
}

void CDLLTestDlg::OnBnClickedButton3()
{
	// TODO: 在此添加控件通知处理程序代码
	CMyPoint pt;
	pt.output(5, 3);
}

另外,在实现动态链接库时还可以不导出整个类,而只导出类的成员函数:在类的声明中,只在对应函数前加_declspec(dllimport)关键字。

6、使用extern "C" 解决函数名字改编问题

C++编译器在生成DLL时,会对导出的函数进行名字改编,不同的编译器使用的改编规则不同,因此改编后的函数的名字是不一样的。这样如果生成DLL的编译器和访问该DLL的程序使用的编译器不同的话,那么在访问该DLL的导出函数时就可能出现问题,后者将使用自己的改编规则生成的函数名去调用DLL中的函数。

例如,用C++编写了一个DLL,用C语言编写的程序去调用这个DLL中函数就会出现问题,因为C编译器生成的程序会使用函数的原始名称去调用DLL中函数,而C++编译器生成的DLL已经对该函数名称进行了改编。所以,如果我们希望DLL文件在编译时,导出函数的名称不要发生变化,那么在定义该函数前加上限定符:extern "C"。即在C++源文件中的语句前面加上extern "C",表明这些语句应该按照类C的编译和连接规约来编译和连接。

extern "C"只能用于导出全局函数,不能用于导出类的成员函数,且如果导出函数的调用约定发生了改变(比如DLL中方法为WINAPI或CALLBACK),那么导出函数名仍会发生改编。这种情况下,可以通过一个称为模块定义文件(DEF)的方法来解决名字改编问题。方法为右键项目选择添加“模块定义文件(.def)”,在这个文件中声明DLL名称和需要导出的方法名,如下所示,然后右键项目属性选择连接器-输入-模块定义文件,输入我们创建的.def文件名。

LIBRARY DLL_NAME

EXPORTS
func1
func2

C++中调用C生成的库的时候,在C的头文件中添加extern "C"{}。
C调用C++生成的库的时候,在C++生成库的时候在C++的定义文件中添加extern "C"{}。

 

7、显式(动态)加载方式加载DLL

LoadLibrary():加载指定的DLL或可执行模块,将它映射到调用进程的地址空间,函数原型:

HMODULE LoadLibrary(LPCTSTR lpFileName);

参数为DLL文件名或可执行模块exe文件名;加载可执行模块exe文件时,一般主要是为了访问该模块的一些资源,如对话框,位图,图标等。函数执行成功返回加载的那个模块的句柄,为HMODULE类型,其可以与HINSTANCE类型通用。函数执行失败返回0。

GetProcAddress():获得DLL中导出函数的地址,函数原型:

FARPROC GetProcAddress(
     HMODULE hModule,
     LPCSTR lpProcName
);

第一个参数为DLL模块的句柄,第二个参数指定导出函数的名字或函数的序号。函数调用成功返回导出函数的地址,应该定义一个对应的函数指针来保存该地址,通过这个函数指针来调用对应的函数;函数执行失败返回NULL。GetProcAddress()返回NULL也有可能是因为导出的函数名称改编问题,这时候可以给DLL中方法添加extern "C",调用DLL中方法的声明也要添加extern "C"。

FreeLibrary():在需要访问DLL时,调用LoadLibrary()加载DLL,当不再需要访问该DLL时,调用FreeLibrary()释放对DLL的引用。函数原型:

BOOL WINAPI FreeLibrary(HMODULE hModule);

参数为DLL模块的句柄。

#include <stdio.h>
#include<Windows.h>
#include <tchar.h> 

int _tmain(int argc, _TCHAR* argv[])
{
	//映射DLL到当前进程的地址空间
	HINSTANCE hInst=LoadLibrary(_T("Kernel32.DLL"));   
    
	if(hInst)   
    {   
		typedef BOOL(WINAPI * MYFUNC)(LPTHREAD_START_ROUTINE, PVOID,ULONG);   
        MYFUNC MyQueueUserWorkItem=NULL; 

        //取得QueueUserWorkItem函数指针   
        MyQueueUserWorkItem=(MYFUNC)GetProcAddress(hInst,"QueueUserWorkItem");   
        if(!MyQueueUserWorkItem)     
			printf("函数指针获取失败");   
	}

	//释放DLL引用
	FreeLibrary(hInst);

	return 0;
}

8、DllMain()函数

对于可指定模块来说,其入口函数为WinMain(),而对于DLL,其入口函数为DllMain(),但该函数是可选的,即可以提供也可以不提供该函数。DllMain的第一个参数为DLL模块的句柄, 第二个参数指明了系统调用Dll的原因,第三个参数可以用来判断是动态加载还是隐式加载。

9、MFC DLL

除了Win32 DLL,还可以编写MFC DLL,MFC DLL增加了对MFC类库的支持。

内容出处:《孙鑫VC++深入详解》

10、静态库的创建和使用

静态库的创建:新建项目-win32项目-程序类型选择“静态库”。然后分别添加头文件和源文件,内容容下所示。然后编译项目就可得到.lib静态库文件。

需要注意的一点是,静态库编译时的选项:“C/C++->代码生成->运行库” 应该与使用项目一致。

头文件:

//header.h

#ifndef __MY_LIB_HEADER__
#define __MY_LIB_HEADER__

int add(int a, int b);
int sub(int a, int b);

#endif


源文件:

//lib.cpp

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

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

静态库的使用有三种方式,首先都是将静态库的头文件添加到使用项目中,然后:
①、将静态库文件放置指定目录,然后将指定目录设置为项目的附加库目录:项目属性->链接器->常规->附加库目录。然后在项目属性->链接器->输入->附加依赖项(或项目属性->链接器->命令行)添加.lib静态库文件名。
②、将静态库文件放置指定目录。在文件中使用#pragma comment(lib, "path\\static_lib_name")来链接静态库。

11、DLL文件冲突解决

  有时候我们想要把一些DLL文件单独放到一个目录中,或者像我们的应用使用了cef浏览器,我们的应用exe和cef浏览器是在一个目录下的,所以DLL文件也在一个目录下,然后发生了我们应用里使用的DLL和cef浏览器使用的DLL是同一种但版本不同的DLL,这种情况,如果我们不想移动cef的exe的话,就可以使用延迟加载DLL。

  方法是在我们的项目属性-连接器-输入-延迟加载的DLL里输入冲突的DLL名,然后在Main()执行后调用SetDllDirectory(L"WeDlls\\"),然后把我们的DLL移动到WeDlls目录中,cef项目也是一样,在项目属性里设置延迟加载的DLL为冲突的DLL,在Main()执行后调用SetDllDirectory(L"cefDlls\\"),然后把cef的DLL移动到cefDlls目录中。

12、使用def文件生成.lib文件

  有时候第三方库只提供了dll动态库和.def文件,没有.lib导入库文件,我们可以通过.def文件来生成.lib导入库,方法是进入VS命令行,执行 lib /def:libdll.def命令,其中libdll.def即为第三方库提供的def文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值