Windows环境下的静态库和动态库的使用详解

命运对每个人都是一样的,不一样的是各自的努力和付出不同,付出的越多,努力的越多,得到的回报也越多,在你累的时候请看一下身边比你成功却还比你更努力的人,这样,你就会更有动力。

导读:本篇文章讲解 Windows环境下的静态库和动态库的使用详解,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

简介

在Windows系统下常见的库有两种一种是以.lib为扩展名的库,也叫lib库。lib库实际上分为两种:一种是静态链接lib库或者叫做静态lib库,另一种是动态链接库dll库的lib导入库或称为lib导入库。以dll为扩展名的库叫dll库,dll库只有一种就是用于动态链接的动态链接库(也叫dll库,dll是:dynamic link library的英文单词首字母缩写)。在发展史上,动态链接库的出现晚于静态链接库的出现。

lib库的详细说明

第一种是静态lib库

静态lib库,包含了所有的代码实现的,是源代码文件.c或.cpp文件编译生成的,这个lib库就是文本形式的源代码编译后的二进制形式代码。

第二种是lib导入库

lib导入库,这个库里只是相应的dll文件中的所有函数在dll文件中的地址的说明。也就是这个lib导入库里没有函数的具体实现,只是对相应的dll文件的说明。
也就是说目前以.lib为后缀的库有两种,一种为静态链接库(static library,简称“静态库”),另一种为动态链接库(DLL,简称“动态库”)的导入库(import library)。

两种库的说明

从两种库的说明可以看出,静态lib库文件里是包含了所有的代码的,所以只要导入后,使用链接器链接生成exe文件后,那么exe文件就可以直接使用exe 内部的代码了。这个静态链接lib库的过程就相当于把静态lib库里的所有二进制的代码复制到exe文件中。所以,链接完后,静态lib库文件就不需要了。最后, 我们只要exe就行了。这个lib在链接完后,就已经失去价值了。只有下次再编译生成exe文件时,才再此需要。发版时,只提供给别人exe就行,不需要提供静态lib库。缺点是导致exe文件太大。每次修改静态lib库文件里的代码,都需要重新编译生成新的exe文件。优点是:依赖性小。小的应用程序,适用使用静态lib库,大的应用程序不适合使用静态lib库。
lib导入库可以说明相应的dll库的内部结构,简直就对dll内部了如指掌,我们通过lib导入库,再把相应的dll文件放到合适的位置(这个再后面再说具体放到哪里),程序运行时,就可以轻松调用到dll里面的函数。

两种lib库的相同点和不同点

相同点:两种lib都是用于编译的链接阶段,都把自身的内容复制拷贝到exe文件内,在正确生成exe后,两种lib库的使命也就完成了。也就是说后续运行exe程 序,就用不到了(即程序的运行阶段)。这里要说明一下,虽然lib导入库中不存在,函数的具体实现,但是其里面有函数具体在哪个dll文件中的说明。那么在源代码生成的目标文件,在链接阶段,通过在lib导入库文件中,查到相应的外部函数的实现在对应的dll文件中的地址说明,也就保证链接阶段的正确通过,而其相应的具体的dll文件并不参与整个编译的阶段,而是在程序运行阶段才实际需要到,真正的实现了说明和实现的分离。
不同点:静态库是一个或者多个.obj目标文件的打包,静态库只是一个“壳子”,静态库报错的话,会定位到某个.obj目标文件。静态库和导入库的区别很大,他们本质是不一样的东西。静态库本身就包含了实际执行代码、符号表等;而对于导入库而言,其实际的执行代码位于动态库中,导入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。

在visual studio下静态lib库的导出和使用

导出过程演示

1.首先创建一个空项目或者创建一个静态库项目(两者其实没啥区别,区别在于 项目属性页–>配置属性–>常规–>配置类型的默认选项:空项目的为应用程序(.exe),而静态库项目的为:静态库(.lib)),这个如果新建的是空项目的话,可以手动切换成静态库(.lib)。
在这里插入图片描述在这里插入图片描述
2.这里选择新建一个空项目,点击进入后,配置新项目。
在这里插入图片描述
4. 创建头文件 add_sub.h文件。
在这里插入图片描述
头文件内容:
在这里插入图片描述
源文件add_sub.cpp,里面的函数是对add_sub.h中的函数原型的具体实现。【注意:我们这里没有涉及到类,所以函数都是全局的,这里,在源文件中不引入头文件也是可以的,只根据add_sub.cpp也可以生成静态库,但是最好引入头文件,如下我们这里就加上了头文件】
在这里插入图片描述
下面是编译前的属性页设置:鼠标放到项目名上(不是解决方案名称),点击鼠标右键,在弹出框中,选择属性(R),弹出下面界面:
在这里插入图片描述
静态库的设置主要看上面这个界面:输出目录:指定的编译后的静态库文件的生成存放的位置。

