动态链接库(DLL)

摘要:
动态链接库和静态链接库:动态链接库不能直接执行,它们通常不会接收消息。只有当另一个模块调用其包含的函数时,才能启动动态链接库。相反,动态链接在程序运行时发生。动态链接库的标准扩展名为。只有具有扩展名的动态链接库才能由Windows操作系统自动加载。GetProcAddress()描述:函数:从指定的动态链接库中检索导出函数或变量的地址。

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

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

它们是包含许多函数的独立文件,这些函数可以被应用程序和其他 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 ",sum(3,6)); printf("8-6=%d ",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 ",Sum(3,6)); printf("8-6=%d ",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 的动态链接库的创建与引用,以后再谈吧!

免责声明:文章转载自《动态链接库(DLL)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Linux C语言头文件搜索路径Haproxy 开启日志记录下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

MQTT协议学习及实践(Linux服务端,Android客户端的例子)

前言 MQTT(Message Queuing Telemetry Transport),是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT是专门针对物联网开发的轻量级传输协议。MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场...

搭建一套完整的ELK系统

ELK日志收集系统介绍   一  简单介绍             ELK部署搭建有很多成型的方案,这里推荐一种比较中规中矩的方案,它整合了logstash比较消耗资源以及当服务端临时宕机的时候出现数据丢失的问题,主要由filebeat+redis+logstash+elasticsearch+kibana构成,在每个需要收集日志的机器上面下发filebe...

VC6.0设置选项解读(转)

其实软件调试还是一个技术熟练过程,得慢慢自己总结,可以去搜索引擎查找一些相关的文章看看,下边是一篇关于VC6使用的小文章,贴出来大家看看: 大家可能一直在用VC开发软件,但是对于这个编译器却未必很了解。原因是多方面的。大多数情况下,我们只停留在“使用”它,而不会想去“了解”它。因为它只是一个工具,我们宁可把更多的精力放在C++语言和软件设计上。我们习惯于这...

armv8 汇编入门

准备环境 aarch64-linux-gnu-gcc: 可以通过下载 linaro 交叉编译工具链获得 qemu-system-aarch64 aarch64-linux-gnu-gdb: 可以通过下载 linaro 交叉编译工具链获得 一个简单的汇编程序 首先,创建一个空目录,例如,名为aarch64_assembly。然后,创建一个名为entr...

Linux12-内存管理

Linux内核第12章 内核不能像用户空间那样奢侈地使用内存,内核与用户空间不同,它不具备这种能力,它不支持简单便捷的内存分配方式。比如,内核一般不能睡眠,此外处理内存分配错误对内核来说也很困难。正是因为这些限制和内存分配机制不能太复杂,所以在内核中获取内存要比在用户空间复杂得多。 12.1 页 内核把物理页作为内存管理的基本单位。尽管处理器的最小可寻址单...

高仿微信新消息提示音功能

近期公司在做一个项目。有一个切换消息提示音的功能,能够切换本应用收到消息的提示音,而不影响系统提示音。我就依照微信的那个样式进行了编程,终于得到想要的效果。 转载请注明出处。谢谢:http://blog.csdn.net/harryweasley/article/details/46408037 怕有些人不知道怎么进入微信的新消息提示音功能,我这里说...