Windows核心编程 静态库与动态库

news2024/11/15 8:29:00

资源文件 .rc 文件 会被 rc.exe 变成 .res 文件(二进制文件) 在链接时链接进入 .exe 文件

一、如何保护源码

程序编译链接过程

不想让别人拿到源代码,但是想让其使用功能,根据上图观察,把自己生成的obj给对方,对方拿到obj后,链接到自己的程序中。

新建一个控制台项目进行测试,目录结构

Math.h

Math.cpp

test.cpp

编译后,会生成一个 Math.obj的文件

再新建一个工程使用Math.obj

首先,包含头文件,其次需要导入 .obj文件

方式一:直接托进解决方案里;

方式二:项目-属性-链接器-输入-附加依赖项-箭头-编辑-添加obj文件(一行一个obj文件)

项目目录结构

Math.h

如何兼容C?

test.c  

链接时报错

原因:
C语言的名称粉碎是:_Sub,_Add;
C++的名称粉碎是: ?Sub@@YAHHH@Z,?Add@@YAHHH@Z
编译器拿着“?Sub@@YAHHH@Z”,在obj中匹配C的_Sub,当然匹配不上

解决办法:告诉编译器,名称粉碎的时候,按照C的名称粉碎规则进行粉碎。

C++ 项目使用时,函数声明加上extern "C"后,C++支持extern "C"语法,能够直接使用
C项目使用时,由于函数声明上extern "C",但是C不支持该语法,不认识,所以编译不通过

解决办法:头文件被C 包含的时候前面不加extern "C"   int Add(int n1, int n2);
                  头文件被C++ 包含的时候,声明前面加上extern "C" ,说明用C风格名称粉碎去找实现extern "C"

条件编译宏:这样使用的时候就可以不管是C包含还是Cpp包含了

//要想C 和C++ 都能所用该obj,声明的前面必须加上extern "C",生成的obj文件名称粉碎是C风格的。C++可以使用,C也可以使用
//C++ 项目使用时,函数声明加上extern "C"后,C++支持extern "C"语法,能够直接使用
//C   项目使用时,由于函数声明上extern "C",但是C不支持该语法,不认识,所以编译不通过。

//解决办法:头文件被C 包含的时候前面不加extern "C"   int Add(int n1, int n2);
//          头文件被C++ 包含的时候,声明前面加上extern "C" ,说明用C风格名称粉碎去找实现extern "C"  int Add(int n1, int n2);

//条件编译宏:这样使用的时候就可以不管是C包含还是Cpp包含了
#ifdef __cplusplus
extern "C"
{
#endif // __cplusplus

int Add(int n1, int n2);
 
#ifdef __cplusplus
}
#endif // __cplusplus

上述的obj的方法中,当有很多obj时候,需要拷贝很多的obj,很不方便,考虑将这些obj合并成一个大的“obj”,这时就引出了静态库的概念。

补充:

#pragma once 是一种预处理指令,用于确保头文件只被编译一次。当一个头文件被多次包含在不同的源文件中时,使用 #pragma once 可以防止重复包含,从而避免编译错误和重复定义的问题。

#pragma once 的作用类似于传统的头文件保护宏(header guard),但更加简洁和方便。传统的头文件保护宏需要在头文件开头和结尾分别使用条件编译语句,如 #ifndef HEADER_NAME_H#define HEADER_NAME_H#endif,以确保头文件只被编译一次。而 #pragma once 只需要在头文件的开头使用一次,即可达到相同的效果。

使用 #pragma once 的好处是可以提高编译速度,因为编译器可以直接根据指令判断是否需要重新编译头文件。而传统的头文件保护宏需要进行条件判断,会增加编译时间和额外的预处理工作。

二、静态库 动态库 概述

函数和数据被编译进一个二进制文件(通常扩展名为.lib)。在使用静态库的情况下,在编译可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.exe文件)。

本质:把所有的obj文件全部打包到一个.lib文件内。

