Fork me on GitHub

动态链接库(DLL)

       DLL(Dynamic Link Library)也就是动态链接库,是一个可以被其他应用程序调用的程序模块,其中封装了可以被调用的资源或函数。DLL文件属于可执行文件,它符合Windows系统的PE文件格式,它依附于EXE文件创建的进程来执行,不能单独运行。一个DLL文件可以被多个进程所装载调用。

一、DllMain( )函数

       DLL程序的入口函数为DllMain( )函数。使用VC++创建DLL程序,系统默认会生成如下代码

 1 BOOL APIENTRY DllMain( HMODULE hModule,//指向DLL本身的实例句柄;
 2                        DWORD  ul_reason_for_call,//指明了DLL被调用的原因;
 3                        LPVOID lpReserved   //保留值,暂时没有意义;
 4                      )
 5 {
 6     switch (ul_reason_for_call)
 7     {
 8     case DLL_PROCESS_ATTACH:
 9     case DLL_THREAD_ATTACH:
10     case DLL_THREAD_DETACH:
11     case DLL_PROCESS_DETACH:
12         break;
13     }
14     return TRUE;
15 }

       但在使用过程中,DllMain( )函数不是必须由我们提供、定义的, 这是因为Windows在找不到DllMain的时候,系统会从其它运行库中引入一个不做任何操作的缺省DllMain函数版本。

       根据编写规范,Windows必须查找并执行DLL里的DllMain函数作为加载DLL的依据,它使得DLL得以保留在内存里。DllMain( )函数并不属于导出函数,而是DLL的内部函数。这意味着不能直接在应用工程中引用DllMain函数,它是自动被调用的。不要将DllMain写成DLLMain

 

二、DLL函数的导出

方式1:关键词_declspec(dllexport) 导出方式

       首先添加一个用于导出函数的头文件,在头文件中放置需要导出的函数声明。

       在使用C语言编写DLL函数时,在函数的声明前加上_declspec(dllexport)  链接器便会在生成的DLL文件中嵌入一个导出符号表。使用Depends工具可以查看到。使用C++编写DLL时,则要在函数声明前加上extern "C" _declspec(dllexport),可避免编译器对函数名进行改编。

       注意!当C/C++编写的函数使用了__stdcall也就是WINAPI调用约定时,即使使用的是extern "C" _declspec(dllexport),Microsoft的编译器还是会对函数名进行改编。改编的方法是给函数名添加下划线前缀和一个特殊的后缀,该后缀由一个@符号后跟作为参数传给函数的字节数组成。

extern "C" _declspec(dllexport) LONG __stdcall MyFunc(int a, int b);//该函数在DLL的导出段中被导出为_MyFunc@8。

如果想要导出未经改编的函数名,可在DLL的源文件中加入如下代码:

#pragma comment(linker, "/export:MyFunc=_MyFunc@8")

使用这种方法时,DLL实际上导出了两个函数MyFunc和_MyFunc@8 ,而使用下面介绍的Def文件导出方法则不会存在这个问题。 

 

方式2:Def文件导出

■文件中的第一个语句必须是 LIBRARY 语句。此语句将 .def 文件标识为属于 DLL。LIBRARY 语句的后面是 DLL 的名称。链接器将此名称放到 DLL 的导入库中。

■EXPORTS 语句列出名称,可能的话还会列出 DLL 导出函数的序号值。通过在函数名的后面加上 @ 符和一个数字,给函数分配序号值。当指定序号值时,序号值的范围必须是从 1 到 N,其中 N 是 DLL 导出函数的个数。

■注释语句,在语句前面加分号 “;” 。

例如:

;DLLTest.def : Declares the module parameters for the DLL.
LIBRARY   "DLLTest"
EXPORTS
   add   @1
   fun   @2

 

三、对DLL程序的调用

       对DLL的调用分为两种,静态调用和动态调用。

       我们可以首先创建一个名为MyDLL的DLL工程,其中添加DLLShow函数。

1 #include <Windows.h>
2 extern "C" _declspec(dllexport) void MyDLL(char* szMessage);
3  
4 void MyDLL(char* szMessage)
5 {
6     MessageBox(NULL,szMessage,"MyDLL",MB_OK);
7 }

       编译生成的MyDLL.dll和MyDLL.lib两个文件将在调用中使用到。

       调用方法一:静态调用。

       创建一个调用DLL文件的EXE程序,命名为CallDLL。如下:

#include<Windows.h>
#pragma comment(lib,"MyDLL")
 
extern "C" void DLLShow(char* szMessage);
 
int _stdcall WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance, __in LPSTR lpCmdLine, __in int nShowCmd )
{
     DLLShow("HelloWorld!");
     return 0;
}

       直接编译的话,会产生一个连接错误。无法打开文件“MyDLL.lib”,需要将之前编译好的MyDLL.lib和MyDLL.dll文件添加到工程目录下。

       注意,是工程目录下(有.cpp文件的目录),添加到其它目录下仍旧会提示这个错误。

       静态调用是通过连接器将DLL的导出函数写进可执行文件中。下面提到的动态调用则是在程序运行时完成调用的。

       调用方法二:动态调用。

       首先需要知道DLL中函数的声明。然后通过以下几个步骤进行动态的加载。

       1、根据函数声明构建函数指针。

       2、使用LoadLibrary函数加载dll文件。

       3、使用GetProcAddress将DLL中的函数指针赋值给我们构建的函数指针。

       到此,函数指针就具有了DLL函数一样的功能。

 1 #include<Windows.h>
 2 typedef void (*PFUN)(char*);
 3 int _stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd )
 4 {
 5      HMODULE hModule =LoadLibrary("Test.dll");
 6      if (hModule==NULL)
 7      {
 8           MessageBox(NULL,"MyDLL文件不存在","加载失败",MB_OK);
 9           return -1;
10      }
11      PFUN pFun=(PFUN)GetProcAddress(hModule,"DLLShow"); 
12      pFun("Hello World!");
13      return 0;
14 }        

       相比两种调用方式,动态调用有很多的优点。比如有些动态链接库没有提供lib文件,也有时候我们可以能要编写DLL文件供别人使用、或自己在其他语言中调用,等等很多情况下,我们只有动态调用一种方式。

 

四、卸载DLL模块

      当进程不再需要引用DLL中的符号时,我们应该将其从进程的地址空间中卸载。

      如果DLL没有创建线程,我们使用FreeLibrary函数

BOOL  FreeLibrary(HMODULE hInstDll);

      我们需要传入标识DLL的句柄,也就是我们使用调用的LoadLibrary函数的返回值。

      如果DLL创建了其他线程,我们需要使用FreeLibraryAndExitThread函数。

VOID FreeLibraryAndExitThread(HMODULE  hInstDll,  DWORD  dwExitCode);

 

posted on 2015-09-22 15:51  地精的贪婪  阅读(625)  评论(0编辑  收藏  举报