当我们进行程序的编写的时候,经常需要将一些资源进行重复利用等操作。于是,微软就提出了DLL(动态链接库),它可以很好的将资源进行重复利用。
当一个进程加载一个DLL的时候呢,一般都会执行DllMain这个动态链接库的入口点(有时候不会,后面再讲),来看下它的声明:
BOOL WINAPI DllMain(
HINSTANCE hinstDLL, // dll模块的内存基址
DWORD fdwReason, // 加载本dll的原因
LPVOID lpvReserved // 系统保留
);
其中,fdwReason有四个原因,第一个是DLL_PROCESS_ATTACH,表示进程加载本dll时。第二个是DLL_THREAD_ATTACH,表示线程加载本dll时。第三个是DLL_THREAD_DETACH,进程卸载本dll时,第四个是DLL_PROCESS_DETACH,线程卸载本dll时。
因为它是被刚加载或卸载的时候调用的,因此我们可以在该函数内写一些初始化的工作和释放资源的工作。
在DLL中,我们编写完要共享的代码后,我们需要对这些代码进行导出,变成导出函数或类等才能被我们共享使用。下面来介绍两种DLL导出函数的两种方式。
第一种导出方式是使用def文件,里边的内容格式如下:
LIBRARY 项目名
EXPORTS
导出函数名1
导出函数名2
写完之后,将后缀名改为.def格式,将其添加到工程里边,然后在VS里边右键属性----配置信息/链接器/输入/模块定义文件这一栏添加该文件即可,然后我们生成解决方案,我们的函数就被导出了。我们可以使用Dependency Walker工具来查看是否已经被导出了。
第二种导出的方式是使用__declspec(export)的方式。我们在要导出的函数名前添加这样一句话即可。如果你使用的是VS的C++编译器,最好在该句话前在添加一句extern "C"来声明是以C语言的标准来进行编译的,为什们呢?因为VS的C++编译器会自动的将我们的函数名以微软的方式添加一些信息。以下是一个声明导出函数的例子:
extern "C" __declspec(dllexport) DWORD TestFun(int a, int b)
{
return a + b;
}
知道了怎样在dll工程中导出一个函数后,我们在来看下如何在别的工程中使用我们导出的函数。调用导出函数,也有两种方式,一种是隐式调用,一种是显示调用。
先来看下隐式调用。在我们的dll工程中,生成解决方案后,会生成一个dll文件和一个.lib文件。其中.dll文件里边包含了代码的实现等信息。而.lib文件则包含了函数的内存地址信息,用于指明我们的函数在哪个内存地址。因此,我们可以链接这个lib文件来使用我们的函数。在此之前呢,我们还要声明一下我们要使用哪个函数,然后再来链接,声明时要在函数前加一句__declspec(dllimport)表示导入一个函数,下面是一个示例:
extern "C" __declspec(dllimport) int TestFun(int a, int b);<span style="white-space:pre"> </span>//声明一个导入函数
#pragma comment(lib,"testLib.lib")<span style="white-space:pre"> </span>//链接我们的lib文件
显式调用。假设我们没有.lib文件,那么我们应该使用显示调用。显示调用就是调用LoadLibrary函数来动态的加载一个dll文件,然后用GetProcAddress来确定导出函数的地址。它们的声明如下:
HMODULE LoadLibrary(
LPCTSTR lpFileName // 模块名
);
FARPROC GetProcAddress(
HMODULE hModule, // 上一个函数的返回值
LPCSTR lpProcName // 函数名
);
下面是一个显示调用的示例代码:
#include <stdio.h>
#include <windows.h>
//定义一个函数指针,返回值和参数要与导出的函数相同
typedef int(*lpTestFun)(int,int);
int _tmain(int argc, _TCHAR* argv[])
{
HMODULE hDll;
lpTestFun testFun;
//加载一个dll
hDll = LoadLibrary("testLib.dll");
if (hDll)
{
//获取要导出函数的地址
testFun = (lpTestFun)GetProcAddress(hDll, "testFun");
if (testFun)
{
printf("testFun result:%d\n", testFun(3, 2));
}
}
return 0;
}
在文章开头讲过我们可能不需要DllMain这个函数入口点,当我们动态的加载一个函数时,除了调用LoadLibrary这个函数外,我们还可以调用它的加强版LoadLibraryEx,其声明如下:
HMODULE LoadLibraryEx(
LPCTSTR lpFileName, // 文件名
HANDLE hFile, // 系统保留,必须为NULL
DWORD dwFlags // 一些标志位
);
该函数的第三个参数里边,有一个DONT_RESOLVE_DLL_REFERENCES这个标志位,它表示当使用这个标志位的时候呢,加载一个Dll时,程序不会执行我们的DllMain这个入口点函数。为什么要使用这个函数呢,假如我们在网上下载了一个dll,它可能在DllMain里边写了一些邪恶的代码,如果我们调用LoadLibrary去加载时,就可能引发一些严重的后果。这时加强版就起到了作用。