缺点:

  1. 维护困难:如果.lib更新,使用的工程如需更新,则必须重新编译。
  2. 磁盘冗余:如果很多工程使用,就要拷贝很多份.lib文件,这些lib都是一样的
  3. 无法很好的同时兼容C和C++
  4. 其他语言无法使用

动态链接库(DLL) 通常不能直接运行,也不能接收信息,只有在其他模块调用动态链接库中的函数时,才能发挥作用。通常我们把完成某种功能的函数放在一个动态链接库中,提供给其他程序调用。DLL就是整个windows操作系统的基础。动态链接库不能直接运行,也不能接收消息。他们是一些独立的文件。

Windows API中所有的函数都包含在DLL中,其中有3个重要的DLL:

  • Kernel32.dll:包含用于管理内存、进程和线程的函数、例如CreateThread函数。
  • User32.dll:它包含用于执行用户界面任务(如窗口的创建和消息的传送)的函数。例如CreateWindow函数。
  • GDI32.dll:它包含用于画图和显示文本的函数。

使用动态链接库的好处:

  1. 可以采用多种编程语言来编写。
  2. 增强产品的功能(扩展插件)
  3. 提供二次开发的平台(扩展插件)
  4. 简化项目管理(一个团队负责自己团队的dll)
  5. 可以节省磁盘空间和内存
  6. 有助于资源的共享
  7. 有助于实现应用程序的本地化。

三、静态链接库创建与使用

VS2019中直接找到静态链接库,一路确认即可

不适用预编译头即可

项目目录:

pch.h framework.h 文件是作用是减少重复文件编译,提升性能有关。不用管

如果想建立一个自己的静态链接库,直接添加 .h  .cpp文件即可,编译后就可以得到 .lib 文件

使用静态库和使用 .obj 类似

1. 添加头文件,使用者才能知道传的什么参数以及其他
2. 拷贝lib文件和.h头文件到VS工程根目录
3. 添加lib文件到工程的方式(用法):
  a. 直接拖入项目中
  b. 依赖项添加.lib文件
  c. 代码内添加.lib文件 # pragma comment(lib,lib路径)

如何把两个 obj 合成为 lib

静态库中还可以放 全局变量,类(通过源文件右击添加-类)

四、动态链接库创建

新建>>类向导>>项目类型>>.dll动态链接库。

动态链接库中有导出函数和非导出函数:

  • 导出函数:DLL提供给其他应用程序调用的函数
  • 非导出函数:给DLL内的函数调用的函数,中间函数等。

如果想导出函数给外面的工程使用,需要指定函数,告诉编译器哪个函数需要导出

从DLL中导出函数:
为了让DLL导出一些函数,需要在每一个将要被导出的函数前面添加标识符__declspec(dllexport) 

编译:生成DLL文件和LIB文件

LIB文件:称为DLL的导入库文件,是一个特殊的库文件,和静态库文件有着本质上的区别,引入库文件包含该DLL导出的函数和变量的符号名;而DLL文件包含该DLL实际函数和数据。

工程结构:

CTest.h

#pragma once
class __declspec(dllexport) CTest
{
public:
	void Show();
};

Add.h

#pragma once
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

	__declspec(dllexport) int Add(int n1, int n2);
	__declspec(dllexport) extern int g_nVal;
#ifdef __cplusplus
}
#endif // __cplusplus


Sub.h

#pragma once
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

	__declspec(dllexport) int Sub(int n1, int n2);
#ifdef __cplusplus
}
#endif // __cplusplus

Add.cpp

#include "Add.h"
int Add(int n1, int n2)
{
	return n1 + n2;
}
int g_nVal = 0x12345678;

CTest.cpp

#include "CTest.h"
#include <iostream>
using namespace std;
void CTest::Show()
{
	cout << "CTest::Foo()" << endl;
}

dll.cpp

#include <iostream>
#include "CTest.h"
#include "Add.h"
#include "Sub.h"
int main()
{
	std::cout << Add(1, 2) << std::endl;
	std::cout << Sub(2, 1) << std::endl;
	CTest test;
	test.Show();
}

Sub.cpp

#include "Sub.h"
int Sub(int n1, int n2)
{
	return n1 - n2;
}

