windows动态链接库的使用,隐式调用(静态链接)和显示调用(动态链接)

2023-05-16

一,动态链接库项目创建

dll工程内部架构

用VS2019创建动态链接库dll工程。
初始会有如下几个文件:

pch.h和pch.cpp与动态链接库功能无关。是用来保存多个文件时共同引用的头文件,函数,类等等,使得项目编译时这些代码只需要在pch.h中编译一次即可,节约时间。

dllmain.cpp里包含动态链接库的入口函数,作用是进程加载或卸载动态链接库时会调用此函数,可以编写switch中代码进行信息提示等。

BOOL APIENTRY DllMain( HMODULE hModule,  //自己的句柄
                       DWORD  ul_reason_for_call,  //调用的场景
                       LPVOID lpReserved  //隐式调用或显示调用
                     )
{
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH: //进程加载动态链接库时调用
        case DLL_THREAD_ATTACH:  //线程加载动态链接库时调用
        case DLL_THREAD_DETACH:  //线程卸载动态链接库时调用
        case DLL_PROCESS_DETACH: //进程卸载动态链接库时调用
        break;
    }
    return TRUE;
}

如:你的进程加载动态库时,case DLL_PROCESS_ATTACH:
后的代码会被执行。

上述这些文件可以被删除,不影响动态库的使用。但若想用隐式调用,工程里必须至少保留一个头文件用于放要被引用的函数声明(因为隐式调用依赖.h文件)
除这些初始文件之外,程序员可以自己往dll工程中添加任意数量头文件或cpp文件,在其中写入想被引用的变量,函数,类等等。但所有的cpp文件开始都要引用pch.h(若其未被删除),且这些文件不能有同名的变量函数等。

上述所有文件在工程被生成时都会被整体编译出一个lib静态库文件和dll动态库文件。

引用部分

被引用函数格式:
extern “C” _declspec(dllexport) void fuc(){}
变量和类可以通过函数用返回值提供,例:

class A {

};
extern "C" _declspec(dllexport) A GetA() {
    A a;
    return a;
}

extern “C” :文件是C++的时候需要添加。若写在.h里,如果你的头文件不能被C文件引用,只能被C++引用。
功能是:表明函数使用c语言的风格编译函数,DLL跟其使用者都采用这种约定,那么就可以解决函数重命名规则不一致导致的错误,后面显式调用时,GetProcAddress第二个参数就是原来的函数名。否则如果使用C++编译的话支持函数重载,则第二个参数会发生变化而产生错误。

_declspec(dllexport) :声明导出函数、类、对象等供外面调用,表示这是要被外界引用的。省略给出.def文件。即将函数、类等声明为导出函数,供其它程序调用,作为动态库的对外接口函数、类等。同时调用函数也可以选择性加上_declspec(dllimport)表示这是从dll里调用的函数。

补充:.def文件(模块定义文件):是包含一个或多个描述各种DLL属性的Module语句的文本文件。.def文件或__declspec(dllexport)都是将公共符号导入到应用程序或从DLL导出函数。如果不提供__declspec(dllexport)导出DLL函数,则DLL需要提供.def文件。

二,动态链接库的调用

要生成动态链接库文件的代码

h

extern "C" _declspec(dllexport) void fuc();

cpp

#include<iostream>
using namespace std;

extern "C" _declspec(dllexport) void fuc() {
    cout << 3 << endl;
}

隐式调用

隐式链接调用就是在程序开始执行时就将dll文件加载到程序当中且整个执行过程无法分离。调用十分简单,但需要h,lib和dll文件的支持。
程序员在建立一个DLL文件时,链接程序会自动生成一个与之对应的LIB导入文件,该文件包含了每一个DLL导出函数的符号名和可选的标识号,但是并不含有实际的代码。LIB文件作为DLL的替代文件被编译到应用程序项目中。当程序员通过静态链接方式编译生成应用程序时,应用程序中的调用函数与LIB文件中导出符号相匹配,这些符号或标识号进入到生成的EXE文件中。 LIB文件中也包含了对应的DLL文件名(但不是完全的路径名),链接程序将其存储在EXE文件内部。当应用程序运行过程中需要加载DLL文件时,Windows根据这些信息发现并加载DLL,然后通过符号名或标识号实现对DLL函数的动态链接。
调用方简单代码:(需要.h,.lib,.dll文件都放在要执行文件目录下,放在别处需直接寻址)
例:

#include"testdll.h"
#pragma comment(lib, "TESTDLL.lib")

int main()
{
	fuc(); //直接调用dll里的函数
}

显式调用

显式链接调用是执行过程中随时可以加载或卸载dll文件。调用较麻烦但可以脱离h和lib,只需要dll文件。显式链接具有更好的灵活性,在工程较大时可以节省内存。对于解释性语言更为合适,也节省了编译时间。显示调用可以利用指针实现多态。

调用方简单代码:(需要.dll文件放在要执行文件目录下,放在别处需直接寻址)
例:

#include<Windows.h>
#include <iostream>
using namespace std;

void (*fuc)();

int main()
{
	HMODULE hdll = LoadLibrary(L"test.dll"); //加载动态链接库,会在此处执行dllmain里case DLL_PROCESS_ATTACH:的代码
	if (hdll == NULL) {
		printf("加载dll失败\n");
		return 0;
	}
	fuc = (void(*)())GetProcAddress(hdll, "fuc"); //获得函数地址,放进函数指针中
	if (fuc == NULL) {
		cout << "获取函数失败"<<endl;
		return 0;
	}
	fuc();
	FreeLibrary(hdll);  ///卸载动态链接库,会在此处执行dllmain里case DLL_PROCESS_DETACH:的代码
}

注: 在实际工程项目中,若想在dll增加时,完全不需要改动代码就实现读取新的dll,就需要让代码直接读取dll所在的目录而非单个dll:

void GetDLL() {
	char* filePath = "存放dll文件夹目录";//自己设置目录  exe文件有时执行不了是因为exe文件路径和cpp文件路径不同,所以需要绝对路径
	long   hFile = 0;
	//文件信息,声明一个存储文件信息的结构体  
	struct _finddata_t fileinfo;
	string p;//字符串,存放路径
	if ((hFile = _findfirst(p.assign(filePath).append("\\*.dll").c_str(), &fileinfo)) != -1)//若查找成功,则进入
	{
		do
		{
			files.push_back(p.assign(filePath).append("\\").append(fileinfo.name));
		} while (_findnext(hFile, &fileinfo) == 0);
		//_findclose函数结束查找
		_findclose(hFile);
	}
	kindnumber = files.size();
	typedef ShapePackage* (*FUNGSP)();
	HINSTANCE hShape;
	FUNGSP GetPackage;
	for (int i = 0; i < files.size(); i++)
	{
		hShape = LoadLibrary(files[i].c_str());
		GetPackage = (FUNGSP)GetProcAddress(hShape, "函数名");
		shapePackage[i] = GetPackage();		//循环得到并保存每个dll内的函数
	}
}

三,补充知识:

动态链接库和静态链接库

静态链接库:
程序在编译时,就会把静态库的内容和执行程序一起打包成一个exe文件,两者是一体的,exe文件可以直接执行。多个执行程序都有各自的静态库,一个静态库不能被多个程序共用。(整体)
静态链接库(.a,.lib)可以看作是obj文件,只不过多的代码隐蔽性。

动态链接库:
程序在被编译的时候,只有少量的有关dll的符号信息会被打包到执行程序中,只有执行时才会去寻找动态链接库并调用。dll文件是独立的,一个dll可以被多个exe程序调用,exe执行需要dll文件的存在。(分离)

静态连接库就是把(lib)文件中用到的函数代码直接链接进目标程序,程序运行的时候不再需要其它的库文件;动态链接就是把调用的函数所在文件模块(DLL)和调用函数在文件中的位置等信息链接进目标程序,程序运行的时候再从DLL中寻找相应函数代码,因此需要相应DLL文件的支持。

.h头文件,.lib库文件,.dll动态链接库文件的关系

.h头文件是编译时必须的,lib是链接时需要的,dll是运行时需要的。

