📜  DLL-快速指南

📅  最后修改于: 2020-10-15 10:52:46             🧑  作者: Mango


DLL-简介

动态链接是一种在运行时将应用程序链接到库的机制。这些库保留在它们自己的文件中,并且不会复制到应用程序的可执行文件中。 DLL在运行应用程序时而不是在创建时链接到应用程序。 DLL可能包含指向其他DLL的链接。

很多时候,DLL被放置在具有不同扩展名的文件中,例如.EXE,.DRV或.DLL。

DLL的优点

下面给出了具有DLL文件的一些优点。

使用更少的资源

DLL文件不会与主程序一起加载到RAM中。除非需要,否则它们不会占用空间。需要DLL文件时,将加载并运行该文件。例如,只要Microsoft Word的用户正在编辑文档,RAM中就不需要打印机DLL文件。如果用户决定打印文档,则Word应用程序将导致打印机DLL文件被加载并运行。

促进模块化架构

DLL帮助促进开发模块化程序。它可以帮助您开发需要多种语言版本的大型程序或需要模块化体系结构的程序。模块化程序的一个示例是具有许多可以在运行时动态加载的模块的记帐程序。

帮助轻松部署和安装

当DLL中的函数需要更新或修复时,DLL的部署和安装不需要将程序与DLL重新链接。此外,如果多个程序使用相同的DLL,则它们都将从更新或修复程序中受益。当您使用定期更新或修复的第三方DLL时,可能会更频繁地出现此问题。

如果在模块定义文件的IMPORTS部分中指定了DLL链接作为编译的一部分,则应用程序和DLL可以自动链接到其他DLL。另外,您可以使用Windows LoadLibrary函数显式加载它们。

重要的DLL文件

  • COMDLG32.DLL-控制对话框。

  • GDI32.DLL-包含用于绘制图形,显示文本和管理字体的众多功能。

  • KERNEL32.DLL-包含数百个用于管理内存和各种进程的功能。

  • USER32.DLL-包含许多用户界面功能。参与程序窗口的创建以及它们之间的交互。

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语句的头文件来分隔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文件夹的路径)。

DLL-注册

为了使用DLL,必须通过在注册表中输入适当的引用进行注册。有时会发生注册表引用损坏并且无法再使用DLL功能的情况。可以通过打开“开始-运行”并输入以下命令来重新注册DLL:

regsvr32 somefile.dll

此命令假定somefile.dll位于PATH中的目录或文件夹中。否则,必须使用DLL的完整路径。如下所示,也可以通过使用开关“ / u”注销DLL文件。

regsvr32 /u somefile.dll

这可用于打开和关闭服务。

DLL-工具

有几种工具可用来帮助您解决DLL问题。其中一些将在下面讨论。

依赖行者

Dependency Walker工具( depends.exe )可以递归扫描程序使用的所有依赖DLL。当您在Dependency Walker中打开程序时,Dependency Walker将执行以下检查:

  • 检查缺少的DLL。
  • 检查无效的程序文件或DLL。
  • 检查导入功能和导出功能是否匹配。
  • 检查循环依赖项错误。
  • 检查无效模块,因为这些模块用于其他操作系统。

通过使用Dependency Walker,您可以记录程序使用的所有DLL。它可以帮助防止和纠正将来可能发生的DLL问题。当您安装Microsoft Visual Studio 6.0时,Dependency Walker位于以下目录中:

drive\Program Files\Microsoft Visual Studio\Common\Tools

DLL通用问题解决器

DLL通用问题解决器(DUPS)工具用于审核,比较,记录和显示DLL信息。下表描述了组成DUPS工具的实用程序:

  • Dlister.exe-此实用工具枚举计算机上的所有DLL,并将信息记录到文本文件或数据库文件中。

  • Dcomp.exe-此实用程序将比较两个文本文件中列出的DLL,并生成包含差异的第三个文本文件。

  • Dtxt2DB.exe-此实用程序将使用Dlister.exe实用程序和Dcomp.exe实用程序创建的文本文件加载到dllHell数据库中。

  • DlgDtxt2DB.exe-此实用程序提供Dtxt2DB.exe实用程序的图形用户界面(GUI)版本。

DLL-提示

编写DLL时请牢记以下提示:

  • 使用正确的调用约定(C或stdcall)。

  • 注意传递给函数的参数的正确顺序。

  • 切勿使用直接传递给函数的参数来调整数组大小或连接字符串。请记住,您传递的参数是LabVIEW数据。更改数组或字符串大小可能会因覆盖LabVIEW内存中存储的其他数据而导致崩溃。如果您传递了LabVIEW Array Handle或LabVIEW String Handle并使用Visual C++编译器或Symantec编译器来编译DLL,则可以调整数组大小或连接字符串。

  • 在将字符串传递给函数,选择要传递的正确字符串类型。 C或Pascal或LabVIEW字符串句柄。

  • Pascal字符串的长度限制为255个字符。

  • C字符串以NULL终止。如果您的DLL函数以二进制字符串格式(例如,通过GPIB或串行端口)返回数字数据,则它可能会返回NULL值作为数据字符串的一部分。在这种情况下,传递短(8位)整数数组最可靠。

  • 如果使用数组或数据字符串,则始终传递足够大的缓冲区或数组以容纳函数放置在缓冲区中的所有结果,除非您将它们作为LabVIEW句柄传递,在这种情况下,可以使用CIN调整它们的大小Visual C++或Symantec编译器下的函数。

  • 如果使用_stdcall,请在模块定义文件的EXPORTS部分中列出DLL函数。

  • 在模块定义文件EXPORTS部分中列出其他应用程序调用的DLL函数,或在函数声明中包含_declspec(dllexport)关键字。

  • 如果使用C++编译器,请在头文件中使用extern .C。{}语句导出函数,以防止名称篡改。

  • 如果您正在编写自己的DLL,则在另一应用程序将DLL加载到内存中时,不应重新编译DLL。重新编译DLL之前,请确保使用该特定DLL的所有应用程序都已从内存中卸载。它确保DLL本身不会加载到内存中。如果您忘记了这一点,并且编译器没有警告您,则可能无法正确重建。

  • 用另一个程序测试您的DLL,以确保函数(和DLL)的行为正确。使用编译器的调试器或简单的C程序(可在其中调用DLL中的函数,将有助于您确定DLL或LabVIEW相关的固有困难。

DLL-示例

我们已经看到了如何编写DLL以及如何创建“ Hello World”程序。该示例必须使您对创建DLL的基本概念有所了解。

在这里,我们将描述使用Delphi,Borland C++和VC++创建DLL的描述。

让我们一一列举这些例子。