动态链接库(DLL)

动态链接库和静态链接库:

动态链接库一般不能直接执行,而且它们一般也不接收消息。

它们是包含许多函数的独立文件,这些函数可以被应用程序和其他 DLL 调用以完成某些特定的工作。

一个动态链接库只有在另外一个模块调用其所包含的函数时才被启动。

 

“静态链接” 一般是在程序开发过程中发生的,用于把一些文件链接在一起创建一个 Windows 可执行文件。

这些文件包括各种各样的对象模块(.OBJ),运行时库文件(.LIB),通常还有已编译的资源文件(.RES)。

与其相反,动态链接则发生在程序运行时。 

静态库:函数和数据被编译进一个二进制文件,扩展名为(.lib)。

在使用静态库的情况下,在编译链接可执行文件时:

链接器从静态库中复制这些函数和数据,并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.exe)。

当发布产品时,只需要发布这个可执行文件,并不需要发布被使用的静态库。

 

“动态链接” 是指 Windows 的链接过程,在这个过程中它把模块中的函数调用与在库模块中的实际函数链接在一起。

动态库:在使用动态库时,往往提供两个文件:一个导入库(.lib,非必须) 和一个(.dll)文件。

导入库和静态库本质上的区别

静态库本身就包含了实际执行代码和地址符号表等数据。

而对于导入库而言,其实际的执行代码位于动态库中导入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。

动态链接库的标准扩展名是(.dll)。只有扩展名为(.dll)的动态链接库才能被 Windows 操作系统自动加载。

如果该文件有另外的扩展名,则程序必须明确地用 LoadLibrary() 或 LoadLibraryEx() 加载相应模块。

 

编写动态链接库

我们编写的程序都可以根据 UNICODE 标识符的定义编译成能够处理 UNICODE 或者 非 UNICODE 字符串的程序。

在创建一个 DLL 时,对于任何有字符或者字符串参数的函数,它都应该包括 UNICODE 和非 UNICODE 两个版本。

 VC++6.0 编译器下:

File->New->Win32 Dynamic-Link Library->An empty DLL project || An Simple DLL project

An empty DLL project 和 An Simple DLL project 的区别是:后者有个简单的示例代码。

我以前者为例:

新建两个文件:MyDLL.h,MyDLL.cpp。

// MyDLL.h
#define
Import extern "C" _declspec(dllexport) Import int sum(int a, int b); Import int sub(int a, int b);

...................................................................................................................................................................................................................................................................

// MyDLL.cpp
#include"MyDLL.h" Import int sum(int a, int b) { return a+b; } Import int sub(int a, int b) { return a-b; }

最后编译 MyDLL.cpp,如果成功则在 Debug 里可以看到 MyDLL.dll。

提示:

函数声明前加上 "_declspec(dllexport)" 表明函数将输出为动态链接库,是必不可少的。

在相同的调用约定下,采用不同的编译器,对函数名的修饰是不一样的。

例如:C语言和C++语言导出的dll文件中,函数的修饰名是不一样的。

如果要C语言风格的(.dll)文件,就要再加上 "extern C" 进行修饰,或者把源文件名的后缀改为(.c)。

如果是要C++风格的(.dll)文件,则源文件名后缀必须为(.cpp)。

 

调用方式:

隐式调用:

将 MyDLL.lib 和 MyDLL.h 拷贝到需要应用该 DLL 的工程的目录下,将 MyDLL.dll 拷贝到产生的应用程序的目录下,

并在需要应用该 DLL 中的函数的 CPP 文件开头添加如下几行:

#include"MyDLL.h"
#pragma comment(lib,"MyDLL")

例如:

// MyDLL.cpp
#include<stdio.h> #include"MyDLL.h" #pragma comment(lib,"MyDLL") int main(void) { printf("3+6=%d\n",sum(3,6)); printf("8-6=%d\n",sub(8,6)); return 0; }

 

显式调用:

1、将 MyDLL.lib 和 MyDLL.h 拷贝到需要应用该 DLL 的工程的目录下,将 MyDLL.dll 拷贝到产生的应用程序的目录下,

      在添加 CPP 文件之前一步,需要在 Project->Setting->Link->Object/library modules 的框中增加 MyDll.lib 这个库。

      最后,在创建的 CPP 文件的开头添加这一行:

#include"MyDLL.h"

      现在就可以使用这个 DLL 文件了。

2、将 MyDLL.lib 和 MyDLL.h 拷贝到需要应用该 DLL 的工程的目录下,将 MyDLL.dll 拷贝到产生的应用程序的目录下,

      简单的调用 DLL 文件的 CPP 文件如下:

