Windows下静态库与动态库的建立与使用

Windows下静态库与动态库的建立与使用

学习内容:本博客介绍了Windows下使用Visual C++ 6.0制做与使用静态库与动态库的方法。html

————————CONTENTS————————


一.什么是静态库与动态库?

库(Library)是能够复用的代码,在一些大型项目中经常会用到库。linux

本质上说,库是一种可执行代码的二进制形式,能够被操做系统载入内存执行。程序员

在前期的课程中,咱们学习了Linux下静态库与动态库的使用,Windows下与之对应:windows

  • 静态库:linux下.a文件、windows下.lib文件
  • 动态库:linux下.so文件、windows下.dll文件

所谓静态、动态是指连接。回顾一下将一个程序编译成可执行文件的步骤:app

静态库与动态库的区别就在于,【连接阶段】如何处理库,从而链接成可执行程序。函数


1.静态库工具

之因此成为【静态库】,是由于在连接阶段,会将汇编生成的目标文件.o与引用到的库一块儿连接打包到可执行文件中。所以对应的连接方式称为静态连接。学习

试想一下,静态库与汇编生成的目标文件一块儿连接为可执行文件,那么静态库一定跟.o文件格式类似。其实一个静态库能够简单当作是一组目标文件(.o/.obj文件)的集合,即不少目标文件通过压缩打包后造成的一个文件。测试

静态库的特色可总结为:ui

  1. 静态库对函数库的连接是放在编译时期完成的。
  2. 程序在运行时与函数库再无瓜葛,移植方便。
  3. 浪费空间和资源,由于全部相关的目标文件与牵涉到的函数库被连接合成一个可执行文件。

Linux下使用ar工具、Windows下使用lib.exe,将目标文件压缩到一块儿,而且对其进行编号和索引,以便于查找和检索。通常建立静态库的步骤如图所示:


2.动态库

经过上面的介绍发现静态库,容易使用和理解,也达到了代码复用的目的,那为何还须要动态库呢?

为何须要动态库,其实也是静态库的特色致使。

  • 空间浪费是静态库的一个问题。

  • 另外一个问题是静态库会对程序的更新、部署和发布带来麻烦。若是静态库lib.lib更新了,因此使用它的应用程序都须要从新编译、发布给用户(对于玩家来讲,多是一个很小的改动,却致使整个程序从新下载,全量更新)。

动态库在程序编译时并不会被链接到目标代码中,而是在程序运行是才被载入。不一样的应用程序若是调用相同的库,那么在内存里只须要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只须要更新动态库便可,增量更新

动态库特色总结以下:

  1. 动态库把对一些库函数的连接载入推迟到程序运行的时期。
  2. 能够实现进程之间的资源共享(所以动态库也称为共享库)
  3. 将一些程序升级变得简单。
  4. 甚至能够真正作到连接载入彻底由程序员在程序代码中控制(显式调用)。

综上所述,静态库与动态库的不一样点在于:代码被载入的时刻不一样

  • 静态库在程序编译时会被链接到目标代码中,程序运行时将再也不须要该静态库,所以体积较大
  • 动态库在程序编译时并不会被链接到目标代码中,而是在程序运行是才被载入,所以在程序运行时还须要动态库存在,所以代码体积较小
  • 动态库的好处是,不一样的应用程序若是调用相同的库,那么在内存里只须要有一份该共享库的实例。

返回目录


二.静态库的建立与使用


1.静态库的建立

1.咱们先来完成准备工做。打开Visual C++ 6.0,点击左上角/File->New...,选择须要新建的对象:

选择Workspaces选项卡,输入Workspaces name以及Location,新建一个工做区:

本篇博客所用到的Project都建在这个工做区就能够啦~

2.选中咱们新建的workspace,右击选择Add New Project to Workspace...

咱们先尝试建立静态库,所以在Project选项卡选择Win32 Static Library,并在右侧输入Project name便可完成项目建立。

接下来一路next,一个Win32 Static Library格式的Project就建成了:

3.注意到一个工程下面有两个文件夹,分别是Source FilesHeader Files。顾名思义,Source Files用来存放源文件,如.cpp等;Header Files则用来存放头文件。

咱们先来建一个头文件,存放函数说明。

