动态链接库——深入探讨C++程序中.so技术细节和实现方式及C程序中动静态库的区别(+工程分步骤编译库)

news2024/11/14 20:20:06

在开发中,动态链接库(DLL)和共享对象(Shared Object).so文件的使用成为提升程序灵活性和重用性的关键手段。如下相关工具,GNU Libtool: 一种用于创建可移植共享库的工具。Dynamic Linker: 关于动态链接器的更多信息。CMake: 一个跨平台的构建系统,简化了共享库的管理和构建。无论是提升程序的模块化程度,还是优化资源使用,动态链接都是一种强大的技术手段。

库概念的由来

比如项目合作时,你不可能直接把源代码给别人,那样别人就可以自己开发,因为源代码就是你的核心技术。理论上不应该卖给别人源代码,而应该是程序,这样你可以根据别人有什么需求,进行改进或添加什么功能模块等,即改一次就可以收费一次,达成一个长期合作。

问题是给到客户的程序,应该是什么呢? 首先当然可以是可执行程序,不过这不是库讨论的范围

对于库,即两种文件:(1)生成的库  (2)头文件
这样把生成的库头文件给客户也能够使用,只是他不知道里面具体怎么实现的。这样二者才能维持一个长期的合作。头文件对应的.c文件都被打包到了静态库动态库里面了

动态库

一种可执行代码模块,允许多个程序共享相同的库文件,从而减少内存使用和磁盘空间占用。在Linux系统中,这类文件通常以.so为后缀名,而在Windows系统中则为.dll。使用动态库可以(1)节省内存和磁盘空间:多个程序可以同时使用同一个动态库实例。(2)简化更新:编译更新动态库后,所有使用该库的程序都自动获得更新后的功能。(3)动态加载:程序可以在运行时选择性地加载,从而实现模块化设计和延迟加载,提升启动性能。

动态库创建

假设要创建一个简单的库,包含一个打印“Hello, World!”的函数。

编写一个动态库的源代码   mylib.cpp

#include <iostream>

/*
    使用 extern "C" 指定函数采用C语言的调用约定
    以避免C++函数名修饰带来的问题
*/

extern "C" {
    void hello() {
        std::cout << "Hello from shared library!" << std::endl;
    }
}

编译动态库

g++ -fPIC -shared -o libmylib.so mylib.cpp

(-fPIC:生成与位置无关的代码(Position-Independent Code)创建动态库的必需选项

-shared:指示编译器生成动态库(共享库)。 -o:指定输出文件名为 libmylib.so。)

动态库调用

编写一个主程序    main.cpp

#include <iostream>
#include <dlfcn.h>

typedef void (*HelloFunc)();


int main() {    
    // 动态加载共享库    
    void* handle = dlopen("./libmylib.so", RTLD_LAZY);    
    if (!handle) {        
        std::cerr << "Cannot open library: " << dlerror() << std::endl;        
        return 1;    
    }

    // 清除之前的错误    
    dlerror();

    // 获取函数指针    
    HelloFunc hello = (HelloFunc) dlsym(handle, "hello");    

    const char* dlsym_error = dlerror();    
    if (dlsym_error) {        
        std::cerr << "Cannot load symbol 'hello': " << dlsym_error << std::endl;                            
        dlclose(handle);        
        return 1;    
    }

    // 调用函数    
    hello();

    // 关闭共享库   
    dlclose(handle);

    return 0;
}

编译

g++ -o main main.cpp -ldl

(-ldl:链接动态加载库(libdl),以使用 dlopendlsymdlclose 函数。)

运行程序确保共享库 libmylib.so 位于程序的可访问路径下,然后运行编译后的可执行文件:

./main

//如果一切正常,输出 Hello from shared library!。