编译后生成以下文件:

在下面动态链接库的debug目录下:生成了dll文件;dll.exp 文件是一个输出库文件。

LIB文件:称为DLL的导入库文件,是一个特殊的库文件,和静态库文件有着本质上的区别,引入库文件包含该DLL导出的函数和变量的符号名;而DLL文件包含该DLL实际函数和数据。

查看导出函数工具-DEPENDS,拖进去使用即可

五、动态链接库的两种调用方式

动态链接库的使用

  1. 静态调用:在程序编译的时候将DLL的信息植入可执行文件
  2. 动态调用:在程序中用语句显示地加载DLL,编译器不需要知道任何关于DLL的信息。

显式加载和隐式加载是在使用动态链接库(DLL)时的两种加载方式。下面我将为你解释这两种加载方式的区别:

  1. 隐式加载(Implicit Loading):

    • 在编译时,程序会将对 DLL 的引用嵌入到可执行文件中。
    • 在程序运行时,操作系统会自动加载并初始化 DLL。
    • 隐式加载不需要手动加载 DLL 或指定 DLL 的路径。
    • 函数调用时,直接使用函数名进行调用,编译器会根据嵌入的引用找到对应的函数地址。
    • DLL 的导入函数表会在程序加载时自动解析,可以直接访问 DLL 中的函数。
  2. 显式加载(Explicit Loading):

    • 程序需要显式地通过代码来加载 DLL 并获取其函数地址。
    • 使用 LoadLibrary 函数加载 DLL,并返回一个句柄,表示已加载的 DLL。
    • 使用 GetProcAddress 函数根据函数名获取 DLL 中的函数地址。
    • 加载后的 DLL 需要手动卸载,使用 FreeLibrary 函数释放 DLL 句柄。
    • 函数调用时,需要通过函数指针来调用 DLL 中的函数。

显式加载和隐式加载主要的区别在于加载时机和加载方式。隐式加载在程序运行时自动加载 DLL,并且可以直接调用 DLL 中的函数。而显式加载需要手动加载 DLL,并使用函数指针来调用 DLL 中的函数。显式加载提供了更大的灵活性和控制权,适用于需要在运行时动态加载和卸载 DLL 的情况,而隐式加载则更加简单和方便。

六、动态链接库的静态加载

静态调用步骤:

  1. 新建应用工程。
  2. 通过编译器供给应用程序关于DLL的名称,以及DLL函数的链接参考(.h文件)。这种方式不需要在程序中用代码将DLL加载到内存。
  3. 将DLL和LIB文件拷贝到工程目录下
  4. 将lib文件添加到工程
    1. 方式一:项目>>属性>>链接>>依赖项>>lib名称
    2. 方式二:拖入到项目
  5. 添加头文件>>直接调用头文件中的函数即可。
     

新建一个控制台项目,目录结构如下

Add.h

#pragma once
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

	__declspec(dllimport) int Add(int n1, int n2);
	__declspec(dllimport) extern int g_nVal;
#ifdef __cplusplus
}
#endif // __cplusplus


Sub.h

#pragma once
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

	__declspec(dllimport) int Sub(int n1, int n2);
#ifdef __cplusplus
}
#endif // __cplusplus

CTest.h 

#pragma once
class __declspec(dllimport) CTest
{
public:
	void Show();
};

.cpp

#include <iostream>
#include "CTest.h"
#include "Add.h"
#include "Sub.h"
#pragma comment(lib,"dll.lib")

int main()
{
	std::cout << Add(1, 2) << std::endl;
	std::cout << Sub(3, 4) << std::endl;
	std::cout << std::hex << g_nVal << std::endl;
	CTest test;
	test.Show();
}

动态链接库与可执行文件放在同一目录下:

lib文件放到根目录下

方式一:使用 extern 声明外部函数

extern 关键字在C和C++中都有着重要的作用,它的具体含义取决于它所修饰的变量或函数。