在这里插入图片描述
在这里插入图片描述
目标文件名:这里默认是以当前工程名做为生成的静态库的文件名。
在这里插入图片描述
其实上面的都按照默认就行了,就是配置类型确认为**静态库(.lib)**就行了。
还有一处C/C++下的预编译头菜单的设置:设置为不使用预编译头,网上有人说,设置预编译头,可以提高编译效率,具体也不是太清楚。
在这里插入图片描述

启动编译静态库: 点击 生成 下的 生成XXX或者 通过Ctrl+B生成。不要像含main函数的 项目那样,直接点击 本地Window调试器。否则会在编译完成后,尝试运行lib文件,提示报错。
在这里插入图片描述
生成后的文件:
在这里插入图片描述
至此一个简单的静态库的生成就演示完毕了。

使用过程演示

一般情况下,我们在项目中使用静态链接库时,都需要头文件和静态lib库文件的搭配。头文件中主要放置宏定义,函数原型等,保证 链接之前的代码的正常编译,以及使用函数原型充当目标文件中的函数符号。
也是在visual studio下演示
新建一个空项目:
在这里插入图片描述

在创建的当前项目下,新建两个目录 include和lib。(在其他地方新建也可以)。
新建
在这里插入图片描述
在这里插入图片描述

使用方式一

在这里插入图片描述
关键点:通过#include命令通过相对路径的方式引入头文件,通过#pragma comment()命令 通过相对路径的方式添加lib
库。也可以写绝对路径,但是绝对路径的话,不灵活。采用方式一,属性页界面不用设置。

使用方式二

在这里插入图片描述
关键点:这种方式变化的就是:#pragma comment(lib,“./lib/Static_lib_proj”)里的lib库文件的扩展名不写。这里会根据前一项的类型lib,编译器会把Static_lib_proj当成文件名,然后拼接扩展名.lib后,查找Static_lib_proj.lib文件。注意没有#pragma comment(dll,“xxxxx”);这种方式,注意没有动态库的类型,#pragma comment命令和动态库的引入没有直接关系。动态库的引入我们在动态库的使用环节再介绍。

使用方式三

在这里插入图片描述
在这里插入图片描述
关键点: 在当前工程属性页下的【配置属性】下的【C/C++】下的【常规】下的 【附加包含目录】添加 头文件所在目
录。这种方式下#include里就可以直接写头文件名。因为IDE会智能的按附加包含目录里配的路径下去搜索
头文件。这种方式设置头文件的目录时,源代码里引入头文件时,也可以用#include <add_sub.h>。即可用< >
代替” “。即能用< >的时候,也一定能双引号” “。反过来 能双引号” “的时候,不一定能用< >。关于这个#include
<add_sub.h>和#include “add_sub.h”引入方式的区别,单独写篇文章再讲;

使用方式四

在这里插入图片描述

在这里插入图片描述
关键点: 方式三和方式四的源代码是一样的,不同的是工程属性页的设置:这里是通过【配置属性】下的【VC++目录】下的【包含目
录】里添加了头文件的路径。方式四的包含目录和方式三的附加包含目录的区别,我们会单独讲解。这也是IDE的历史遗留问
题,就目前来说,设置的效果都只会影响到当前项目了。

使用方式五

在这里插入图片描述
在这里插入图片描述
关键点:这里源代码中没有写lib库的具体路径,只写的文件名,它会去到【配置属性】下的【VC++目录】下的【库目
录】下的路径里去搜索文件名为Static_lib_proj.lib的库文件。

使用方式六

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用方式七

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

关键点:属性页设置里 方式七比方式六多设置了个 【链接器】下的【输入】下的【附加依赖项】,在附加依赖项里,我们填写上要依赖
的lib库的名称。这样代码里就不用添加#pragma comment(lib,“Static_lib_proj.lib”) 这句代码了。编译链接时,会自动通过附加
库目录里的路径+附件依赖项里的文件名链接到。

其他组合方式

这里只是列举里常见的几种方式,还有其他组合方式,这里就不在讲解了,相信大家也都有所领悟了。

在visual studio下动态链接dll库的导出和使用

导出过程演示

第一种方式导出

1.创建一个动态库项目
在这里插入图片描述
在这里插入图片描述
2.创建头文件 mathDll.h

