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

news2025/4/15 2:40:11

一、前言

        最近在项目需要将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.c

OPENCV_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.coloradmin.cn/o/2334085.html

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

相关文章

Rag实现流程

Rag实现流程 目录 Rag实现流程1. 加载问答链代码解释`chain_type="stuff"` 的含义其他 `chain_type` 参数选项及特点1. `map_reduce`2. `refine`3. `map_rerank`示例代码展示不同 `chain_type` 的使用其他参数类型2. 提出问题3. 检索相关文档代码解释其他参数类型4. …

【c语言】指针习题

练习一&#xff1a;使用指针打印数组内容 #include <stdio.h> void print(int* p, int sz) {int i 0;for (i 0; i < sz; i) {printf("%d ", *p);//printf("%d ", *(p i));} } int main() {int arr[] { 1,2,3,4,5,6,7,8,9,10 };int sz sizeof…

银行业务知识序言

银行业务知识体系全景解析 第一章 金融创新浪潮下的银行业务知识革命 1.1 数字化转型驱动金融业态重构 在区块链、人工智能、物联网等技术的叠加作用下&#xff0c;全球银行业正经历着"服务无形化、流程智能化、风控穿透化"的深刻变革。根据麦肯锡《2023全球银行业…

智慧水务项目(八)基于Django 5.1 版本PyScada详细安装实战

一、说明 PyScada&#xff0c;一个基于Python和Django框架的开源SCADA&#xff08;数据采集与监视控制系统&#xff09;系统&#xff0c;采用HTML5技术打造人机界面&#xff08;HMI&#xff09;。它兼容多种工业协议&#xff0c;如Modbus TCP/IP、RTU、ASCII等&#xff0c;并具…

畅游Diffusion数字人(23):字节最新表情+动作模仿视频生成DreamActor-M1

畅游Diffusion数字人(0):专栏文章导航 前言:之前有很多动作模仿或者表情模仿的工作,但是如果要在实际使用中进行电影级的复刻工作,仅仅表情或动作模仿还不够,需要表情和动作一起模仿。最近字节跳动提出了一个表情+动作模仿视频生成DreamActor-M1。 目录 贡献概述 核心动…

【Unity网络编程知识】C#的 Http相关类学习

1、搭建HTTP服务器 使用别人做好的HTTP服务器软件&#xff0c;一般作为资源服务器时使用该方式&#xff08;学习阶段建议使用&#xff09;自己编写HTTP服务器应用程序&#xff0c;一般作为Web服务器或者短连接游戏服务器时使用该方式&#xff08;工作后由后端程序员来做&#…

SpringBoot企业级开发之【用户模块-更新用户头像】

功能如下所示&#xff1a; 我们先看一下接口文档&#xff1a; 为什么头像是一串字符串呢&#xff1f;因为我们是将头像图片放到第三方去存储&#xff0c;比如&#xff1a;阿里云等 开发思路&#xff1a; 实操&#xff1a; 1.controller 注意!这里使用【PatchMapping】注解…

DAPP实战篇:使用ethersjs连接智能合约并输入地址查询该地址余额

本系列目录 专栏:区块链入门到放弃查看目录-CSDN博客文章浏览阅读400次。为了方便查看将本专栏的所有内容列出目录,按照顺序查看即可。后续也会在此规划一下后续内容,因此如果遇到不能点击的,代表还没有更新。声明:文中所出观点大多数源于笔者多年开发经验所总结,如果你…

网络流量管理-流(Flow)

1. 传统网络的问题&#xff1a;快递员送信模式 想象你每天要寄100封信给同一个朋友&#xff0c;传统网络的处理方式就像一个固执的快递员&#xff1a; 每封信都单独处理&#xff1a;检查地址、规划路线、盖章、装车…即使所有信的目的地、收件人都相同&#xff0c;也要重复100…

每日文献(十一)——Part two

今天从第四章&#xff1a;快速RCNN&#xff0c;方法细节开始介绍。 目录 四、快速RCNN&#xff1a;方法细节 4.1 快速R-CNN回顾 4.2 对抗网络设计 4.2.1 遮挡的对抗空间信息损失 4.2.2 对抗空间Transformer网络 4.2.3 对抗融合 五、实验 5.1 实验设置 5.2 PASCAL VOC…