在C语言中,extern 关键字用于声明一个变量或函数是在别处定义的,告诉编译器该变量或函数的定义在其他地方,不在当前文件中。具体来说:

  1. 外部变量声明:在C语言中,当你在一个文件中使用了一个全局变量,而该变量的定义在另外一个文件中时,你可以使用 extern 来声明该变量,以便编译器知道该变量的定义在其他地方。

    // 在一个文件中声明外部变量
    extern int global_var; // 声明global_var是在其他文件中定义的全局变量
    
  2. 外部函数声明extern 也可以用于声明外部函数,在这种情况下,它告诉编译器该函数的定义在其他地方,不在当前文件中。

    // 外部函数声明
    extern void external_function(); // 声明external_function是在其他文件中定义的函数
    

 

方式二:__declspec(dllimport) 声明外部函数

除了使用extern 关键字表明函数是外部定义的之外,还可以使用标识符:__declspec(dllimport) 来表明函数是从动态链接库中引入的。
__declspec(dllimport) 与使用extern 关键字这种方式相比,再使用__declspec(dllimport) 标识符声明外部函数时,它将告诉编译器该函数是从动态链接库中引入的,编译器可以生成运行效率更高的代码。所以调用的函数来自于动态链接库,则应该使用这种方式来声明外部函数。

标准来说,无论是全局变量,还是函数都是需要使用关键字dllimport

使用宏优化导关键字dllimport

代码如下:

#pragma once
#ifdef DLL_EXPORT
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif 

解释一下这段代码的含义:

  1. #ifdef DLL_EXPORT:这个条件编译指令用于检查是否定义了DLL_EXPORT宏。如果定义了,表示当前是在编译DLL库的源代码,需要导出函数和数据。如果没有定义,则表示当前是在使用DLL的客户端代码,需要导入函数和数据。

  2. #define DLL_API __declspec(dllexport):如果DLL_EXPORT被定义了,那么将DLL_API宏定义为__declspec(dllexport)__declspec(dllexport)是在Windows平台上用于标记要导出的函数和数据的修饰符。

  3. #else:如果DLL_EXPORT未被定义,执行下面的代码块。

  4. #define DLL_API __declspec(dllimport):将DLL_API宏定义为__declspec(dllimport)__declspec(dllimport)是在Windows平台上用于标记要导入的函数和数据的修饰符。

通过这种方式,可以在编写DLL库时使用DLL_API宏来修饰要导出的函数和数据,而在使用DLL库的客户端代码中使用DLL_API宏来修饰要导入的函数和数据。这样可以保证在编译时正确地处理导出和导入函数的修饰符。

动态链接库创建优化

预处理器包含宏:DLL_EXPORT

DLL_API 替换 __declspec(dllexport),并包含 common.h 头文件

加载动态链接库优化

不需要包含这个宏即可, .h 文件 包含 common.h 文件 替换宏即可

读取全局变量来说,最好直接无脑加 extern 关键字

补充:在实现动态链接库时,可以不导出整个类,而只导出该类中的某些函数,在导出类的成员函数的时候需要注意,该函数必须具有public类型的访问权限。

兼容 C

如果使用C++语言编写了一个DLL,那么使用C语言编写的客户端程序访问DLL中的函数就会出现问题,因为后者将使用函数原始名称来调用DLL中的函数,而C++编译器已经对该名称进行了改编,所以C语言编写的客户端程序就找不到所需的DLL导出函数。

#pragma once
#ifdef DLL_EXPORT
#define DLL_API extern "C" __declspec(dllexport)
#else
#define DLL_API extern "C" __declspec(dllimport)
#endif 

利用限定符 extern “C” 可以解决C++和C语言之间的相互调用是函数命名问题。但是这种方法有一个缺陷,就是不能用于导出一个类的成员函数和全局变量,只能用于导出全局函数这种情况。
如果导出函数的调用约定发生了变化,那么即使使用了限定符 extern “C” ,该函数的名字仍然会发生改编。
在这种情况下,可以通过一个称为模块定义文件(DEF) 的方式来解决名字改编问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1228369.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nodejs+vue实验室上机管理系统的设计与实现-微信小程序-安卓-python-PHP-计算机毕业设计