附加依赖项的是.lib不是.dll,若生成了DLL,则肯定也生成 LIB文件。如果要完成源代码的编译和链接,有头文件和lib就够了。如果也使动态连接的程序运行起来,有dll就够了。在开发和调试阶段,当然最好都有。

.h .lib .dll三者的关系是:

H文件作用是:声明函数接口

DLL文件作用是: 函数可执行代码

当我们在自己的程序中引用了一个H文件里的函数,编链器怎么知道该调用哪个DLL文件呢?这就是LIB文件的作用: 告诉链接器 调用的函数在哪个DLL中,函数执行代码在DLL中的什么位置,这也就是为什么需要附加依赖项, .LIB文件,它起到桥梁的作用。(因此动态链接库的隐式调用必须加载lib库来当向导,而显示调用没有向导则需要自己用GetProcAddress获取dll里函数地址(原本是lib库文件的任务),都是为了能找到dll里的对应函数)。
如果生成静态库文件,则没有DLL ,只有lib,这时函数可执行代码部分也在lib文件中。(静态链接库,不需要dll保存可执行代码,代码直接在lib里,编译时和主代码一起成为exe文件)

目前以lib后缀的库有两种,一种为包含函数代码本身,称为静态链接库(Static Libary,以下简称“静态库”),另一种是包含了函数所在DLL文件和文件中函数位置的信息,为动态连接库(DLL,以下简称“动态库”)的导入库(Import Libary,以下简称“导入库”)。般现有的DLL,用的是前一种库;以前在DOS下的TC/BC等,是后一种库。包含函数原型声明的,是头文件(.h)。
静态库是一个或者多个obj文件的打包,所以有人干脆把从obj文件生成lib的过程称为Archive,即合并到一起。比如你链接一个静态库,如果其中有错,它会准确的找到是哪个obj有错,即静态lib只是壳子。动态库一般会有对应的导入库,方便程序静态载入动态链接库,否则你可能就需要自己LoadLibary调入DLL文件,然后再手工GetProcAddress获得对应函数了。有了导入库,你只需要链接导入库后按照头文件函数接口的声明调用函数就可以了。导入库和静态库的区别很大,他们实质是不一样的东西。静态库本身就包含了实际执行代码、符号表等等,而对于导入库而言,其实际的执行代码位于动态库中,导入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。

一般的动态库程序有lib文件和dll文件。lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的。如果有dll文件,那么对应的lib文件一般是一些索引信息,具体的实现在dll文件中。如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。静态编译的lib文件有好处:给用户安装时就不需要再挂动态库了。但也有缺点,就是导致应用程序比较大,而且失去了动态库的灵活性,在版本升级时,同时要发布新的应用程序才行。在动态库的情况下,有两个文件,而一个是引入库(.LIB)文件,一个是DLL文件,引入库文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。从上面的说明可以看出,DLL和.LIB文件必须随应用程序一起发行,否则应用程序将会产生错误。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

windows动态链接库的使用,隐式调用(静态链接)和显示调用(动态链接) 的相关文章