// MyDLL.cpp
#include<stdio.h> #include<windows.h> int main(void) { HMODULE hModule; typedef int (*pSum)(int a, int b); typedef int (*pSub)(int a, int b); pSum Sum = NULL; pSub Sub = NULL; hModule = LoadLibrary("MyDLL.dll"); Sum = (pSum)GetProcAddress(hModule,"sum"); Sub = (pSum)GetProcAddress(hModule,"sub"); printf("3+6=%d\n",Sum(3,6)); printf("8-6=%d\n",Sub(8,6)); return 0; }

介绍一下两个函数:

LoadLibrary() 介绍:

功能:将指定模块加载到调用进程的地址空间中。指定的模块可能会导致加载其他模块。

函数原型:HMODULE WINAPI LoadLibrary(

                  LPCTSTR lpFileName // 动态链接库的名字。

                  );

返回值:如果函数成功, 则返回值是模块的句柄。如果函数失败, 返回值为 NULL。

 

GetProcAddress() 介绍:

功能:从指定的动态链接库 (DLL) 中检索导出函数或变量的地址。

函数原型:FARPROC WINAPI GetProcAddress(

                  HMODULE hModule,  // 模块的句柄。

                  LPCSTR  lpProcName  // 函数或变量的名字, 或函数的序号值。

                  );

返回值:如果函数成功, 则返回值是导出函数或变量的地址。如果函数失败, 返回值为 NULL。

 

再来看一下这段代码:     typedef int (*pSum)(int a, int b);

我们通常见到的都是:     typedef unsigned Long uLong;

其实 typedef int (*pSum)(int a, int b); 的意思也挺好理解的:

就是定义一个别名为 pSum 函数指针,指向返回值为 int 型并且含有两个 int 型参数的函数指针。


VS2017下:

其实 VS2017 下的步骤和上面也一样,这里介绍一下模块定义文件创建 DLL 文件:

文件->新建->项目->DLL

// MyDLL.cpp
#include "stdafx.h" int sum(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; }

解决方案->资源文件->添加->新建项->代码->模块定义文件

// Source.def
LIBRARY EXPORTS sum sub

项目->MyDLL属性->链接器->输入->模块定义文件:Source.def

最后:生成->MyDLL

 

隐式调用和显式调用的对比:

1、隐式链接方式实现简单,一开始就把dll加载进来,在需要调用的时候直接调用即可。

     但是如果程序要访问十多个 DLL 文件,如果都采用隐式链接方式加载他们的话,在该程序启动时:

     这些dll都需要被加载到内存中,并映射到调用进程的地址空间,这样将加大程序的启动时间。

     而且一般来说,在程序运行过程中只是在某个条件满足的情况下才需要访问某个dll中的函数。

     这样如果所有dll都被加载到内存中,资源浪费是比较严重的。

2、显示加载的方法则可以解决上述问题,DLL 只有在需要用到的时候才会被加载到内存中。

     另外,其实采用隐式链接方式访问 DLL 时,在程序启动时也是通过调用 LoadLibrary() 加载该进程需要的动态链接库的。

 

带有 API 函数的 动态链接库:

创建方式相同,只是得有个 DllMain() 入口函数。

// Dll.h 
/*
#define Import extern "C" _declspec(dllexport)
Import void Text(void);
*/
#include"Dll.h"
#include<windows.h>
BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpRserved)
{
   switch(ul_reason_for_call)
    {
     case DLL_PROCESS_ATTACH:
          Text();
          break;
     case DLL_PROCESS_DETACH:
          break;
     case DLL_THREAD_ATTACH:
          break;
     case DLL_THREAD_DETACH:
          break;
    }
   return 0;
}
Import void Text(void)
{
   MessageBox (NULL, TEXT ("Hello, World!"), TEXT ("HelloMsg"), MB_OKCANCEL);
}

 

DllMain() 简介:

功能:动态链接库 (DLL) 中的可选入口点。

函数原型:BOOL APIENTRY DllMain(
                  HMODULE hModule,  // DLL 模块的句柄。
                  DWORD  ul_reason_for_call,  // 指示为什么调用 DLL 入口点函数的原因代码。
                  LPVOID   lpvReserved // 保留值,通常为 NULL。
                  );

参数:ul_reason_for_call

含义
DLL_PROCESS_ATTACH 当 dll 文件第一次被进程加载时,调用该值下的 DLL 函数。
DLL_PROCESS_DETACH 当 dll 文件从进程中被解除时,调用该值下的 DLL 函数。TerminateProcess() 除外。
DLL_THREAD_ATTACH 当进程创建一个线程时,新建的线程将调用该值下的 DLL 函数。
DLL_THREAD_DETACH 当线程调用 ExitThread() 结束时,该进程将调用该值下的 DLL 函数。TerminateThread() 除外。

返回值:当系统使用 DLL_PROCESS_ATTACH 值调用 DllMain 函数时,

               如果调用成功则返回 TRUE,否则返回 FALSE。

 

关于 MFC 的动态链接库的创建与引用,以后再谈吧!

posted @ 2018-08-23 14:16  M-Anonymous  阅读(13201)  评论(1编辑  收藏  举报