📜  DLL-如何编写

📅  最后修改于: 2020-10-15 10:50:18             🧑  作者: Mango


首先,我们将讨论在开发自己的DLL时应考虑的问题和要求。

DLL的类型

在应用程序中加载DLL时,有两种链接方法可让您调用导出的DLL函数。链接的两种方法是-

  • 加载时动态链接,以及
  • 运行时动态链接。

加载时动态链接

在加载时动态链接中,应用程序对导出的DLL函数(例如本地函数)进行显式调用。若要使用加载时动态链接,请在编译和链接应用程序时提供头文件(.h)和导入库文件(.lib)。当您这样做时,链接程序将为系统提供加载DLL并在加载时解析导出的DLL函数位置所需的信息。

运行时动态链接

在运行时动态链接中,应用程序调用LoadLibrary函数或LoadLibraryEx函数以在运行时加载DLL。成功加载DLL之后,您可以使用GetProcAddress函数来获取要调用的导出DLL函数的地址。使用运行时动态链接时,不需要导入库文件。

以下列表描述了在加载时动态链接和运行时动态链接之间进行选择的应用程序标准-

  • 启动性能-如果应用程序的初始启动性能很重要,则应使用运行时动态链接。

  • 易于使用-在加载时动态链接中,导出的DLL函数类似于本地函数。它可以帮助您轻松调用这些函数。

  • 应用程序逻辑-在运行时动态链接中,应用程序可以分支以根据需要加载不同的模块。开发多语言版本时,这一点很重要。

DLL入口点

创建DLL时,可以选择指定入口点函数。当进程或线程将自身附加到DLL或将自身与DLL分离时,将调用入口点函数。您可以使用入口点函数来初始化或销毁DLL所需的数据结构。

此外,如果应用程序是多线程的,则可以使用线程本地存储(TLS)来分配入口点函数每个线程专用的内存。以下代码是DLL入口点函数的示例。

BOOL APIENTRY DllMain(
   HANDLE hModule,       // Handle to DLL module 
   DWORD ul_reason_for_call, 
   LPVOID lpReserved )     // Reserved
{
   switch ( ul_reason_for_call )
   {
      case DLL_PROCESS_ATTACHED:
      // A process is loading the DLL.
      break;
      
      case DLL_THREAD_ATTACHED:
      // A process is creating a new thread.
      break;
      
      case DLL_THREAD_DETACH:
      // A thread exits normally.
      break;
      
      case DLL_PROCESS_DETACH:
      // A process unloads the DLL.
      break;
   }
   return TRUE;
}

当入口点函数返回FALSE值时,如果使用加载时动态链接,则应用程序将不会启动。如果使用运行时动态链接,则不会加载单个DLL。

入口点函数应仅执行简单的初始化任务,而不应调用任何其他DLL加载或终止函数。例如,在入口点函数,您不应直接或间接调用LoadLibrary函数或LoadLibraryEx函数。此外,过程终止时,您不应调用FreeLibrary函数。

警告-在多线程应用程序中,请确保对DLL全局数据的访问已同步(线程安全),以避免可能的数据损坏。为此,请使用TLS为每个线程提供唯一的数据。

导出DLL函数

若要导出DLL函数,可以将函数关键字添加到导出的DLL函数,或者创建列出导出的DLL函数的模块定义(.def)文件。

要使用函数关键字,必须使用以下关键字声明要导出的每个函数-

__declspec(dllexport)

要在应用程序中使用导出的DLL函数,必须使用以下关键字声明要导入的每个函数-

__declspec(dllimport)

通常,您将使用一个具有define语句和ifdef语句的头文件来分隔export语句和import语句。

您还可以使用模块定义文件来声明导出的DLL函数。使用模块定义文件时,不必将函数关键字添加到导出的DLL函数中。在模块定义文件中,声明DLL的LIBRARY语句和EXPORTS语句。以下代码是定义文件的示例。

// SampleDLL.def
//
LIBRARY "sampleDLL"

EXPORTS
   HelloWorld

编写示例DLL

在Microsoft Visual C++ 6.0中,可以通过选择Win32动态链接库项目类型或MFC AppWizard(dll)项目类型来创建DLL。

下面的代码是通过使用Win32动态链接库项目类型在Visual C++中创建的DLL的示例。

// SampleDLL.cpp

#include "stdafx.h"
#define EXPORTING_DLL
#include "sampleDLL.h"

BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved )
{
   return TRUE;
}

void HelloWorld()
{
   MessageBox( NULL, TEXT("Hello World"), 
   TEXT("In a DLL"), MB_OK);
}
// File: SampleDLL.h
//
#ifndef INDLL_H
#define INDLL_H

   #ifdef EXPORTING_DLL
      extern __declspec(dllexport) void HelloWorld() ;
   #else
      extern __declspec(dllimport) void HelloWorld() ;
   #endif

#endif

调用示例DLL

下面的代码是Win32应用程序项目的示例,该项目在SampleDLL DLL中调用导出的DLL函数。

// SampleApp.cpp 

#include "stdafx.h"
#include "sampleDLL.h"

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{     
   HelloWorld();
   return 0;
}

–在加载时动态链接中,必须链接在构建SampleDLL项目时创建的SampleDLL.lib导入库。

在运行时动态链接中,您使用与以下代码相似的代码来调用SampleDLL.dll导出的DLL函数。

...
typedef VOID (*DLLPROC) (LPTSTR);
...
HINSTANCE hinstDLL;
DLLPROC HelloWorld;
BOOL fFreeDLL;

hinstDLL = LoadLibrary("sampleDLL.dll");

if (hinstDLL != NULL)
{
   HelloWorld = (DLLPROC) GetProcAddress(hinstDLL, "HelloWorld");
    
   if (HelloWorld != NULL)
      (HelloWorld);

   fFreeDLL = FreeLibrary(hinstDLL);
}
...

编译并链接SampleDLL应用程序时,Windows操作系统将在以下位置按以下顺序搜索SampleDLL DLL-

  • 应用程序文件夹

  • 当前文件夹

  • Windows系统文件夹( GetSystemDirectory函数返回Windows系统文件夹的路径)。

  • Windows文件夹( GetWindowsDirectory函数返回Windows文件夹的路径)。