用户&#xff1a;管理员、教师、学生 基础功能&#xff1a;管理课表、管理机房情况、预约机房预约&#xff1b;权限不同&#xff0c;预约类型不同&#xff0c;教师可选课堂预约和个人&#xff1b;课堂预约。 在实验室上机前&#xff0c;实验室管理员需要对教务处发来的上机课表…

Spring 配置

配置文件最主要的目的 : 解决硬编码的问题(代码写死) SpringBoot 的配置文件,有三种格式 1.properties 2.yaml 3.yml(是 yaml 的简写) SpringBoot 只支持三个文件 1.application.properties 2.application.yaml 3.application.yml yaml 和 yml 是一样的,学会一个就行…

SpringCloud微服务注册中心:Nacos介绍,微服务注册,Ribbon通信,Ribbon负载均衡,Nacos配置管理详细介绍

微服务注册中心 注册中心可以说是微服务架构中的”通讯录“&#xff0c;它记录了服务和服务地址的映射关系。在分布式架构中&#xff0c;服务会注册到这里&#xff0c;当服务需要调用其它服务时&#xff0c;就这里找到服务的地址&#xff0c;进行调用。 微服务注册中心 服务注…

PyTorch神经网络-激励函数

在PyTorch 神经网络当中&#xff0c;使用激励函数处理非线性的问题&#xff0c;普通的神经网络出来的数据一般是线性的关系&#xff0c;但是遇到比较复杂的数据的话&#xff0c;需要激励函数处理一些比较难以处理的问题&#xff0c;非线性结果就是其中的情况之一。 FAQ:为什么要…

LV.12 D18 中断处理 学习笔记

一、ARM的异常处理机制及工程代码结构 1.1异常概念 处理器在正常执行程序的过程中可能会遇到一些不正常的事件发生 这时处理器就要将当前的程序暂停下来转而去处理这个异常的事件 异常事件处理完成之后再返回到被异常打断的点继续执行程序。 1.2异常处理机制 不同的处…

【算法】滑动窗口题单——2.不定长滑动窗口(求最长/最大)

文章目录 3. 无重复字符的最长子串1493. 删掉一个元素以后全为 1 的最长子数组904. 水果成篮1695. 删除子数组的最大得分2841. 几乎唯一子数组的最大和2024. 考试的最大困扰度1004. 最大连续1的个数 III1438. 绝对差不超过限制的最长连续子数组2401. 最长优雅子数组解法1——维…

nodejs微信小程序-实验室上机管理系统的设计与实现-安卓-python-PHP-计算机毕业设计

用户&#xff1a;管理员、教师、学生 基础功能&#xff1a;管理课表、管理机房情况、预约机房预约&#xff1b;权限不同&#xff0c;预约类型不同&#xff0c;教师可选课堂预约和个人&#xff1b;课堂预约。 目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 …

容斥 C. Strange Function改编题

补题&#xff1a; 题目详情 - 9.段坤爱取模%%% - SUSTOJ 本题或许是参考 Problem - C - Codeforces 根据题意&#xff0c;f(i)就是不能被整除的最小的一个质因子。 打表发现&#xff0c;当15个质因子相乘后&#xff0c;长度就大于18。 因此可以知道小于等于1e16内的正整数x…

(动手学习深度学习)第13章 计算机视觉---微调

文章目录 微调总结 微调代码实现 微调 总结 微调通过使用在大数据上的恶道的预训练好的模型来初始化模型权重来完成提升精度。预训练模型质量很重要微调通常速度更快、精确度更高 微调代码实现 导入相关库 %matplotlib inline import os import torch import torchvision f…

java文件压缩加密,使用流的方式

使用net.lingala.zip4j来进行文件加密压缩。 添加依赖net.lingala.zip4j包依赖&#xff0c;这里使用的是最新的包2.11.5版本。 <dependency><groupId>net.lingala.zip4j</groupId><artifactId>zip4j</artifactId><version>${zip4j.versi…

丹麦能源袭击预示着更关键的基础设施成为目标