//__declspec(dllexport)修饰符指示编译器和链接器从DLL导出函数或变量,以便其他应用程序可以使用它
//这里使用的宏定义来代替 这两个修饰符__declspec(dllexport) 和 __declspec(dllimport)
#pragma once
#ifdef EXPORTDLL_EXPORTS //此宏的定义在,源文件mathDll.cpp中
#pragma message("====dllexport=====")
#define EXPORTDLL_API __declspec(dllexport)//生成DLL库时需要
#else
#pragma message("====dllimport====")
#define EXPORTDLL_API __declspec(dllimport)//在应用程序中使用DLL库时需要
#endif

#ifdef __cplusplus //C++环境时,执行extern "C" { }
extern "C" {
#endif
	EXPORTDLL_API int add(int a, int b);			//加法
	EXPORTDLL_API int sub(int a, int b);			//减法
	EXPORTDLL_API int multiply(int a, int b);		//乘法
	EXPORTDLL_API float divide(float a, float b);	//除法

#ifdef __cplusplus
}
#endif

3.创建源文件mathDll.cpp

//#include "pch.h"
//自定义一个宏,此宏在mathDll.h中被使用,所以这里要在#include "mathDll.h"前定义在有效
#define EXPORTDLL_EXPORTS
#include "mathDll.h"
EXPORTDLL_API int add(int a, int b)
{
	return a + b;
}

EXPORTDLL_API int sub(int a, int b)
{
	return a - b;
}

EXPORTDLL_API int multiply(int a, int b)
{
	return a * b;
}

EXPORTDLL_API float divide(float a, float b)
{
	if (b != 0)
		return a / b;
	else
		return (float)0;
}

4.dllmain.cpp(创建项目时自动生成的)

//此dllmain里的内容 是创建动态库dll时自动添加的,暂切不管其内容。
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h" //visual studio 2017 或更早的版本是 "stdafx.h"

BOOL APIENTRY DllMain( HMODULE hModule,             //模块句柄
                       DWORD  ul_reason_for_call,   //调用原因
                       LPVOID lpReserved  //参数保留,好像没什么用
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:    //DLL被某个程序加载
    case DLL_THREAD_ATTACH:     //DLL被某个线程加载
    case DLL_THREAD_DETACH:     //DLL被某个线程卸载
    case DLL_PROCESS_DETACH:    //DLL被某个程序卸载
        break;
    }
    return TRUE;
}

5.pch.h (创建项目时自动生成的)

// pch.h: 这是预编译标头文件。
// 下方列出的文件仅编译一次,提高了将来生成的生成性能。
// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。
// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。

#ifndef PCH_H
#define PCH_H

// 添加要在此处预编译的标头
#include "framework.h"

#endif //PCH_H

6.framework.h(创建项目时自动生成的)

#pragma once

#define WIN32_LEAN_AND_MEAN             // 从 Windows 头文件中排除极少使用的内容
// Windows 头文件
#include <windows.h>

6.pch.cpp(创建项目时自动生成的)

// pch.cpp: 与预编译标头对应的源文件

#include "pch.h"

// 当使用预编译的头时,需要使用此源文件,编译才能成功。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最终生成结果:(我们需要的就是dll和lib这两个文件,其他文件暂时没有接触到,不便讲解)
在这里插入图片描述

第二种方式导出

创建模块定义文件.def以列出导出的DLL函数。(这种方式暂时没有遇到过,后面遇到了再补上)

使用过程演示

1.新建一空项目
在这里插入图片描述
调用DLL动态库有两种方式。一种是隐式链接 ,一种是显示链接

隐式链接

需要准备的是头文件mathDll.h,导入库Dll_export_proj.lib,动态库Dll_export_proj.dll。
mathDll.h和Dll_export_proj.lib在程序编译链接时需要。Dll_export_proj.dll库是在程序运行时才需要的。
其中头文件和导入库的引入和使用静态库时的方式是一样的。
下面说下动态库的放置位置(有两种):1. C:\Windows\SysWOW64(放win32或x86平台编译的动态库) 和C:\Windows\System32(放置x64平台编译的动态库)。当我们的可执行文件是64位的系统就自动去C:\Windows\System32下搜索想要的dll文件。当我们的可执行文件是32位的就去C:\Windows\SysWOW64下搜索想要的dll文件。2.还可以放到与生成的可执行文件位于同一目录下。3.也可以放到 电脑的系统环境的PATH路径下(指定好dll库文件所在目录)。4.还可以【属性页】–【配置属性】–【调试】–【环境】里配置指定PATH=D:\vs_project\use_dll_sln\use_dll_proj\dll(所依赖的dll所在目录)。方法4:使用于在【调试】–【开始调试】或【开始执行(不调试)】在环境下执行时,自动运行程序时,会自动去设置的调试环境配置路径里找dll库文件。但是在生成的exe可执行文件里直接点击exe,还是提示找不到dll。所以方法4的调试环境配置路径,使通过IDE才能找到。

