【Linux】Linux动态库和静态库

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:Linux
🎯长路漫漫浩浩,万事皆有期待

上一篇博客:【Linux】Linux进程控制

文章目录

  • 动静态库的基本原理
  • 认识动静态库
    • 打包
    • 使用
  • 动态库的打包与使用
    • 打包
    • 使用
  • 总结:

动静态库的基本原理

动静态库的本质是可执行程序的“半成品”。

我们都知道,一堆源文件和头文件最终变成一个可执行程序需要经历以下四个步骤:

预处理: 完成头文件展开、去注释、宏替换、条件编译等,最终形成xxx.i文件。
编译: 完成词法分析、语法分析、语义分析、符号汇总等,检查无误后将代码翻译成汇编指令,最终形成xxx.s文件。
汇编: 将汇编指令转换成二进制指令,最终形成xxx.o文件。
链接: 将生成的各个xxx.o文件进行链接,最终形成可执行程序。

例如,用test1.c、test2.c、test3.c、test4.c以及main1.c形成可执行文件,我们需要先得到各个文件的目标文件test1.o、test2.o、test3.o、test4.o以及main1.o,然后再将这写目标文件链接起来,最终形成一个可执行程序。
在这里插入图片描述

如果我们在另一个项目当中也需要用到test1.c、test2.c、test3.c、test4.c和项目的main2.c或者main3.c分别形成可执行程序,那么可执行程序生成的步骤也是一样的。
在这里插入图片描述

而实际上,对于可能频繁用到的源文件,比如这里的test1.c、test2.c、test3.c、test4.c,我们可以将它们的目标文件test1.o、test2.o、test3.o、test4.o进行打包,之后需要用到这四个目标文件时就可以之间链接这个包当中的目标文件了,而这个包实际上就可以称之为一个库。
在这里插入图片描述

实际上,所有库本质都是一堆目标文件(xxx.o)的集合,库的文件当中并不包含主函数而只是包含了大量的方法以供调用,所以说动静态库本质是可执行程序的“半成品”。

认识动静态库

在Linux下创建文件编写以下代码,并生成可执行程序。

#include <stdio.h>

int main()
{
	printf("hello world\n"); //库函数
	return 0;
}

这是最简单的代码,运行结果大家也都知道,就是hello world。
在这里插入图片描述

下面我们就通过这份简单的代码来认识一下动静态库

在这份代码当中我们可以通过调用printf输出hello world,主要原因是gcc编译器在生成可执行程序时,将C标准库也链接进来了。

在Linux下,我们可以通过ldd 文件名来查看一个可执行程序所依赖的库文件。
在这里插入图片描述

这其中的libc.so.6就是该可执行程序所依赖的库文件,我们通过ls命令可以发现libc.so.6实际上只是一个软链接。
在这里插入图片描述

实际上该软链接的源文件libc-2.17.so和libc.so.6在同一个目录下,为了进一步了解,我们可以通过file 文件名命令来查看libc-2.17.so的文件类型。
在这里插入图片描述

此时我们可以看到,libc-2.17.so实际上就是一个共享的目标文件库,准确来说,这还是一个动态库。

在Linux当中,以.so为后缀的是动态库,以.a为后缀的是静态库。
在Windows当中,以.dll为后缀的是动态库,以.lib为后缀的是静态库。
这里可执行程序所依赖的libc.so.6实际上就是C动态库,当我们去掉一个动静态库的前缀lib,再去掉后缀.so或者.a及其后面的版本号,剩下的就是这个库的名字。

而gcc/g++编译器默认都是动态链接的,若想进行静态链接,可以携带一个-static选项。

gcc -o mytest-s mytest.c -static

在这里插入图片描述

此时生成的可执行程序就是静态链接的了,可以明显发现静态链接生成的可执行程序的文件大小,比动态链接生成的可执行程序的文件大小要大得多。

