[Linux]从零开始的ARM Linux交叉编译与.so文件链接教程

一、前言

        最近在项目需要将C++版本的opencv集成到原本的代码中从而进行一些简单的图像处理。但是在这其中遇到了一些问题,首先就是原本的opencv我们需要在x86的架构上进行编译然后将其集成到我们的项目中,这里我们到底应该将opencv编译为x86架构的还是编译成ARM架构,其次就是,编译完成以后,我们得到的都是.so的二进制文件,我们应该怎么将其链接到我们自己的项目中呢?带着这些问题,所以才有了这一篇教程。所以本次教程就是为了教大家如何将一个开源的项目交叉编译,并且将其链接到自己的项目中,如果你准备好了,就让我们开始吧!

二、谁适合本次教程

        因为本次教程已经涉及到Linux C应用开发了所以并不适用小白,所以请具备一定的Linux基础以后,再阅读本次教程。在教程中很多关于Linux的基础操作我并不会细讲,甚至有的简单的步骤我会直接省略。让我们开始吧!

三、动态链接的概念

        首先我们来讲讲什么是动态链接,以及什么是动态链接库。首先来讲讲什么是动态链接,动态链接是一种在程序运行时解析外部函数和变量引用的机制。简单来说,我们在编译程序时,并不需要将这部分程序编译到二进制文件中而是在程序运行时进行调用。然后就是动态连接库,动态链接库是包含可被多个程序共享的代码和数据的文件。动态连接库往往被我们编译成二进制文件(.dll或.so)然后在外部通过.h或者其它接口进行调用,这也是目前最主流的编程方式。总的来说动态连接库解决了代码重复依赖的问题,我们将一部分公共的代码编译到一个二进制文件中并且对外开放接口,外部程序通过预留接口访问动态连接库从而实现一套代码被多个程序使用。其次,如果我们的代码涉及机密,可以将其编译成动态链接库并且开放接口给用户使用,用户在可以使用完整函数功能的同时又不能获取源代码。了解了这些,下面就来带大家看看如何交叉编译动态链接库并且将其链接到自己的项目中!

四、动态链接库的编译与链接

        这里大家需要注意,我们后面所说的编译都是指交叉编译,也就是说我们会在x86的设备上交叉编译动态链接库,并且将其链接到自己的代码中再将其放到开发板端运行。如果你只想在x86设备上完成此操作其实也可以直接看本次教程,因为不管需不需要交叉编译,道理都是一样的,唯一不一样的就是交叉编译后的程序需要放到开发板端运行。

        这里我们首先需要安装交叉编译环境,交叉编译器的安装与环境变量的添加在之前交叉调试的文章中已经讲过了,大家可以直接参考:

vs code交叉调试教程:[Linux]从零开始的vs code交叉调试arm Linux程序教程-CSDN博客

这里我就默认大家已经安装好了Ubuntu并且已经安装好了交叉编译器,需要像这样能够输出版本号,如下图所示:

输入命令以后,能够有上面的输出就表示交叉编译环境没有问题,就可以进行下一步了。

这里我们同样使用图形化中的vs code进行操作,首先我们需要新建一个文件夹,直接使用下面的命令即可:

mkdir Project

然后我们再用vs code打开这个文件夹,这个文件夹后面也会作为我们的工程文件夹:

然后我们新建一个用于编译动态链接库的.c文件,这里我就直接叫“lib.c”了:

下面我们可以将下方的测试代码拷贝到这个.c的文件中:

#include "stdio.h"void Hello_World()
{printf("Hello World\n");
}void Hello_Gcc()
{printf("Hello Gcc\n");
}void Hello_Arm()
{printf("Hello Arm\n");
}

因为我们使用这个.c的文件编译动态链接库,所以并不需要写主函数。又因为是用于测试,所以写的函数非常简单,写好以后,如图所示:

大家将代码写入文件以后,记得保存。这里我们还需要一个.h文件来调用动态链接库中的函数,因为在动态链接库中只包含了函数的定义,没有包含声明,我们需要在.h文件中声明这些函数,这也是为了给外部一个接口供外部调用。这里我直接新建了了一个名为“lib.h”的文件:

我们在.h文件中写入下面的代码声明我们在.c文件中定义的函数:

#ifndef __LIB_H__
#define __LIB_H__void Hello_World();
void Hello_Gcc();
void Hello_Arm();#endif

写入以后,如图所示:

这里同样的,写入以后记得保存,然后我们在.c文件中引用这个.h文件:

这里我们修改完以后,我们将.c和.h文件都保存好,然后我们在项目目录下使用下面的命令将我们刚刚的代码编译成动态链接库:

aarch64-linux-gnu-gcc -fPIC -shared -o lib.so lib.c

这里还是来简单解释一下这段代码,首先是“aarch64-linux-gnu-gcc”,这就是我们编译时使用的交叉编译器,这里就不多说了,然后“-fPIC”是为了生成位置无关代码,这是动态库的要求。然后是“-shared”是为了告诉编译器要将这个文件编译成动态链接库,“-o lib.so”是为了指定生成的二进制文件的名字,这里生成的二进制名字就叫“lib.so”,最后“lib.c”就是我们输入的源文件了。

编译完成以后,就可以看到我们的项目目录下多了一个.so的文件,这个就是我们通过.c文件编译出的动态链接库:

这里我们可以使用“nm”工具来查看这个动态链接库中是否包含了我们写的函数,先使用下面的命令安装一个“nm”的工具库:

sudo apt install binutils

安装完成以后,我们使用下面的命令来检查我们的.so文件,这里lib.so就是我们编译出来的动态链接文件:

nm -D lib.so

输入命令以后,可以看到许多关于这个动态链接库的信息,看不懂没关系,我们只需要找输出的信息中有没有我们刚刚写的函数,这里可以看到,我们写的函数已经成功的编译到动态链接库中了:

这一步一般不会出错,就不多说了。

下面我们来使用一下这个被我们编译出来的动态链接库,这里我们直接在原本的项目文件夹中直接新建一个名为“main.c”的文件:

下面我们在main.c中直接输入下面的代码:

#include "lib.h"int main()
{Hello_World();Hello_Gcc();Hello_Arm();
}

这里我们只需要引用我们动态链接库的头文件即可。

写入完成以后,如图所示:

然后我们使用下面的命令来编译这个main.c文件:

aarch64-linux-gnu-gcc main.c -o main -L./ lib.so

这里还是来简单解释一下命令,首先就是“aarch64-linux-gnu-gcc”这就不多说了,然后是“main.c”这是我们编译时输入的源文件,同样不多说了,“-o main”表示我们要输出的二进制文件名为“main,”

这里的“-L”表示自己指定.so文件路径,这里我写的“./”表示在当前目录下搜索.so文件,最后“lib.so”表示要链接的库的名称。

比那一完成以后,就可以看到项目目录下多了一个名为“main”的可执行文件:

我们下面再使用“nm”工具来查看一下我们编译出来的可执行文件:

这里可以看到,我们的函数已经被编译到这个可执行文件中了,但是可以看到这些函数的前面都有一个U,这里的U表示“Undefined”,这也证实了这些函数未在我们的可执行文件中定义,需要从别的库链接。

因为这个可执行文件我们是使用交叉编译器编译的,所以肯定是不能在X86的主机上运行的:

下面我们就来测试一下这个可执行文件是否可以正常运行,这里我们需要将可执行文件和动态链接库文件发送到开发板端,这里我直接使用sftp发送,大家可以选择自己熟悉的方式去传输文件:

在开发板端,我们有一个可执行文件和一个动态链接库文件,如下:

这里我们需要指定一个环境变量LD_LIBRARY_PATH,这个环境变量会指定除了在标准路径以外的路径中寻找链接库文件,我们直接使用下面的命令将这个环境变量设置为当前目录,表示在当前目录寻找动态链接库:

export LD_LIBRARY_PATH=./

路径设置完成以后,我们直接运行可执行文件即可:

这里我们可以看到,我们的函数可以正常打印。

如果我们不指定LD_LIBRARY_PATH路径的话,运行可执行文件时就会提示库找不到:

至此,我们的动态链接库已经正常的编译并且正常的链接到了我们的可执行文件中。

五、编译与链接opencv

        有了上面的经验以后,我们就可以来实战一下,这里就来教大家如何交叉编译opencv库并且链接到自己的项目中。

这里我们首先使用下面的命令来下载一下opencv的源码:

wget https://github.com/opencv/opencv/archive/refs/tags/4.5.5.tar.gz

如果这里下载卡住的话,就使用下面的命令配置一下代理,大家根据自己的情况自行配置即可:

export http_proxy=http://192.168.112.10:7890
export https_proxy=http://192.168.112.10:7890

拉取到opencv的源码压缩包以后,如图所示:

我们使用下面的命令解压opencv的源码压缩包:

tar -xvf 4.5.5.tar.gz

解压以后得到了下面的文件夹:

我们进入这个文件夹可以看到下面的文件夹:

下面我们准备编译,首先在opencv项目目录下新建一个名为“build”的目录,并且进入:

mkdir build

然后我们在build目录下新建一个名为build的构建文件:

touch build

下面我们在build构建文件中写入下面的构建脚本:

cmake-DCMAKE_SYSTEM_NAME=Linux \-DCMAKE_SYSTEM_PROCESSOR=aarch64 \-DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc \-DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++ \-DCMAKE_INSTALL_PREFIX="/home/chulingxiao/Opencv/install" \-DBUILD_LIST=core,imgproc,highgui \-DBUILD_EXAMPLES=OFF \-DBUILD_TESTS=OFF \-DWITH_JPEG=ON \-DWITH_PNG=ON \..make -j4
make install

因为我们已经将交叉编译器的可执行文件的路径添加到环境变量了,所以这里直接写交叉编译器的名字即可。写入以后我们保存退出即可。

DCMAKE_INSTALL_PREFIX变量可以设置我们编译后安装的路径。这里大家自己写安装的路径即可。

我们再使用下面的命令给这个构建脚本可执行权限:

chmod +x build

 然后我们使用下面的命令安装一下cmake:

sudo apt install cmake

然后我们直接执行这个可执行文件即可:

./build

随后就开始编译了:

我们等待makefile生成完成即可。生成完成makefile以后编译就开始了:

编译完成以后,可以看到我们的文件被安装到了如下目录:

我们打开安装的目录,可以看到以下文件夹:

这里的“include”文件夹里面放了所有的头文件:

在lib目录下放了所有的动态链接库文件:

下面来教大家如何将我们编译出来的内容链接到我们自己的项目中,这里我们首先回到项目文件夹中,然后将下面的内容写入main.c中用于测试我们opencv的功能,这是一个使用opencv将图片二值化的程序,可以将传入的图片二值化:

#include <opencv2/opencv.hpp>
#include <iostream>
int main()
{cv::Mat image = cv::imread("test.jpg", cv::IMREAD_GRAYSCALE);if (image.empty()) {std::cerr << "无法读取图片!" << std::endl;return -1;}// 二值化处理cv::Mat binary_image;double thresh_value = 128;  // 设定阈值cv::threshold(image, binary_image, thresh_value, 255, cv::THRESH_BINARY);// 保存结果cv::imwrite("test_output.jpg", binary_image);std::cout << "二值化完成,结果保存在 test_output.jpg" << std::endl;
}

这里因为头文件以及库的引用比较复杂,我们写一个makefile来帮助我们编译文件,在项目目录下新建一个makefile文件,将下面的内容复制到文件中:

# 编译器设置
CXX = aarch64-linux-gnu-g++
CXXFLAGS = -std=c++11# OpenCV 路径(改成你的安装路径)
OPENCV_PATH = /home/chulingxiao/Opencv/install
OPENCV_INC = /home/chulingxiao/Opencv/install/include/opencv4/
OPENCV_LIB = /home/chulingxiao/Opencv/install/lib# 程序名称
TARGET = main
SRC = main.cOPENCV_LIBS = -lopencv_core -lopencv_imgcodecs -lopencv_imgproc $(TARGET): $(SRC)$(CXX) $(CXXFLAGS) -I$(OPENCV_INC)   $< -o $@  -L$(OPENCV_LIB) $(OPENCV_LIBS)

复制以后如图所示:

这里大家只需要修改几个地方即可,首先就是OPENCV_PATH,这里大家将路径改为我们一开始构建opencv时DCMAKE_INSTALL_PREFIX变量的路径,也就是一开始设置的opencv的安装路径。

然后OPENCV_INC 路径大家写到opencv安装路径下的头文件路径,这里可以参考我写的。

最后OPENCV_LIB 路径大家写到opencv安装路径下的动态链接库路径,这里同样参考我写的。

修改完以上内容以后,就没有什么需要改了,我们直接在项目目录下输入“make”即可开始编译:

这里没有输出别的错误并且生成可执行文件就表示编译没有问题。

下面我们将这个可执行文件通过sftp传输到开发板端:

然后我们在开发板端运行这个可执行文件,发现缺少库:

我们在opencv安装目录中,将这个库拷贝到开发板:

然后再在开发板端指定一下寻找库的路径:

export LD_LIBRARY_PATH=./

然后再次执行可执行文件,发现还缺少了一个名为“libopencv_imgcodecs.so.405”的库:

我们再次使用sftp传输这个库到开发板:

我们再次运行可执行文件,发现还缺少了一个名为“libopencv_imgproc.so.405”的库:

我们再次使用sftp将这个库传输到开发板:

我们再次运行可执行文件,发现,可执行文件已经不提示找不到库了,提示的是找不到文件:

大家还记得我们的程序是做什么的吗?是的,这是一个将图像二值化的程序,要求我们传入一个图像,然而我们的目录下没有图像,这里我们传输一张图片到当前目录下并且将名字改为“test.jpg”这也和我们程序中的名称一样:

我们再次执行可执行文件,可以看到,图像已经正常被处理了,并且输出为了“test_output.jpg”:

我们将其传输到可视化界面中,可以看到图像被正常二值化:

这也证明了我们的opencv在正常工作,表示我们的交叉编译以及so文件的链接都是成功的。

六、结语

        尽管我们在这个过程中遇到了很多问题,但我教给大家的是解决问题的方法,这些方法也包括了如果我们在运行可执行文件缺少库我们应该怎么办编译时怎样链接库不会出错。当然,做完上面的步骤,相信大家对嵌入式Linux开发多少有一定的了解了,但这也只是学习嵌入式Linux开发的一个开始。那么最后,感谢大家的观看!

 

 

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

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

相关文章

svelte+vite+ts+melt-ui从0到1完整框架搭建

框架太“重”了&#xff1a;通常一个小型项目只由少数几个简单页面构成&#xff0c;如果使用 Vue 或者 React 这些框架来研发的话&#xff0c;有点“大材小用”了。构建的产物中包含了不少框架运行时代码(虚拟 DOM、响应式、状态管理等)&#xff0c;这些代码对于小型项目而言是…

无法看到新安装的 JDK 17

在 Linux 系统中使用 update-alternatives --config java 无法看到新安装的 JDK 17&#xff0c;可能是由于 JDK 未正确注册到系统备选列表中。 一、原因分析 JDK 未注册到 update-alternatives update-alternatives 工具需要手动注册 JDK 路径后才能识别新版本。如果仅安装 JDK…

鼎讯信通 便携式雷达信号干扰模拟器:打造实战化电磁环境的新利器

在现代战争中&#xff0c;电磁环境的复杂性直接影响着雷达装备的性能和作战效果。面对敌方日益精进的电子战手段&#xff0c;如何提升雷达设备的抗干扰能力&#xff0c;确保其在实战环境中的稳定性和可靠性&#xff0c;已成为各国军队和科研机构的重要课题。 为此&#xff0c;…

【AI提示词】决策专家

提示说明 决策专家可以帮助你进行科学决策&#xff0c;尽可能避免错误&#xff0c;提升决策成功的概率。 提示词 # Role : 决策专家决策&#xff0c;是面对不容易判断优劣的几个选项&#xff0c;做出正确的选择。说白了&#xff0c;决策就是拿个主意。决策专家是基于科学决策…

力扣Hot100题,刷题

力扣HOT100 - 1. 两数之和 解题思路&#xff1a; 解法一&#xff1a;暴力 class Solution {public int[] twoSum(int[] nums, int target) {int n nums.length;for (int i 0; i < n; i)for (int j i 1; j < n; j) {if (target nums[i] nums[j])return new int[]…

uni-app ucharts自定义换行tooltips

实现效果&#xff1a; 第一步&#xff1a;在uni_modules文件夹下找到config-ucharts.js和u-charts.js文件 第二步&#xff1a;在config-ucharts.js文件中配置换行格式 // 换行格式"wrapTooltip":function(item, category, index, opts){return item.name&#xff1a;…

国标GB28181视频平台EasyCVR顺应智慧农业自动化趋势,打造大棚实时视频监控防线

一、方案背景 近年来&#xff0c;温室大棚种植技术凭借其显著的优势&#xff0c;在提升农作物产量和质量、丰富农产品供应方面发挥了重要的作用&#xff0c;极大改善了人们的生活水平&#xff0c;得到了广泛的推广和应用。大棚内的温度、湿度、光照度和二氧化碳浓度等环境因素…

InternVideo2.5:Empowering Video MLLMs with Long and Rich Context Modeling

一、TL&#xff1b;DR InternVideo2.5通过LRC建模来提升MLLM的性能。层次化token压缩和任务偏好优化&#xff08;mask时空 head&#xff09;整合到一个框架中&#xff0c;并通过自适应层次化token压缩来开发紧凑的时空表征MVBench/Perception Test/EgoSchema/MLVU数据benchmar…

【时时三省】(C语言基础)条件运算符和条件表达式

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 有一种if语句&#xff0c;当被判别的表达式的值为“真”或“假”时&#xff0c;都执行一个赋值语句且向一个变量赋值。 如&#xff1a; if ( a > b ) max a&#xff1b; else max …

