DLL - 如何编写



首先,我们将讨论在开发您自己的 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语句的头文件来分离导出语句和导入语句。

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

// SampleDLL.def
//
LIBRARY "sampleDLL"

EXPORTS
   HelloWorld

编写示例 DLL

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

以下代码是在 Visual C++ 中使用 Win32 动态链接库项目类型创建的 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

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

// 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 文件夹的路径)。

广告