随机推荐

  • 解决android opengl glReadPixels 慢的问题 二

    解决android opengl glReadPixels 慢的问题 二 上篇讲到使用pbo解决glreadpix慢的问题 xff0c 但是效果不太理想 xff0c 后来参考链接 xff1a OpenGL Pixel Buffer Obje
  • Tesseract 3.02中文字库训练

    下载chi sim traindata字库 下载tesseract ocr setup 3 02 02 exe 下载地址 xff1a http code google com p tesseract ocr downloads list 下
  • Windows下使用pip安装包 出错 TLS/SSL

    Windows下使用pip安装包的时候出现如下问题 xff1a WARNING pip is configured with locations that require TLS SSL however the ssl module in
  • C++进阶(七)-模板与群体数据8

    选择排序 选择排序的基本思想 每次从待排序序列中选择一个关键字最小的元素 xff0c xff08 当需要按关键字升序排列时 xff09 xff0c 顺序排在已排序序列的最后 xff0c 直至全部排完 例9 12 简单选择排序函数模板 tem
  • Ubuntu 20.04 WARNING笔记(长期更新,欢迎交流)

    记录使用Ubuntu 20 04过程的报错 以及解决 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
  • 循环嵌套例题

    循环嵌套例题 1 例题1 span class token comment 代码 span span class token keyword for span span class token punctuation span span c
  • java实现学生信息管理(对象数组实现)

    java实现学生信息管理 xff08 对象数组实现 xff09 1 例题 实体类 学生类 id 姓名 xff0c 年龄 xff0c 性别 xff0c 成绩 需要使用数组保存学生信息 Student allStu 需要完成的方法 1 根据学生
  • java基础语法思维导图

    java从入门到放弃 简单总结之前的
  • 学生管理系统2.0 (可对学生数组扩容)

    学生管理系统2 0 可对学生数组扩容 1 用户可初始化数组长度 xff0c 不够用时可以扩充数组容量 尝试完成以下功能 实体类 学生类 id 姓名 xff0c 年龄 xff0c 性别 xff0c 成绩 需要使用数组保存学生信息 Studen
  • LinkedList和Set

    LinkedList和Set 1 LinkedList 1 1 LinkedList概述 底层存储数据是一个双向链表结构 自行车链子 就是一个生活中链表结构 xff0c 环环相扣 xff0c 替换 xff0c 拆除非常方便 1 2 Link
  • shiro与springboot整合

    Shiro 与 SpringBoot 的整合 1 创建SpringBoot工程 xff0c 导入依赖 span class token generics function span class token punctuation lt sp
  • Vue

    Author Thor Version 9 0 1 文章目录 一 Vue简介1 1 简介1 2 MVVM 模式的实现者 双向数据绑定模式1 3 其它 MVVM 实现者1 4 为什么要使用 Vue js1 5 Vue js 的两大核心要素1
  • android AudioRecord 音频录制 噪音消除

    android AudioRecord 音频录制 噪音消除 因为公司APP做适配 xff0c 一些低端机的噪音比较严重 xff0c 所以再一些低端机上做了简单除噪音功能 xff0c 1 xff0c 由于APP使用场景的限制 xff0c 所以
  • springboot 常用注解

    springboot 常用注解 在spring boot中 xff0c 摒弃了spring以往项目中大量繁琐的配置 xff0c 通过自身默认配置 xff0c 极大的降低了项目搭建的复杂度 在spring boot中 xff0c 大量注解的使
  • X86架构基本汇编指令详解

    文章目录 汇编指令伪指令1 MODEL2 STACK3 ENDP4 END 汇编指令1 MOV xff1a 将源操作数复制到目的操作数2 MOVZX 和 MOVSX3 XCHG 交换两个操作数内容4 INC 和 DEC5 ADD 和 SUB
  • 详解 C++ 对象模型

    文章目录 何为 C 43 43 对象模型 xff1f 基本 C 43 43 对象模型C 43 43 对象模型中加入单继承1 无重写的单继承2 有重写的单继承 C 43 43 对象模型中加入多继承C 43 43 对象模型中加入虚继承1 简单虚
  • Ubuntu 扩大/home磁盘分区

    在删除或重命名home目录之前 xff0c 千万确保你可以使用root账户 xff01 xff01 xff01 sudo 无用 xff01 xff01 xff01 根目录一共212G xff0c 已经使用了80 了 xff0c 其中130G
  • 正则表达式学习的个人小结

    正则表达式是对字符串的一种操作 xff0c 运用到JS中可以帮助我们去寻找符合要求的字符串 表单验证 http xff1a regexper com xff08 这个网站可以把正则表达式输入进去然后用图形显示出来 xff0c 因为是国外的网
  • ROS学习 一、Debian10安装ROS Noetic,解决rosdep update失败问题(更新一个可修改位置)

    目录 前言ROS安装1 添加ROS的apt源和key xff08 中科大源 xff09 2 apt安装ros noetic核心组件3 配置ROS的bash环境4 安装其他常用ROS依赖项5 解决python3 rosdep安装中出现的ros
  • windows动态链接库的使用,隐式调用(静态链接)和显示调用(动态链接)

    一 xff0c 动态链接库项目创建 dll工程内部架构 用VS2019创建动态链接库dll工程 初始会有如下几个文件 xff1a pch h和pch cpp与动态链接库功能无关 是用来保存多个文件时共同引用的头文件 xff0c 函数 xff