选中Header Files文件夹,点击File->New...->Files->C/C++ Header File,并输入文件名,完成建立。

内容为:

int statlib_demo(int a, int b);

4.一样的方法,在Source Files中新建staticlib.cpp,此次须要选中C++ Source File

内容为:

#include "staticlib.h"
int statlib_demo(int a, int b)
{
    if (a > b)
        return a-b;
    else
        return a+b;
}

就此完成了代码基本的编写。

5.选择这个Project,依次点击CompileBuild按钮:

打开Project所在文件夹中的Debug文件夹,能够看到成功生成lib.lib,这就是咱们须要的静态库啦~

返回目录


2.静态库的使用

接下来咱们编写代码,对生成的静态库进行测试。

1.与前面相似,此次在workspace中新建一个Win32 Console Application的Project:

选择A "Hello, world!" application.

能够看到在工做区生成了一个名为libTest的Project,其中包含一些自动生成的文件和代码:

2.将刚才生成的lib.lib拷贝到新建的这个项目文件夹下,以进行后续的调用。

3.修改这个Project中的代码,完成对静态库调用的测试:

libTest.cpp

#include "stdafx.h"
#include "../lib/staticlib.h"  //要注意在这里添加头文件的位置
int main(int argc, char* argv[])
{
    printf("Hello World!\n");
    int iRet;
    iRet = statlib_demo(3,5);
    printf("the result of value is %x\n",iRet);
    return 0;
}

StdAfx.h:只须要在#include <stdio.h>下一行加上#pragma comment (lib,"lib")便可。其中,第一个“lib”表明连接一个lib库,第二个“lib”是这个库的名字,需根据实际状况修改。

StdAfx.cpp不须要作改动。

4.完成了以上代码的编写和修改,Build这个项目,并Execute Program就能够了。运行结果以下:

到此为止,咱们完成了静态库的编写和测试。最关键的点就是#pragma comment(lib,"XXX")这句预处理指令了。

静态连接时,编译器将函数和过程都编译到exe文件,且函数的相对位置在连接时已经肯定。多个程序调用同一个函数时,内存中保存多份函数。

返回目录


三.动态库的建立与使用


1.动态库的建立

DLL(Dynamic Linkable Library),动态连接库,能够向程序提供一些函数、变量或类。这些能够直接拿来使用。

DLL中通常定义有两种类型的函数:导出函数和内部函数。其中:

  • 导出函数能够被外部程序调用;
  • 内部函数只能在DLL内部使用。

为了使生成的dll文件能够被外部程序调用,咱们主要关注导出函数的生成与使用。

从dll中声明导出函数有两种方式:

  • (1)用模块定义(.def) 文件声明,.def文件为连接器提供了有关被连接程序的导出、属性及其余方面的信息;
  • (2) 用__declspec(dllexport)来声明函数

下面咱们来详细了解这两种方式的用法。

1、使用.def文件

1.一样地,在workspace新建一个Project,选择为Win32 Dynamic-Link Library

选择A simple DLL project.

能够看到在工做区生成了一个名为dll_def的Project,其中包含一些自动生成的文件和代码:

2.修改这个Project中的代码,完成动态库的编写:

dll_def.cpp

#include "stdafx.h"
#include "dll_def.h"
BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
                     )
{
    return TRUE;
}

int cmp3_demo(int a, int b)
{
    if (a > b)
        return a;
    else
        return b;
}

Header Files中新建一个dll_def.h,内容为:

int cmp3_demo(int a, int b);

其余已存在的文件不须要作修改。

3.最关键的步骤到了!!!——编写.def文件。

  • DEF文件是一个包含EXE文件或DLL文件声明的文本文件;
  • 每一个.DEF文件至少必须包含LIBRARY语句和EXPORTS语句

.def文件的规则为:

  1. LIBRARY语句说明.def文件相应的DLL;
  2. EXPORTS语句后列出要导出函数的名称。能够在.def文件中的导出函数名后加@n,表示要导出函数的序号为n(在进行函数调用时,这个序号将发挥其做用);
  3. .def 文件中的注释由每一个注释行开始处的分号 (;) 指定,且注释不能与语句共享一行。

针对咱们这个Project,须要在Source Files中新建一个dll_def.def文件,内容为:

LIBRARY DLL_DEF
EXPORTS

cmp3_demo

