动态连接库(DLL)

动态连接库和静态连接库:windows

动态连接库通常不能直接执行,并且它们通常也不接收消息。函数

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

一个动态连接库只有在另一个模块调用其所包含的函数时才被启动。操作系统

 

“静态连接” 通常是在程序开发过程当中发生的,用于把一些文件连接在一块儿建立一个 Windows 可执行文件。线程

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

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

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

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

连接器从静态库中复制这些函数和数据,并把它们和应用程序的其余模块组合起来建立最终的可执行文件(.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;
}

显式调用:

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

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

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

#include"MyDLL.h"

如今就可使用这个 DLL 文件了。

二、将 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

 

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

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

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

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

     并且通常来讲,在程序运行过程当中只是在某个条件知足的状况下才须要访问某个dll中的函数。

     这样若是全部dll都被加载到内存中,资源浪费是比较严重的。

二、显示加载的方法则能够解决上述问题,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。