dlopen 函数用于动态加载共享库。第一个参数是共享库的路径,第二个参数指定加载模式。常用的加载模式有:RTLD_LAZY:延迟解析未定义的符号,直到真正需要时再解析。RTLD_NOW:立即解析所有未定义的符号。如果无法解析所有符号,则 dlopen 失败。dlsym 函数用于从共享库中获取符号(函数或变量)的地址。该函数返回一个 void* 指针,需要进行强制类型转换以调用实际的函数。

dlclose 函数用于关闭共享库,释放相关资源。程序退出时系统会自动关闭所有打开的共享库,但手动调用 dlclose 有助于及时释放资源,减少内存占用。

找不到动态库的问题

如果在运行程序时出现错误 Cannot open library: ...,需要确保共享库路径正确,并且库文件具有可执行权限。使用以下命令检查库文件权限:

ls -l libmylib.so

权限不足的话,增加执行权限

chmod +x libmylib.so

确保共享库路径包含在环境变量 LD_LIBRARY_PATH 中

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/your/library

使用经验:(符号解析失败:如果 dlsym 失败,通常是因为函数名错误符号不可见。检查共享库代码,确保函数名正确,并使用 extern "C" 指定C语言调用约定。)

在工程中,分步骤制作动态库 (gcc)

.
├── include
│   └── head.h
├── lib
├── main.c
└── src
    ├── a.c
    ├── b.c
    ├── c.c
    └── d.c

编译和位置无关的目标文件

gcc -fPIC -c *c -I ../include

编译打包动态库,并移动到lib目录下

gcc -shared -o libMylibs.so *o -Iinclude
mv libMylibs.so ../lib

应用程序编译使用动态库的方法

gcc main.c lib/libMylibs.so -o myapp -Iinclude
gcc main.c -L lib -l Mylibs -o myapp -Iinclude

 开发经验:

动态链接器搜索某一个动态库时,在它内部有一个默认的搜索顺序,按照优先级从高到低的顺序分别是:
1.可执行文件内部的 DT_RPATH 段
2.系统的环境变量 LD_LIBRARY_PATH
3.系统动态库的缓存文件 /etc/ld.so.cache
4.存储动态库 / 静态库的系统目录 /lib/, /usr/lib
按照以上四个顺序,依次搜索,找到之后结束遍历,最终还是没找到,动态连接器就会提示动态库找不到的错误信息。

解决方案:

Linux 静态库和动态库 | 爱编程的大丙

总结:

分步骤编译有几个优势:

增量编译:如果有多个源文件,单独编译可以减少重复编译的时间。修改一个文件后,只需重新编译这个文件,而不需要重新编译所有文件。
模块化:分步骤编译可以帮助组织代码,使项目更加模块化和易于管理。
调试:生成对象文件后,可以更容易进行分析和调试。
依赖管理:在大型项目中,使用 Makefile 或其他构建系统可以更好地管理依赖关系。

(对于简单项目,一步到位是可以的;但在复杂项目中,分步骤更灵活和高效)

32位系统的内存分配如下:

关于静态库

在工程中,分步骤制作

.
├── include
│   └── head.h
├── lib
├── main.c
└── src
    ├── a.c
    ├── b.c
    ├── c.c
    └── d.c

首先编译目标文件

gcc *.c -c -I ../include

因为静态库是和位置有关的,打包成静态文件(.a文件),并移动到lib目录下

ar rcs libMylibs.a *.o
mv libMylibs.a ../lib

静态库调用的两种方法

//方式一
gcc main.c -Iinclude -L lib -l Mylibs -o myapp

//方式二
gcc main.c -I ./include lib/libMylibs.a  -o myapp

-I参数: 指定头文所在的文件夹名,文件夹名可以和参数贴着写在一起
-L参数:指定静态库的文件夹名
-l参数: 指定静态库的名字,但名字要掐头去尾,原静态库名字为libMylibs.a,在指定-l参数值的时候为:-l Mylibs
-o参数:输出编译之后可执行文件的名字

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

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

相关文章

VSCode - 终端使用 code 命令