4.选择这个Project,依次点击CompileBuild按钮,打开Project所在文件夹中的Debug文件夹,能够看到成功生成dll_def.dll,由此完成了动态库的生成。

2、使用关键字_declspec(dllexport)

下面咱们尝试使用第二种方法生成dll文件。

1.在workspace新建一个Project,选择为Win32 Dynamic-Link Library,具体过程再也不赘述。

2.修改这个Project中的代码,完成动态库的编写:

dll_decl.cpp

#include "stdafx.h"
#include "dll_decl.h"

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
                     )
{
    return TRUE;
}

LIB_API int cmp_demo(int a, int b)
{
    if (a < b)
        return a;
    else
        return b;
}

其他已存在的文件中的代码不须要作修改。

3.这个方法最关键的一步!!!——编写dll_decl.h头文件。

导出函数的声明时须要有_declspec(dllexport)关键字。具体形式为:

_declspec(dllexport) <返回类型> <导出函数名> (<函数参数>);

在本Project中,编写的代码以下:

#define LIB_API __declspec(dllexport)

LIB_API int cmp_demo(int a, int b);

4.与上文一样的步骤,选择这个Project,依次点击CompileBuild按钮,打开Project所在文件夹中的Debug文件夹,能够看到成功生成dll_decl.dll,由此使用第二种方法,完成了动态库的生成:

返回目录


2.动态库的使用

  • 动态连接库加载分为静态加载(隐式连接)和动态加载(显式连接):
    • 静态加载方式是在编译程序代码时即完成对DLL的加载,应用程序结束时卸载DLL。
    • 动态加载方式是使用用API函数来加载和卸载DLL的。

1、隐式连接

1.在workspace中新建一个Win32 Console Application的Project,名为dllstaticTest。首先,须要把以前生成的dll文件和对应的lib文件拷到这个文件夹下,以完成后续调用。

2.隐式连接动态库主要有三种方法:

①将lib文件直接加入新建的这个测试project中。

②在测试程序所在project的StdAfx.h文件中增长#pragma comment (lib,"XXX")实现。

③在本Project下的“project”菜单的“setting”下对话框中选择这个工程,并在对应的“Link”选项窗口中的“Object/library modules”下增长须要导入的lib文件:

3.修改这个Project中的代码,完成动态库的测试:

dllstaticTest.cpp

#include "stdafx.h"
#include "../dll_decl/dll_decl.h"

int main(int argc, char* argv[])
{
    int iRet;
    printf("Hello World!\n");
    iRet = cmp_demo(3,5);
    printf("return value is: %x\n",iRet);
    return 0;
}

2、显式连接

上面介绍的动态库使用方法和静态库相似,属于隐式连接,编译的时候指定相应的库和查找路径。其实,动态库还经过调用API,如:LoadLibrary、GetProcAddress、FreeLibrary完成动态调用(即显式连接)。

应用程序必须进行函数调用以在运行时显式加载 DLL。为显式连接到 DLL,应用程序需经过调用函数完成如下步骤:

  1. 首先调用LoadLibrary()函数加载DLL并获得模块句柄;
  2. 使用句柄调用GetProcAddress()函数获取导出函数指针,使用指针调用导出函数;
  3. 调用FreeLibrary()函数释放加载DLL。

代码以下所示:

#include "stdafx.h"
#include <windows.h>
#include "../dll_def/dll_def.h"

int main(int argc, char* argv[])
{
    printf("Hello World!\n");
    //LoadLibrary函数装载dll
    HMODULE hmod = LoadLibrary("dll_def.dll");
    if(!hmod)
    {
        printf("load library failed\n");
        return 0;
    } 
    typedef int (*LoadProc)(int x, int y);
    //GetProcAddress得到"cmp3_demo"函数地址
    LoadProc Load_proc = (LoadProc)GetProcAddress(hmod,"cmp3_demo");
    if(!Load_proc)
    {
        printf("load function cmp3_demo failed\n");
        return 0;
    }
    //经过函数指针间接调用"cmp3_demo"
    int iRet = Load_proc(3,5);
    printf("the cmp3_demo the value is:%x\n",iRet);
    //释放dll
    FreeLibrary(hmod);
    return 0;
}

返回目录


附:参考资料

相关文章
相关标签/搜索