KWDB创作者计划—边缘计算:从概念到落地的技术解读

引言 随着物联网&#xff08;IoT&#xff09;和人工智能&#xff08;AI&#xff09;的快速发展&#xff0c;数据量呈爆炸式增长&#xff0c;传统的云计算架构逐渐暴露出延迟高、带宽占用大等问题。边缘计算作为一种新兴的分布式计算范式&#xff0c;正在改变数据处理的方式。本…

蓝桥杯基础算法-递归

代码简洁&#xff0c;但涉及到的运算&#xff0c;会随着递归层数的增加成指数级增长 路分析&#xff1a;第20行20列位于45度这条线上 这条线上的数字是1 5 13 25 41...两数之差:4 8 12 16 --->每一个都是在前面的基础上4&#xff0c;可以用递归或者循环 public class dem…

通过学习opencv图像库编程借助第三方库函数完成一个综合程序设计

通过学习opencv图像库编程借助第三方库函数完成一个综合程序设计 1) 编译命令解释&#xff1a; 编译命令&#xff1a; gcc test1.cpp -o test1 pkg-config --cflags --libs opencv这条命令包含了以下部分&#xff1a; gcc test1.cpp -o test1: gcc 是 GNU 编译器集合&#…

第十四届蓝桥杯大赛软件赛国赛C/C++研究生组

研究生C国赛软件大赛 题一&#xff1a;混乘数字题二&#xff1a;钉板上的正方形题三&#xff1a;整数变换题四&#xff1a;躲炮弹题五&#xff1a;最大区间 题一&#xff1a;混乘数字 有一点像哈希表&#xff1a; 首先定义两个数组&#xff0c;拆分ab和n 然后令n a*b 查看两个…

系统与网络安全------网络通信原理(2)

资料整理于网络资料、书本资料、AI&#xff0c;仅供个人学习参考。 物理层解析 物理层概述 物理层是TCP/IP模型的最底层物理层数据传输提供稳定的物理连接 物理层功能 定义设备的物理连接的标准和特性&#xff0c;比如接口形状&#xff0c;大小定义电气特性&#xff0c;高低…

内容中台的数字化管理核心是什么?

数字化整合与系统协同 现代企业的内容管理正经历从分散式架构向数字化整合的范式转变。通过将内容管理系统与文档管理技术深度耦合&#xff0c;组织能够打破数据孤岛&#xff0c;实现跨部门、跨平台的资源互通。例如&#xff0c;基于元数据分类的标准化体系&#xff0c;不仅提…

Python爬虫第二战(使用xpath爬取网站数据)

本文是我在学习过程中记录学习的点点滴滴&#xff0c;目的是为了学完之后巩固一下顺便也和大家分享一下&#xff0c;日后忘记了也可以方便快速的复习。 使用xpath爬取猪八戒网站数据 前言 前言 今天学习的主要是关于Python使用xpath来爬取猪八戒网的网页知识的理解和应用 #1.获…

进程同步和进程互斥的区别

如大家所了解的&#xff0c;进程互斥是由互斥资源引起的&#xff0c;即各进程之间共享互斥资源的使用权&#xff0c;这种竞争没有确定的必然联系&#xff0c;哪个进程竞争到互斥资源的使用权&#xff0c;则该资源就归哪个进程使用&#xff0c;从而获得所需资源的进程就可以获得…

ArkTS语言基础之函数

前言 臭宝们终于来到了ArkTS基础之函数&#xff0c;今天我们来学习一下ArkTS的函数的相关知识&#xff0c;上一节中也有一些函数的基础知识。 函数声明 函数声明引入一个函数&#xff0c;包含其名称、参数列表、返回类型和函数体,在下面的例子中&#xff0c;我们声明了一个名…

redis中的hash

Redis中的hash是什么 Hash: 哈希&#xff0c;也叫散列&#xff0c;是一种通过哈希函数将键映射到表中位置的数据结构&#xff0c;哈希函数是关键&#xff0c;它把键转换成索引。 Redis Hash&#xff08;散列表&#xff09;是一种 field-value pairs&#xff08;键值对&#x…

弹簧质点系统(C++实现)

本文实现一个简单的物理算法&#xff1a;弹簧质点系统&#xff08;Mass-Spring System&#xff09;。这是一个经典的物理模拟算法&#xff0c;常用于模拟弹性物体&#xff08;如布料、弹簧等&#xff09;的行为。我们将使用C来实现这个算法&#xff0c;并结合链表数据结构来管理…