1、Command Shift P 调出 输入 shell 选择 安装命令 2、授权 3、使用 % code --version 1.92.1 eaa41d57266683296de7d214f574d0c2652e1fc4 arm64% code ~/.zshrc 伊织 2024-08-21&#xff08;三&#xff09;

6.InnoDB引擎

InnoDB引擎 1.逻辑存储结构2.架构2.1内存架构2.2 磁盘结构 3.事务原理3.1 事务3.2 redo log3.3undo log 4.MVCC4.1MVCC 基本概率14.2 实现原理 1.逻辑存储结构 2.架构 2.1内存架构 2.2 磁盘结构 create tablespace mytest add datafile mytest.idb engineinnodb;后台线程 mys…

Ubuntu 24.04 上安装和配置 Zabbix Agent

Zabbix 是一个强大的开源监控工具&#xff0c;可以帮助您跟踪服务器&#xff0c;网络和应用程序。在主机环境中配置了 Zabbix Server 之后&#xff0c;下一步是添加用于监视的远程主机。Zabbix Agent 从您的服务器收集数据并将其发送到 Zabbix 服务器进行监控。 本指南将向您展…

CUDA-BEVFusion(1): 环境安装

文章目录 1. 查看ubantu配置2. 环境安装2.1 安装包下载2.1.1 tensorRT 下载2.1.2 CUDA 下载2.1.3 cuDNN 下载2.2 安装2.2.1 cuda 安装2.2.2 cuDNN 安装2.2.3 tensorRT安装3. 安装包下载1. 查看ubantu配置 查看GPU的版本sudo apt-get install pciutilslspci | grep VGA查看linux…

Tabby 终端工具推荐

前言:Tabby 是一个现代化的、跨平台的终端模拟器&#xff0c;旨在提供增强的用户体验和定制功能。Tabby&#xff08;以前称为 Terminus&#xff09;被设计为比传统终端更加灵活和美观 Eugeny/tabby: A terminal for a more modern age (github.com)https://github.com/Eugeny/…

网络安全售前入门02——产品了解

目录 1.前言 2.WEB应用防火墙介绍 2.1产品架构功能 2.2应用场景 2.3部署形式 2.4产品价值 2.5选型依据 3.上网行为审计 3.1产品架构功能 3.2应用场景 3.3部署形式 3.4产品价值 3.5选型依据 后续 1.前言 为方便初接触网络安全售前工作的小伙伴了解网安行业情况,我…

BUUCTF PWN wp--ciscn_2019_n_1

第一步 checksec&#xff0c;并检查该题的保护机制 该题的保护机制如下&#xff1a; Arch (架构): amd64-64-little 这意味着这个二进制文件是为64位AMD64架构编译的&#xff0c;使用小端序&#xff08;little-endian&#xff09;。RELRO (重定位-read-only): Partial RELRO P…

工具技巧:如何使用AutoDL算力云

AutoDL算力云可以快速构建编程环境&#xff0c;价格也很实惠 模型运行已知需要显存少&#xff0c;可以考虑选择4090&#xff0c;有24G&#xff0c;具体选择哪种类型&#xff0c;可以看看重点看看这两方面**&#xff1a;数据盘能否扩容&#xff0c;CUDA版本是否够高** 根据自身…

虚拟化技术VirtualBox

虚拟化技术是当今云计算领域中的重要技术之一&#xff0c;而VirtualBox作为一款开源的虚拟化软件&#xff0c;在Linux系统中发挥着重要作用。本文将从VirtualBox的基本概念入手&#xff0c;介绍其在Linux系统中的应用和高级云计算技术&#xff0c;包括其原理、特点、优势、使用…

http基础原理及应用

三次握手 第一次 发送SYN报文 &#xff0c;传达信息&#xff0c;我想建立连接 第二次 回传SYNACK报文&#xff0c;传达信息 &#xff1a;好的可以建立链接 第三次 回传ACK报文 传到信息&#xff1a;好的&#xff0c;我知道了&#xff0c;哪我能连接&#xff0c;然后就建立连…