Laravel 实现 队列 发送邮件功能

一. 什么是队列 在构建 Web 应用程序时&#xff0c;你可能需要执行一些任务&#xff0c;例如解析文件&#xff0c;发送邮件&#xff0c;大量的数据计算等等&#xff0c;这些任务在典型的 Web 请求期间需要很长时间才能执行。 庆幸的是&#xff0c;Laravel 可以创建在后台运行…

一、绪论(Introduction of Artificial Intelligence)

写在前面&#xff1a; 老师比较看重的点&#xff1a;对问题的概念本质的理解&#xff0c;不会考试一堆运算的东西&#xff0c;只需要将概念理解清楚就可以&#xff0c;最后一个题会出一个综合题&#xff0c;看潜力&#xff0c;前面的部分考的不是很深&#xff0c;不是很难&…

Web攻防—SSRF服务端请求伪造Gopher伪协议无回显利用

前言 重学Top10的第二篇&#xff0c;希望各位大佬不要见笑。 SSRF原理 SSRF又叫服务端请求伪造&#xff0c;是一种由服务端发起的恶意请求&#xff0c;SSRF发生在应用程序允许攻击者诱使服务器向任意域或资源发送未经授权的请求时。服务器充当代理&#xff0c;执行攻击者构造…

【时时三省】(C语言基础)选择结构程序综合举例

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 下面综合介绍几个包含选择结构的应用程序。 例题1&#xff1a; 写一程序&#xff0c;判断某一年是否为闰年。 程序1&#xff1a; 先画出判别闰年算法的流程图&#xff0c;见下图用变量le…

File 类 (文件|文件夹操作)

一、File 类 1.1 前言 在 JDK 中 通过 java.io.File 类&#xff0c;可以实现操作系统重文件|文件夹的创建、删除、查看、重命名等操作。 1.2 File 类构造方法 File 一共提供了四个构造方法&#xff0c;都是有参构造。其中最常使用的是 File(String) 和 File(String, String)…

Hadoop文件操作指南:深入解析文件操作

1 Hadoop文件系统概述 Hadoop分布式文件系统(HDFS)是Hadoop生态的核心存储组件&#xff0c;专为大规模数据集设计&#xff0c;具有高容错性和高吞吐量特性。 HDFS核心特性: 分布式存储&#xff1a;文件被分割成块(默认128MB)分布存储多副本机制&#xff1a;每个块默认3副本&…

STM32 HAL库之EXTI示例代码

外部中断按键控制LED灯 在main.c中 HAL_Init(); 初始化Flash&#xff0c;中断优先级以及HAL_MspInit函数&#xff0c;也就是 stm32f1xx_hal.c 中 HAL_StatusTypeDef HAL_Init(void) {/* Configure Flash prefetch */ #if (PREFETCH_ENABLE ! 0) #if defined(STM32F101x6) || …

《TCP/IP网络编程》学习笔记 | Chapter 23:IOCP

《TCP/IP网络编程》学习笔记 | Chapter 23&#xff1a;IOCP 《TCP/IP网络编程》学习笔记 | Chapter 23&#xff1a;IOCP通过重叠 I/O 理解 IOCPepoll 和 IOCP 的性能比较实现非阻塞模式的套接字以纯重叠 I/O 方式实现回声服务器端重新实现客户端测试从重叠 I/O 模型到 IOCP 模型…

CAD导入arcgis中保持面积不变的方法

1、加载CAD数据&#xff0c;选择面数据&#xff0c;如下&#xff1a; 2、加载进来后&#xff0c;右键导出数据&#xff0c;导出成面shp数据&#xff0c;如下&#xff1a; 3、选择存储路径&#xff0c;导出面后计算面积&#xff0c;如下&#xff1a; 4、与CAD中的闭合线面积核对…

rustdesk自建服务器怎么填写客户端配置信息

目录 # id、api、中继都怎么填&#xff1f;rustdesk程序启动后服务不自动启动 # id、api、中继都怎么填&#xff1f; rustdesk程序启动后服务不自动启动 完全退出RudtDesk程序&#xff08;右下角托盘区有的话&#xff0c;需要右键点退出&#xff09; 创建windows服务&#xff…