静态链接生成的可执行程序并不依赖其他库文件,此时当我们使用ldd 文件名命令查看该可执行程序所依赖的库文件时就会看到以下信息。
在这里插入图片描述
此外,当我们分别查看动静态链接生成的可执行程序
的文件类型时,也可以看到它们分别是动态链接和静态链接的。
在这里插入图片描述

动静态库各自的特征

静态库

静态库是程序在编译链接的时候把库的代码复制到可执行文件当中的,生成的可执行程序在运行的时候将不再需要静态库,因此使用静态库生成的可执行程序的大小一般比较大。

优点:

使用静态库生成可执行程序后,该可执行程序就可以独自运行,不再需要库了。

缺点:

使用静态库生成可执行程序会占用大量空间,特别是当有多个静态程序同时加载而这些静态程序使用的都是相同的库,这时在内存当中就会存在大量的重复代码。

动态库

动态库是程序在运行的时候才去链接相应的动态库代码的,多个程序共享使用库的代码。一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。

在可执行文件开始运行前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接。动态库在多个程序间共享,节省了磁盘空间,操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
在这里插入图片描述

优点:

节省磁盘空间,且多个用到相同动态库的程序同时运行时,库文件会通过进程地址空间进行共享,内存当中不会存在重复代码。
缺点:

必须依赖动态库,否则无法运行。
静态库的打包与使用
为了更容易理解,下面演示动静态库的打包与使用时,都以下面的四个文件为例,其中两个源文件add.c和sub.c,两个头文件add.h和sub.h。

add.h当中的内容如下:

#pragma once

extern int my_add(int x, int y);

add.c当中的内容如下:

#include "add.h"

int my_add(int x, int y)
{
	return x + y;
}

sub.h当中的内容如下:

#pragma once

extern int my_sub(int x, int y);

sub.c当中的内容如下:

#include "sub.h"

int my_sub(int x, int y)
{
	return x - y;
}

代码内容都非常简单,我就不做过多的阐述了。

打包

下面我们就利用这四个文件打包生成一个静态库:
在这里插入图片描述

第一步:让所有源文件生成对应的目标文件

在这里插入图片描述

第二步:使用ar命令将所有目标文件打包为静态库

ar命令是gnu的归档工具,常用于将目标文件打包为静态库,下面我们使用ar命令的-r选项和-c选项进行打包。

-r(replace):若静态库文件当中的目标文件有更新,则用新的目标文件替换旧的目标文件。
-c(create):建立静态库文件。

ar -rc libcal.a add.o sub.o

在这里插入图片描述

此外,我们可以用ar命令的-t选项和-v选项查看静态库当中的文件。

-t:列出静态库中的文件。
-v(verbose):显示详细的信息。

ar -tv libcal.a

在这里插入图片描述

第三步:将头文件和生成的静态库组织起来

当我们把自己的库给别人用的时候,实际上需要给别人两个文件夹,一个文件夹下面放的是一堆头文件的集合,另一个文件夹下面放的是所有的库文件。

因此,在这里我们可以将add.h和sub.h这两个头文件放到一个名为include的目录下,将生成的静态库文件libcal.a放到一个名为lib的目录下,然后将这两个目录都放到mathlib下,此时就可以将mathlib给别人使用了。
在这里插入图片描述

使用Makefile

当然,我们可以将上述所要执行的命令全部写到Makefile当中,后续当我们要生成静态库以及组织头文件和库文件时就可以一步到位了,不至于每次重新生成的时候都要敲这么多命令,这也体现了Makefile的强大。
在这里插入图片描述

编写Makefile后,只需一个make就能生成所有源文件对应的目标文件进而生成静态库。
在这里插入图片描述

一个make output就能将头文件和静态库组织起来。
在这里插入图片描述

使用

创建源文件main.c,编写下面这段简单的程序来尝试使用我们打包好的静态库。

#include <stdio.h>
#include <add.h>

int main()
{
	int x = 20;
	int y = 10;
	int z = my_add(x, y);
	printf("%d + %d = %d\n", x, y, z);
	return 0;
}

现在该目录下就只有main.c和我们刚才打包好的静态库。

方法一:使用选项

此时使用gcc编译main.c生成可执行程序时需要携带三个选项:

-I:指定头文件搜索路径。
-L:指定库文件搜索路径。
-l:指明需要链接库文件路径下的哪一个库。

 gcc main.c -I./mathlib/include -L./mathlib/lib -lcal

在这里插入图片描述

此时就可以成功使用我们自己打包的库文件并生成可执行程序。
在这里插入图片描述

说明一下:

因为编译器不知道你所包含的头文件add.h在哪里,所以需要指定头文件的搜索路径。
因为头文件add.h当中只有my_add函数的声明,并没有该函数的定义,所以还需要指定所要链接库文件的搜索路径。
实际中,在库文件的lib目录下可能会有大量的库文件,因此我们需要指明需要链接库文件路径下的哪一个库。库文件名去掉前缀lib,再去掉后缀.so或者.a及其后面的版本号,剩下的就是这个库的名字。
-I,-L,-l这三个选项后面可以加空格,也可以不加空格。

方法二:把头文件和库文件拷贝到系统路径下

既然编译器找不到我们的头文件和库文件,那么我们直接将头文件和库文件拷贝到系统路径下不就行了。

 sudo cp mathlib/include/* /usr/include/
 sudo cp mathlib/lib/libcal.a /lib64/

在这里插入图片描述

需要注意的是,虽然已经将头文件和库文件拷贝到系统路径下,但当我们使用gcc编译main.c生成可执行程序时,还是需要指明需要链接库文件路径下的哪一个库。
在这里插入图片描述

此时才能成功使用我们自己打包的库文件并生成可执行程序。

在这里插入图片描述

为什么之前使用gcc编译的时候没有指明过库名字?

因为我们使用gcc编译的是C语言,而gcc就是用来编译C程序的,所以gcc编译的时候默认就找的是C库,但此时我们要链接的是哪一个库编译器是不知道的,因此我们还是需要使用-l选项,指明需要链接库文件路径下的哪一个库。

扩展:
实际上我们拷贝头文件和库文件到系统路径下的过程,就是安装库的过程。但并不推荐将自己写的头文件和库文件拷贝到系统路径下,这样做会对系统文件造成污染。

动态库的打包与使用

打包

动态库的打包相对于静态库来说有一点点差别,但大致相同,我们还是利用这四个文件进行打包演示:
在这里插入图片描述

第一步:让所有源文件生成对应的目标文件

此时用源文件生成目标文件时需要携带-fPIC选项:

-fPIC(position independent code):产生位置无关码。

在这里插入图片描述

说明一下:

-fPIC作用于编译阶段,告诉编译器产生与位置无关的代码,此时产生的代码中没有绝对地址,全部都使用相对地址,从而代码可以被加载器加载到内存的任意位置都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。

如果不加-fPIC选项,则加载.so文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的拷贝,并且每个拷贝都不一样,取决于这个.so文件代码段和数据段内存映射的位置。

不加-fPIC编译出来的.so是要在加载时根据加载到的位置再次重定位的,因为它里面的代码BBS位置无关代码。如果该.so文件被多个应用程序共同使用,那么它们必须每个程序维护一份.so的代码副本(因为.so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)。

我们总是用-fPIC来生成.so,但从来不用-fPIC来生成.a。但是.so一样可以不用-fPIC选项进行编译,只是这样的.so必须要在加载到用户程序的地址空间时重定向所有表目。

第二步:使用-shared选项将所有目标文件打包为动态库

与生成静态库不同的是,生成动态库时我们不必使用ar命令,我们只需使用gcc的-shared选项即可。

gcc -shared -o libcal.so add.o sub.o

在这里插入图片描述

第三步:将头文件和生成的动态库组织起来

与生成静态库时一样,为了方便别人使用,在这里我们可以将add.h和sub.h这两个头文件放到一个名为include的目录下,将生成的动态库文件libcal.so放到一个名为lib的目录下,然后将这两个目录都放到mlib下,此时就可以将mlib给别人使用了。
在这里插入图片描述

使用Makefile

当然,生成动态库也可以将上述所要执行的命令全部写到Makefile当中,后续当我们要生成动态库以及组织头文件和库文件时就可以一步到位了。
在这里插入图片描述
编写Makefile后,只需一个make就能生成所有源文件对应的目标文件进而生成动态库。

在这里插入图片描述

一个make output就能将头文件和动态库组织起来。
在这里插入图片描述

使用

我们还是用刚才使用过的main.c来演示动态库的使用。

#include <stdio.h>
#include <add.h>

int main()
{
	int x = 20;
	int y = 10;
	int z = my_add(x, y);
	printf("%d + %d = %d\n", x, y, z);
	return 0;
}

现在该目录下就只有main.c和我们刚才打包好的动态库。
在这里插入图片描述

说明一下,使用该动态库的方法与刚才我们使用静态库的方法一样,我们既可以使用 -I,-L,-l这三个选项来生成可执行程序,也可以先将头文件和库文件拷贝到系统目录下,然后仅使用-l选项指明需要链接的库名字来生成可执行程序,下面我们仅以第一种方法为例进行演示。

此时使用gcc编译main.c生成可执行程序时,需要用-I选项指定头文件搜索路径,用-L选项指定库文件搜索路径,最后用-l选项指明需要链接库文件路径下的哪一个库。

 gcc main.c -I./mlib/include -L./mlib/lib -lcal

在这里插入图片描述

与静态库的使用不同的是,此时我们生成的可执行程序并不能直接运行。
在这里插入图片描述

需要注意的是,我们使用-I,-L,-l这三个选项都是在编译期间告诉编译器我们使用的头文件和库文件在哪里以及是谁,但是当生成的可执行程序生成后就与编译器没有关系了,此后该可执行程序运行起来后,操作系统找不到该可执行程序所依赖的动态库,我们可以使用ldd命令进行查看。
在这里插入图片描述

可以看到,此时可执行程序所依赖的动态库是没有被找到的。

解决该问题的方法有以下三个:

方法一:拷贝.so文件到系统共享库路径下

既然系统找不到我们的库文件,那么我们直接将库文件拷贝到系统共享的库路径下,这样一来系统就能够找到对应的库文件了。

sudo cp mlib/lib/libcal.so /lib64

在这里插入图片描述

可执行程序也就能够顺利运行了。

在这里插入图片描述

方法二:更改LD_LIBRARY_PATH

LD_LIBRARY_PATH是程序运行动态查找库时所要搜索的路径,我们只需将动态库所在的目录路径添加到LD_LIBRARY_PATH环境变量当中即可。

 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/cl/BasicIO/testlib/project/mlib/lib

此时我们再用ldd命令查看该可执行程序就会发现,系统现在就可以找到该可执行程序所依赖的动态库了。
在这里插入图片描述

现在我们也就能正常运行该可执行程序了。
在这里插入图片描述

方法三:配置/etc/ld.so.conf.d/

我们可以通过配置/etc/ld.so.conf.d/的方式解决该问题,/etc/ld.so.conf.d/路径下存放的全部都是以.conf为后缀的配置文件,而这些配置文件当中存放的都是路径,系统会自动在/etc/ld.so.conf.d/路径下找所有配置文件里面的路径,之后就会在每个路径下查找你所需要的库。我们若是将自己库文件的路径也放到该路径下,那么当可执行程序运行时,系统就能够找到我们的库文件了。

echo /home/sherry/11.9/project1/mlib/lib > sherry.conf

首先将库文件所在目录的路径存入一个以.conf为后缀的文件当中。
在这里插入图片描述

然后将该.conf文件拷贝到/etc/ld.so.conf.d/目录下。
在这里插入图片描述

但此时我们用ldd命令查看可执行程序时,发现系统还是没有找到该可执行程序所依赖的动态库。
在这里插入图片描述

这时我们需要使用ldconfig命令将配置文件更新一下,更新之后系统就可以找到该可执行程序所依赖的动态库了。

 sudo ldconfig

在这里插入图片描述

而此时我们也就可以正常运行该可执行程序了。
在这里插入图片描述

总结:

今天我们学习了Linux动态库和静态库的相关知识,了解了动静态库的基本原理,认识动静态库,动态库的打包与使用等 。接下来,我们将继续学习Linux的其他知识。希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AIOT数字孪生智慧工地一体化管理平台源码

智慧工地app基于物联网和移动互联网技术&#xff0c;利用各类传感器及终端设备通过与云端服务器的实时数据交互&#xff0c;为施工现场的管理人员提供环境监测、劳务实名制管理、物料管理、巡检记录、设备管理等一系列优质高效的行业解决方案。 一、智能工地应用价值 智慧工地…

Java+Spring Cloud +UniApp +MySql智慧工地综合管理云平台源码

智慧工地围绕工程现场人、机、料、法、环及施工过程中质量、安全、进度、成本等各项数据满足工地多角色、多视角的有效监管,实现工程建设管理的降本增效. 智慧工地综合管理云平台源码&#xff0c;PC监管端、项目端&#xff1b;APP监管端、项目端、数据可视化大屏端源码&#xf…

springboot rocketmq 延时消息、延迟消息

rocketmq也有延迟消息&#xff0c;经典的应用场景&#xff1a;订单30分钟未支付&#xff0c;则取消的场景 其他博客提到从rocketmq5.0开始&#xff0c;支持自定义延迟时间&#xff0c;4.x只支持预定义延迟时间&#xff0c;安装rocketmq可参考RocketMq简介及安装、docker安装ro…

iOS OpenGL ES3.0入门实践

一、效果图 入门实践&#xff0c;做的东西比较简单&#xff0c;效果如下&#xff1a; 二、关于顶点坐标和纹理坐标 绘制图片需要设置顶点坐标和纹理坐标并加载像素数据&#xff0c;之所以要指定两组坐标是因为纹理和顶点使用不同的坐标系&#xff0c;就是告诉OpenGL&#xf…

9 个可以免费检索意外删除或丢失的文件的专业数据恢复软件

今天&#xff0c;我们将探索一些最佳数据恢复软件&#xff0c;它们可以帮助您从 Windows PC 或存储设备中检索意外删除或丢失的文件&#xff01; 丢失数据或意外删除数据是一种令人不安的经历。值得庆幸的是&#xff0c;存在有效的解决方案来解决这种情况。今天&#xff0c;我…

CSS 滚动捕获 scroll-snap-type

scroll-snap-type 语法实例 捕获轴 y 捕获严格程度 mandatory捕获轴 y 捕获严格程度 proximity同理看下捕获轴 x 一些注意事项兼容性 scroll-snap-type 用来指定一个滚动容器(scroll container)是否是滚动捕获容器(scroll snap container)、捕获的严格程度以及在什么方向上执行…

2.8 CE修改器:寻找共享代码

本关我们将学习共享代码&#xff0c;在C语言中角色属性都是以结构体的方式进行存储的&#xff0c;而结构体所存储的信息都是连续性的&#xff0c;这一关我们将会解释如何处理游戏中的共用代码&#xff0c;这种代码是通用在除了自己以外的其他同类型对像上的常常你在修改游戏的时…

在Vue.js中,什么是Vuex?它的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

Spring Cloud OpenFeign:基于Ribbon和Hystrix的声明式服务调用

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Spring Cloud OpenFeign&#xff1a;基于Ribbon和Hystrix的声明式服务调用 Spring Cloud OpenFeign是一个声明式的服务调用框架&#xff0c;基于Feign并整合了Ribbon和…

Crypto | Affine password 第二届“奇安信”杯网络安全技能竞赛

题目描述&#xff1a; 明文经过仿射函数y3x9加密之后变为JYYHWVPIDCOZ&#xff0c;请对其进行解密&#xff0c;flag的格式为flag{明文的大写形式}。 密文&#xff1a; JYYHWVPIDCOZ解题思路&#xff1a; 1、使用在线网站直接破解或手工计算破解&#xff0c;获得flag。&#xf…

中国专利转让数据集(1985-2021年)

专利转让数据追踪和记录专利从一个实体转移到另一个实体的过程。这些数据不仅包括参与转让的申请人和受让人的身份信息&#xff0c;如名字和地址&#xff0c;还涵盖了转让的具体法律细节&#xff0c;包括转让执行日、转让次数、法律状态变更&#xff0c;以及转让登记的相关信息…

软件开发项目文档系列之十六如何撰写系统运维方案

前言 项目运维方案是为了确保项目的稳定运行和可持续发展而制定的指导性文档。本文将详细介绍项目运维方案的各个方面&#xff0c;包括硬件和软件基础设施、监控和警报、备份和恢复、安全性、团队组织和沟通等方面。本博客将提供示例和最佳实践&#xff0c;以帮助您更好地理解…

前端前沿技术

文章目录 网站静态化PWA - Progressive Web APP, 渐进式 Web 应用PWA 简介解决了哪些问题?PWA 的优势浏览器支持情况参考文档 Weex 是一个可以使用现代化的 Web 技术开发高性能原生应用的框架。高性能跨平台贴近前端生态被大规模的使用 GraphQL[一种用于 API 的查询语言](http…

通信原理板块——线性分组码之循环码

微信公众号上线&#xff0c;搜索公众号小灰灰的FPGA,关注可获取相关源码&#xff0c;定期更新有关FPGA的项目以及开源项目源码&#xff0c;包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 1、循环码原理 循环码(cycle code)…

Linux操作系统使用及C高级编程-D3Linux shell命令(权限、输入输出)

Shell 是一种应用程序&#xff0c;用以完成用户与内核之间的交互 一个功能强大的编程语言&#xff08;C语言&#xff09; 一个解释执行的脚本语言&#xff0c;不需要编译&#xff0c;写完直接执行 目前Linux 乌班图的Shell默认是bash 查看当前提供的Shell&#xff1a;cat /…

Milvus Cloud——LangChain 分块简介

LangChain 分块简介 LangChain 是一个 LLM 协调框架,内置了一些用于分块以及加载文档的工具。本次分块教程主要围绕设置分块参数,并最小限度地使用 LLM。简而言之,通过编写一个函数并设置其参数来加载文档并对文档进行分块,该函数打印结果为分块后的文本块。在下述实验中,…

Linux服务器从零开始训练 RT-DETR 改进项目 (Ultralytics) 教程,改进RTDETR算法(包括使用训练、验证、推理教程)

手把手从零开始训练 RT-DETR 改进项目 (Ultralytics版本) 教程,改进RTDETR算法 本文以Linux服务器为例:从零开始使用Linux训练 RT-DETR 算法项目 《芒果剑指 RT-DETR 目标检测算法 改进》 适用于芒果专栏改进RT-DETR算法 文章目录 百度 RT-DETR 算法介绍改进网络代码汇总第…

轻量封装WebGPU渲染系统示例<26>- PingpongBlur模糊效果(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/PingpongBlur.ts 当前示例运行效果: WGSL片段shader group(0) binding(0) var<uniform> param: vec4f; group(0) binding(1) var sampler0: sampler; group(…

(免费领源码)Springboot宠物医院管理系统的设计与实现84724-计算机毕业设计项目选题推荐

摘 要 现如今生活质量提高&#xff0c;人们追求精神健康&#xff0c;与家中宠物朝夕相处&#xff0c;感情深厚&#xff0c;宠物渐渐成了我们身边的朋友。因而宠物生病了&#xff0c;需要去看病&#xff0c;自古医院救死扶伤&#xff0c;生命无贵贱&#xff0c;无论人类还是动物…

C# Onnx LSTR 基于Transformer的端到端实时车道线检测

目录 效果 模型信息 项目 代码 下载 效果 模型信息 lstr_360x640.onnx Inputs ------------------------- name&#xff1a;input_rgb tensor&#xff1a;Float[1, 3, 360, 640] name&#xff1a;input_mask tensor&#xff1a;Float[1, 1, 360, 640] -----------------…