深入探讨Linux中的EncFS:安全、灵活的加密文件系统

在当今数字化时代&#xff0c;数据安全是任何组织和个人都不容忽视的重要问题。随着信息技术的发展&#xff0c;对数据的保护要求也日益提高。Linux操作系统作为开源社区的杰出产物&#xff0c;在提供各种安全工具和解决方案方面也颇具优势。其中&#xff0c;EncFS&#xff08;…

[指南]微软发布Windows-Linux双系统无法启动的完整修复方案

早前微软发布的 2024-08 例行安全更新导致 Windows-Linux 双系统无法启动引起关注&#xff0c;该问题实际上在 7 月份微软已经发现并且添加到已知问题列表中&#xff0c;但可能很多用户没注意结果安装更新后 Linux 系统无法正常启动。 问题根源在原本微软发布的补丁不应该针对…

使用vagrant、virtualbox、快速创建kali linux

使用vagrant、virtualbox、快速创建kali linux 初始化kali下载vagrant相应镜像vagrant添加相应镜像创建vagrantfile在vagrantfile根目录执行cmd虚拟机登录密码修改sshd配置 用shell远程链接(可选)可视化界面设置成中文创建成功展示图 添加实体网卡使用kali 破解WiFi密码解决 on…

网络安全教程初级指南

网络安全是当今最抢手的技能之一。如今&#xff0c;信息库如此庞大&#xff0c;节点网络也越来越庞大&#xff0c;网络安全的重要性也越来越高。 本网络安全教程适合初学者和专业人士。 在本教程中&#xff0c;您将学习有关网络安全的所有基本技能、工具和策略。 本网络安全…

【初阶数据结构】顺序表和链表算法题(上)

顺序表和链表算法题 1.顺序表1.1移除元素1.2删除有序数组中的重复项1.3合并两个有序数组 2.链表2.1移除链表元素2.2反转链表2.3链表的中间结点 1.顺序表 1.1移除元素 注意&#xff1a;返回的是元素个数&#xff0c;while循环不要少了等号 //https://leetcode.cn/problems/rem…

如何快速轻松地从 iPhone 恢复已删除的照片

回忆和照片很珍贵&#xff0c;我们不能丢失它们&#xff0c;尤其是误丢它们。我们都可能不小心删除了智能手机上的图像。您是否也碰巧误删除了 iPhone 上的图像&#xff1f;或者也许是出于愤怒&#xff0c;后来才后悔&#xff1f; 不用担心&#xff0c;因为您可以快速轻松地恢…

iOS RunLoop

一:什么是Runloop Runloop&#xff0c;正如其名&#xff0c;loop表示某种循环&#xff0c;和run放在一起就表示一直在运行着的循环 二:Runloop的创建? iOS并没有提供Runloop的创建方法,因为创建了现场自然会有一个Runloop. 所以只有获取Runloop的方法: NSRunLoop * runloo…

【能量项链】

题目 错误代码&#xff08;10过7&#xff09; #include <bits/stdc.h> using namespace std; const int N 110; int a[N]; int n; int f[N][N]; int cal(int i, int k, int j) {if(i < k && k < j){return a[i] * a[(k1)%n] * a[(j1)%n];}else if(k <…

显示“ ‘cmd‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。”的解决方法

问题呈现 当我们在执行cmd命令时&#xff0c;若出现不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件的提示。 解决方法 在导航搜索框输入编辑系统环境变量&#xff08;或者右键此电脑&#xff0c;左键属性&#xff0c;左键高级系统设置&#xff0c;左键环境…

常见——算法

一.排序算法 1.冒泡排序 public class Test {public static void main(String[] args) {int []arr{12,34,1,56,44,4,5,55};for (int i 0; i < arr.length-1; i) {for (int j 0; j < arr.length-i-1; j) {int temparr[j1];if(arr[j]>arr[j1]){arr[j1]arr[j];arr[j]t…