5 月&#xff0c;22 个丹麦能源部门组织在与俄罗斯 Sandworm APT 部分相关的攻击中受到损害。 丹麦关键基础设施安全非营利组织 SektorCERT 的一份新报告描述了不同的攻击者群体利用合勤防火墙设备中的多个关键漏洞&#xff08;包括两个零日漏洞&#xff09;侵入工业机械&…

Dockerfile自定义镜像以及案例分析

文章目录 一、Dockerfile自定义镜像1.1 镜像结构1.2 Dockerfile语法 二、构建Java项目三、基于java8构建java四、小结 一、Dockerfile自定义镜像 常见的镜像在DockerHub就能找到&#xff0c;但是我们自己写的项目就必须自己构建镜像了。 而要自定义镜像&#xff0c;就必须先了…

boomYouth

上一周实在是过得太颓废了&#xff0c;我感觉还是要把自己的规划做好一下&#xff1a; 周计划 这周截至周四&#xff0c;我可以用vue简单的画完登陆注册的界面并且弄一点预处理&#xff1a; 周一 的话可以把这些都学一下&#xff1a; 父传子&#xff0c;子传父&#xff1a…

配置iTerm2打开自动执行命令

打开iTerm2&#xff0c;commado&#xff0c;打开profies->edit profies&#xff0c;点击号&#xff0c;创建一个新的profile 在新的profile中填写 name&#xff1a;随意 command&#xff1a;Login Shell Send text at start&#xff1a;执行脚本的命令&#xff0c;不想写路…

python django 小程序图书借阅源码

开发工具&#xff1a; PyCharm&#xff0c;mysql5.7&#xff0c;微信开发者工具 技术说明&#xff1a; python django html 小程序 功能介绍&#xff1a; 用户端&#xff1a; 登录注册&#xff08;含授权登录&#xff09; 首页显示搜索图书&#xff0c;轮播图&#xff0…

某60区块链安全之不安全的随机数实战一

区块链安全 文章目录 区块链安全不安全的随机数实战一实验目的实验环境实验工具实验原理实验内容攻击过程分析合约源代码漏洞EXP利用 不安全的随机数实战一 实验目的 学会使用python3的web3模块 学会以太坊不安全的随机数漏洞分析及利用 实验环境 Ubuntu18.04操作机 实验工…

环境配置|GitHub——解决Github无法显示图片以及README无法显示图片

一、问题背景 最近在整理之前写过的实验、项目&#xff0c;打算把这些东西写成blog&#xff0c;并把工程文件整理上传到Github上。但在上传README文件的时候&#xff0c;发现github无法显示README中的图片&#xff0c;如下图所示&#xff1a; 在README中该图片路径为&#xff1…

Unity Meta Quest 一体机开发(七):配置玩家 Hand Grab 功能

文章目录 &#x1f4d5;教程说明&#x1f4d5;玩家物体配置 Hand Grab Interactor⭐添加 Hand Grab Interactor 物体⭐激活 Hand Grab Visual 和 Hand Grab Glow⭐更新 Best Hover Interactor Group &#x1f4d5;配置可抓取物体&#xff08;无抓取手势&#xff09;⭐刚体和碰撞…

【算法】树形DP③ 监控二叉树 ⭐(二叉树染色二叉树灯饰)!

文章目录 前期知识 & 相关链接例题968. 监控二叉树解法1——标记状态贪心解法2——动态规划 相关练习题目P2458 [SDOI2006] 保安站岗⭐&#xff08;有多个儿子节点&#xff09;&#x1f6b9;LCP 34. 二叉树染色⭐&#xff08;每个节点 单独dp[k 1]数组&#xff09;LCP 64.…

时间序列预测实战(十七)利用Prophet实现电力负荷长期预测(附代码+数据集+详细讲解)

一、本文介绍 Prophet是一个由Facebook开发的开源工具&#xff0c;用于时间序列预测。这个工具特别适合于具有强季节性影响和多个历史数据季节的业务时间序列数据。Prophet的主要思想是将数据分解为如下三个部分&#xff1a;趋势、季节性、节假日和特殊事件。这个模型非常适合…