[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.pswp.cn/pingmian/76000.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/76000.shtml
英文地址,請注明出處:http://en.pswp.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;并結合鏈表數據結構來管理…