#include "iostream"
using namespace std;
#include "./include/mathDll.h"
#pragma comment(lib,"./lib/Dll_export_proj.lib")

int main(int argc, char* argv[])
{
	int a = 10, b = 5;
	cout << add(a, b) << endl;
	cout << sub(a, b) << endl;
	cout << multiply(a, b) << endl;
	cout << divide(a, b) << endl;
	system("pause");
	return 0;
}

这里在点击 生成解决方案后或生成(项目名)后,会生成exe文件。我们把dll库放到和exe相同的目录下(选择第二种)。
在这里插入图片描述
双击exe文件,可以正常执行main函数中调用的动态库里的函数。
在这里插入图片描述
没有把dll库放到指定的位置,时双击exe文件的效果:
在这里插入图片描述

显式链接

是由编程者用API函数加载和卸载DLL来达到调用DLL的目的,使用上较复杂,但能更加有效地使用内存,是编制大型应用程序时的重要方式。 显式的调用:是指在应用程序中用LoadLibrary显式的将自己所做的动态连接库调进来,动态连接库的文件名即是上面两个函数的参数,再用GetProcAddress()获取想要引入的函数。自此,你就可以象使用如同本应用程序自定义的函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary释放动态连接库。直接调用Win32 的LoadLibary函数,并指定DLL的路径作为参数。LoadLibary返回HINSTANCE参数,应用程序在调用GetProcAddress函数时使用这一参数。GetProcAddress函数将符号名或标识号转换为DLL内部的地址。程序员可以决定DLL文件何时加载或不加载,显式链接在运行时决定加载哪个DLL文件。使用DLL的程序在使用之前必须加载(LoadLibrary)加载DLL从而得到一个DLL模块的句柄,然后调用GetProcAddress函数得到输出函数的指针,在退出之前必须卸载DLL(FreeLibrary)。这种方式只需要DLL文件就行,不需要头文件和lib导入库。

#include "iostream"
using namespace std;
//显示链接时 必需要pch.h
#include "pch.h"	//visual studio 2017 或更早的版本是 "stdafx.h"
int main(int argc, char* argv[])
{
	HMODULE hModule;   //模块句柄,可以把这个句柄定义为全局的,这样在其他文件中也可以使用

	//定义函数指针
	typedef int(*ADD)(int a, int b);
	typedef int(*SUB)(int a, int b);
	typedef int(*MULTIPLY)(int a, int b);
	typedef float(*DIVIDE)(float a, float b);

	//通过LoadLibrary()函数加载DLL,是运行时的动态加载,指定路径下实际有没有DLL文件,不影响程序的编译。
	hModule = ::LoadLibrary(L"Dll_export_proj.dll");//调用DLL,先找当前文件夹(即exe的同级目录),如果没有,就会去System32\SysWOW64下查找 
	//hModule = ::LoadLibrary(L"D:\\dll\\Dll_export_proj.dll");//也可以指定带目录的路径,当程序运行代码执行到这里时,会去这里找DLL。
	if (hModule == NULL) {
		MessageBox(NULL, L"DLL加载失败", L"Mark", MB_OK);//当执行exe文件时,DLL文件加载失败时的弹框
		return 0;
	}

	//获取相应DLL函数的入口地址(即函数指针)
	ADD add = (ADD)::GetProcAddress(hModule, "add");
	SUB sub = (SUB)::GetProcAddress(hModule, "sub");
	MULTIPLY multiply = (MULTIPLY)::GetProcAddress(hModule, "multiply");
	DIVIDE divide = (DIVIDE)::GetProcAddress(hModule, "divide");

	//----------------------调用函数----------------------
	//加
	if (add != NULL) {
		cout << "a+b=" << add(10, 5) << endl;
	}

	//减
	if (sub != NULL) {
		cout << "a-b=" << sub(10, 5) << endl;
	}

	//乘
	if (multiply != NULL) {
		cout << "a+b=" << multiply(10, 5) << endl;
	}

	//除
	if (divide != NULL) {
		cout << "a+b=" << divide(10, 5) << endl;
	}

	FreeLibrary(hModule);   //释放句柄,即释放已加载的动态库

	system("pause");
	return 0;
}

点击生成解决方案后,执行exe文件的效果如下:
在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/142621.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!