welcom ! Handel home

2024年12月11日 星期三

C# P/Invoke 呼叫 C++ DLL 範例與說明

 P/Invoke (Platform Invoke) 是 .NET Framework 提供的一種機制,允許受控程式碼 (如 C#) 呼叫非受控程式碼 (如 C++ DLL) 中的函式。

這對於需要利用既有 C++ 程式庫或直接與系統 API 互動的 .NET 應用程式來說非常有用。

[基本步驟]

1. 準備 C++ DLL:

○ 使用 C++ 編寫 DLL,並將函式宣告為 extern "C" __declspec(dllexport),以便 C# 能正確找到並呼叫這些函式。

2. 在 C# 中宣告 P/Invoke 函式:

○ 使用 DllImportAttribute 標記,指定 DLL 的名稱和函式的名稱。

○ 匹配函式的簽名,包括傳入參數和回傳值。

3. 呼叫 P/Invoke 函式:

○ 在 C# 程式碼中,直接呼叫 P/Invoke 函式,就像呼叫任何其他 .NET 方法一樣。

[範例]

C++ DLL (mydll.cpp):

C++

#include <windows.h>


extern "C" __declspec(dllexport) int Add(int a, int b)

{

    return a + b;

}


[C# 程式碼]:

C#

using System;

using System.Runtime.InteropServices;


namespace PInvokeExample

{

    class Program

    {

        [DllImport("mydll.dll")]

        static extern int Add(int a, int b);


        static void Main(string[] args)

        {

            int result = Add(5, 3);

            Console.WriteLine("Result: " + result);

        }

    }

}


[說明]

• DllImportAttribute: 

○ DllImport("mydll.dll"): 指定要載入的 DLL 名稱。

○ static extern int Add(int a, int b): 宣告一個外部函式 Add,其簽名與 C++ 中的 Add 函式完全匹配。

• 封送處理 (Marshalling): 

○ P/Invoke 會自動處理 C# 類型和 C++ 類型之間的轉換,這稱為封送處理。

○ 基本的數值類型 (int, double 等) 通常可以直接對應。

○ 對於結構、字串等複雜類型,需要使用 Marshal 類來進行手動封送。

• 注意事項: 

○ DLL 的位數 (32 位或 64 位) 必須與你的 .NET 應用程式一致。

○ DLL 的路徑要確保能被找到。

○ 函式簽名必須完全匹配,包括參數類型、順序和返回值類型。

○ 對於 C++ 中的指標,在 C# 中可以使用 IntPtr 來表示。

[更複雜的例子]:傳遞結構

C++ DLL (mydll.cpp):

C++

struct Point

{

    int x;

    int y;

};


extern "C" __declspec(dllexport) int GetDistance(Point* p1, Point* p2)

{

    // ... 计算距离

}


C# 程式碼:

C#

[StructLayout(LayoutKind.Sequential)]

struct Point

{

    public int x;

    public int y;

}


[DllImport("mydll.dll")]

static extern int GetDistance(ref Point p1, ref Point p2);


[更多注意事項]

• 錯誤處理: 可以使用 Marshal.GetLastWin32Error() 獲取最後一次 Win32 錯誤碼。

• 回呼函式: 可以使用 Callback 屬性來定義回呼函式。

• 字串封送: 使用 Marshal.StringToHGlobalAnsi 和 Marshal.PtrToStringAnsi 來轉換字串。

[總結]

P/Invoke 提供了一種方便的方式,讓 C# 程式可以利用 C++ DLL 中的函式。但需要注意的是,P/Invoke 的使用需要對 C++ 和 C# 的記憶體管理、資料類型以及平台調用機制有一定的了解。

建議:

• 保持函式簽名一致: 確保 C# 中的函式簽名與 C++ 中的函式簽名完全匹配。

• 處理封送: 對於複雜的資料類型,需要仔細處理封送。

• 錯誤處理: 始終檢查 P/Invoke 呼叫是否成功。

• 性能考慮: P/Invoke 會引入一定的性能開銷,對於性能要求極高的應用程式,可以考慮使用 C++/CLI 或其他方式。

探討:Visual C++ 中的 P/Invoke 替代方案與 C++ Interop

 C++ Interop。 這項技術提供了一種更直觀、更符合 C++ 語言特性的方式來與非託管程式碼進行互動,

相較於傳統的 P/Invoke,它能帶來更高的效率和更少的程式碼。

C++ Interop 的優勢與特性

• 無需手動封送: C++ Interop 能自動處理託管與非託管類型之間的轉換,大大簡化了程式碼。

• 原生 C++ 語法: 使用 C++ 語法來宣告和呼叫非託管函式,讓開發者能夠更自然地寫出程式碼。

• 模板支援: C++ 模板可以提供更強大的類型安全和泛型程式設計能力。

• CLR 和 WinRT 元件互動: 不僅限於傳統的 DLL,C++ Interop 還能與 CLR 和 WinRT 元件進行互動。

C++ Interop 的運作原理

C++ Interop 主要透過以下機制實現:

• __declspec(dllimport):用於宣告導入的函式或變數。

• extern "C":用於指定函式採用 C 語言連結方式,以避免名稱修飾。

• CLR 和 WinRT 投影: C++/CLI 提供了 CLR 和 WinRT 的投影,讓 C++ 程式碼能夠直接使用這些平臺的類型和成員。

何時使用 C++ Interop?

• 需要與現有的 C++ DLL 互動: 如果您已經有現成的 C++ DLL,使用 C++ Interop 是最直接的方式。

• 希望利用 C++ 的性能優勢: 對於性能要求較高的部分,可以將部分程式碼用 C++ 實現,然後透過 C++ Interop 呼叫。

• 需要與 CLR 或 WinRT 元件互動: 如果您的應用程式需要同時使用 C++ 和 .NET Framework 或 Windows Runtime,C++ Interop 能提供無縫的整合。

示例

C++

// C++ header file (mylib.h)

extern "C" __declspec(dllimport) int add(int a, int b);


// C++ source file (mycpp.cpp)

#include "mylib.h"


int main() {

    int result = add(3, 4);

    std::cout << result << std::endl;

    return 0;

}


結語

C++ Interop 是 Visual C++ 開發者的一項強大工具,它能幫助開發者更有效地利用 C++ 的優勢,同時又能與其他程式語言和平臺進行互操作。

如果您需要在 C++ 程式中呼叫非託管程式碼,C++ Interop 絕對是值得深入了解的技術。

C++ Interop 與 P/Invoke 的差異比較

 C++ Interop 和 P/Invoke 都是用於在 C++ 程式中呼叫非託管程式碼的技術,但兩者在使用方式、便利性以及功能上有一些差異。

C++ Interop

• 概念: C++ Interop 是一種更現代、更直觀的方式,允許 C++ 程式碼直接與其他 .NET 語言(如 C#、VB.NET)編寫的程式碼進行互動。它利用 C++/CLI 提供的語言擴展,使 C++ 程式碼能更自然地使用 .NET Framework 的類別和功能。

• 特性: 

○ 隱式 P/Invoke: C++ Interop 常常被稱為「隱式 P/Invoke」,因為它在幕後自動處理許多 P/Invoke 所需的手動配置。

○ 類型安全: C++ Interop 提供了更強的類型安全,減少了因類型不匹配導致的錯誤。

○ 自然語法: 可以使用 C++ 語法直接操作 .NET 類別和物件。

○ 自動封送處理: 大部分的資料封送處理都是自動完成的。

• 優點: 

○ 更易於使用: 比 P/Invoke 更直觀,減少了程式設計師的工作量。

○ 更好的整合性: 與 .NET Framework 的整合更緊密。

○ 更高的效率: 在許多情況下,C++ Interop 的性能比 P/Invoke 更高。

• 缺點: 

○ 學習曲線: 需要學習 C++/CLI 的語法和概念。

○ 較為複雜: 當需要處理複雜的資料結構或 COM 元件時,可能需要更深入的了解。

P/Invoke (平台調用)

• 概念: P/Invoke 是一種更傳統的方式,允許 .NET 程式碼呼叫 Windows API 或其他非託管 DLL 中的函式。它需要程式設計師手動指定函式簽名、參數類型以及封送處理方式。

• 特性: 

○ 手動配置: 需要手動指定函式簽名、參數類型和封送處理方式。

○ 封送處理: 必須處理託管類型和非託管類型之間的資料轉換。

○ 較低層級: 提供了更低層級的控制,但也需要更多的程式碼。

• 優點: 

○ 靈活性高: 可以呼叫任何 Windows API 函式。

○ 適用於各種場景: 適用於各種互操作場景,包括 COM 元件、C++ DLL 等。

• 缺點: 

○ 易錯: 手動配置容易出錯,尤其是在處理複雜的資料結構時。

○ 程式碼複雜: 需要寫更多的程式碼。

○ 性能較低: 相對於 C++ Interop,P/Invoke 的性能可能稍低。

何時使用哪一種?

• C++ Interop: 

○ 希望與 .NET Framework 深度整合。

○ 需要使用 C++/CLI 的特性。

○ 追求更高的開發效率。

• P/Invoke: 

○ 需要呼叫特定的 Windows API 函式。

○ 需要對互操作過程有精確的控制。

○ 性能要求極高,且對程式碼大小敏感。

總結

C++ Interop 和 P/Invoke 都是有用的工具,選擇哪一種取決於具體的應用場景。如果您的目標是與 .NET Framework 深度整合,並且希望提高開發效率,那麼 C++ Interop 是更好的選擇。如果需要對互操作過程有精確的控制,或者需要呼叫特定的 Windows API 函式,那麼 P/Invoke 可能是更合適的選擇。

建議:

• 一般情況下,建議優先考慮 C++ Interop,因為它更易於使用且效率更高。

• 對於複雜的場景,可能需要結合 C++ Interop 和 P/Invoke 來實現。

• 如果需要呼叫非常底層的 API,或者對性能要求極高,那麼 P/Invoke 可能是唯一的選擇。

舉例來說:

• 如果您想在 C++ 程式中使用 .NET 的集合類,那麼 C++ Interop 是更好的選擇。

• 如果您想在 C# 程式中呼叫一個 C++ DLL 中的特定函式,那麼 P/Invoke 可能更適合。


C++ 程式中的非託管程式碼與託管程式碼

 


2024年12月10日 星期二

YOLO 目前發展的時間軸

 


YOLOv1: 最早的版本,奠定了YOLO系列的基礎。
YOLOv2: 引入批次正規化、錨框和尺寸聚類等技術,提升了模型的性能。
YOLOv3: 採用更有效的骨幹網路、多尺度特徵融合等方法,進一步增強了模型的表現。
YOLOv4: 引入Mosaic資料增強、無錨框偵測頭、新的損失函數等創新,大幅提升了模型的準確度。
YOLOv5: 優化了超參數,整合實驗追蹤和自動匯出功能,使其更易於使用。
YOLOv6: 由美團開放原始碼,並應用於其許多自動配送機器人中。
YOLOv7: 新增了人體姿態估計等任務。
YOLOv8: 由Ultralytics推出,支援更廣泛的視覺AI任務,性能更佳。
YOLOv9: 引入可程式化梯度資訊(PGI)和通用高效層聚合網路(GELAN)等創新方法。
YOLOv10: 由清華大學研究人員開發,採用端到端偵測頭,無需非極大值抑制(NMS)。
YOLOv11: Ultralytics最新的版本,在多種任務上取得了最先進的性能
Ps: 
    >> V4,V7,V9 於技術上有改進突破 提高準確率和速度 台灣中研院發表 
    >> V5, V8,V11 為 Ultralytics推出改進版本 無核心技術貢獻 但改進使用介面使YOLO容易使用 
         可以用 Pytorch 工具編譯 集多個類模型可適合各種平台使用








Yolov8 發展資源 模型分類

 



#YOLOv8 的不同版本與特色

#參數說明:
模型大小 (MB): 模型檔的大小,直接影響模型的部署和存儲。
參數量 (M): 模型中的可訓練參數數量,影響模型的複雜度和訓練時間。
FLOPs (G): 浮點運算次數,反映模型的計算量,影響推理速度。
輸入尺寸: 模型輸入圖像的尺寸,影響模型的感受野和檢測精度。
mAP@0.5: 平均精度均值,用於評估模型的整體性能。
mAP@0.5:0.95: 平均精度均值,考慮了不同 IoU 閾值下的性能,更全面地反映模型的性能。
推理速度 (FPS): 模型在特定硬體上的推理速度,單位元為幀每秒。
適用場景: 根據模型的性能特點,建議的應用場景。

#模型特性:
YOLOv8n: 極輕量,適合資源受限設備,如嵌入式系統和移動設備。
YOLOv8s: 速度與精度平衡,適用於一般目標檢測任務。
YOLOv8m: 精度較高,速度適中,適合中等複雜度場景。
YOLOv8l: 適合複雜場景,精度高,但對計算資源要求較高。
YOLOv8x: 最大模型,精度最高,但對計算資源要求極高。









2024年12月5日 星期四

pi3 & pi4 安裝 Python3 matplotlib and PyQt5 Note

 #pi3 and pi4 GUI 介面下安裝 matplotlib  得 採用   apt-get 套件管理安裝:

$sudo apt-get update -y

$sudo apt-get install python3-matplotlib -y

$sudo apt install python3-pyqt5 -y



Vbox 安裝 Linux ubuntu 2404 教學資源收集

 教學視頻收集:

   ==>>新版VirtualBox安装Ubuntu 24.04,超详细教学!安装增强功能 ...




# 安裝後 使用注意事項
 >> 設定 使用者  user_id  and user_passwd  初始者將內定 為 管理者 有 sudo 最高權限
 >> 避免 使用 id  Root  外網登入 將 root 設定為 禁止外網登入(ps id root 內定系統管理者)
 >> 啟動時 安裝openssh 安裝並啟動ssh遠端連線伺服器

>> 若要連出外網 得設定 無密碼登入 及 金鑰安裝啟動 提高資安

VirtualBox 虛擬機軟體 安裝摘要筆記

主旨:

     #VirtualBox (VBox) 簡介:

    #安裝準備工作

    #安裝操作


#VirtualBox (VBox) 簡介:

Oracle VirtualBox 是全球最熱門的開源跨平台虛擬化軟體,開發人員可使用這款軟體在單一裝置上執行多個作業系統,藉此以更快的速度交付程式碼。 IT 團隊和解決方案提供者使用VirtualBox 可降低作業成本,縮短在內部部署和雲端安全部署應用系統所需的時間。

#安裝準備工作

>> PC or NB 進入 Bios 設定 缺定 VT 功能打開

  ==> 如何在電腦上啟用虛擬化(VT)?

  


>> download VirtualBox 軟體

 ==> https://www.virtualbox.org/wiki/Downloads


#安裝操作

==> Virtual Box 虛擬機使用教學

==> Day 19 : Linux - 如何用virtualBox安裝Linux的VM虛擬機?


# PC 如何上傳資料到 VM

 ==> Ftp tool 

        download ftp tool 

      


    Ftp 操作教學


==> Telnet tool  

PuTTY



PuTTY是一款整合虛擬終端系統控制臺和網路檔案傳輸為一體的自由及開放原始碼的程式。它支援多種網路協定,包括SCPSSHTelnetrlogin和原始的通訊端連接。它也可以連接到序列埠。在官方網站的FAQ上有提到其軟體名字「PuTTY」沒有特殊含義。


download putty 

==> https://homepage.ntu.edu.tw/~jsc/help/putty.html






2024年12月3日 星期二

PyQt5 Ui demo 範例

 顯示介面









pyqt_code.py

===================================================


import sys

from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QFileDialog, QGridLayout

from PyQt5.QtGui import QPixmap

from PyQt5.QtCore import Qt


class ImageDisplayApp(QWidget):

    def __init__(self):

        super().__init__()

        self.init_ui()


    def init_ui(self):

        # 創建按鈕

        self.button1 = QPushButton('選擇 BMP 圖片', self)

        self.button1.setFixedSize(100, 40)

        self.button1.clicked.connect(self.open_file_dialog)


        self.button2 = QPushButton('選擇 BMP 圖片 2', self)

        self.button2.setFixedSize(100, 40)

        self.button2.clicked.connect(self.open_file_dialog_2)


        self.button3 = QPushButton('按鈕 3', self)

        self.button3.setFixedSize(100, 40)


        self.button4 = QPushButton('按鈕 4', self)

        self.button4.setFixedSize(100, 40)


        self.button5 = QPushButton('按鈕 5', self)

        self.button5.setFixedSize(100, 40)


        self.button6 = QPushButton('按鈕 6', self)

        self.button6.setFixedSize(100, 40)

        

        # 創建顯示圖形的標籤

        self.image_label = QLabel('尚未選擇圖片', self)

        self.image_label.setAlignment(Qt.AlignCenter)

        self.image_label.setScaledContents(False)

        self.image_label_2 = QLabel('尚未選擇圖片 2', self)

        self.image_label_2.setAlignment(Qt.AlignCenter)

        self.image_label_2.setScaledContents(False)


        # 創建顯示檔案路徑的標籤

        self.lab1 = QLabel('尚未選擇檔案路徑', self)

        self.lab1.setAlignment(Qt.AlignLeft)

        

        # 設置按鈕佈局

        button_layout = QVBoxLayout()

        button_layout.addWidget(self.button1)

        button_layout.addWidget(self.button2)

        button_layout.addWidget(self.button3)

        button_layout.addWidget(self.button4)

        button_layout.addWidget(self.button5)

        button_layout.addWidget(self.button6)

        button_layout.addStretch()

        

        # 設置圖片佈局,兩個圖像標籤垂直顯示且不重疊

        image_layout = QVBoxLayout()

        image_layout.addWidget(self.lab1)

        image_layout.addWidget(self.image_label)

        image_layout.addWidget(self.image_label_2)

        image_layout.addStretch()

        

        # 設置主佈局

        main_layout = QHBoxLayout()

        main_layout.addLayout(button_layout)

        main_layout.addLayout(image_layout)

        

        self.setLayout(main_layout)

        

        # 設置窗口屬性

        self.setWindowTitle('PyQt5 圖片顯示範例')

        self.setGeometry(100, 100, 1000, 600)

        self.show()


    def open_file_dialog(self):

        options = QFileDialog.Options()

        file_name, _ = QFileDialog.getOpenFileName(self, '選擇 BMP 圖片', '', 'BMP Files (*.bmp);;All Files (*)', options=options)

        if file_name:

            pixmap = QPixmap(file_name)

            self.image_label.setPixmap(pixmap)

            self.image_label.setFixedSize(pixmap.size())

            self.lab1.setText(f'選擇的檔案路徑: {file_name}')


    def open_file_dialog_2(self):

        options = QFileDialog.Options()

        file_name, _ = QFileDialog.getOpenFileName(self, '選擇 BMP 圖片 2', '', 'BMP Files (*.bmp);;All Files (*)', options=options)

        if file_name:

            pixmap = QPixmap(file_name)

            self.image_label_2.setPixmap(pixmap)

            self.image_label_2.setFixedSize(pixmap.size())


# 主程序入口

if __name__ == '__main__':

    app = QApplication(sys.argv)

    window = ImageDisplayApp()

    sys.exit(app.exec_())


Ubuntu PyQt5 Design TOOL install

如何在Linux Ubuntu Desktop安裝Qt Designer,用來設計python GUI使用者介面程式

ubuntu的版本:20.04.1 (LTS) 1. 確認python3已可以work


2. 安裝PyQt5

sudo apt-get install python3-pyqt5


3. 安裝Qt Designer

sudo apt-get install qttools5-dev-tools

sudo apt-get install qttools5-dev


4. 啟動Qt Designer

cd /usr/lib/x86_64-linux-gnu/qt5/bin

./designer


5. 將透過Qt Designer設計的ui檔,轉換匯出為py檔

pyuic5 /home/userid/workfolder/helloworld.ui -o helloworld.py


如果出現 Command 'pyuic5' not found, but can be installed with:

可以使用以下指令安裝pyuic5 sudo apt install pyqt5-dev-tools

2024年12月2日 星期一

Python 載入 gcc DLL (so) Note

 

動態載入C library (DLL)

從第一次知道python這個語言時,就聽聞python的其中一項強大之處,高彈性:
直接利用python coding方便又快速,真的遇到關鍵的效能瓶頸,再以C語言改寫即可。
CPython能達到這項目的的方法,個人知道有2種:Python/C API和ctypes.

P.S. 當然,如果目的是加速或與其他語言做binding就不只這兩項了。
例如RPython, Cython, …等派生或是擴充語言,但那進來考慮實在有點消化不完。

一來目前主要是想摸熟python本身的特性,
二來由於目前個人感興趣的部分在於CPython和C如何直接溝通,因此其他方式暫時不考慮了。

ctypes

最後稍微比較過後,Python及C之間的API也決定暫時擱置。
以之前經驗來說,這類的API接口大多只要搞懂制式的規格和作用,
剩下來的就是類似將內容填入表格的繁瑣工作。
相較之下,若能在幾乎不動C source code的情形下做到這件事情,個人認為有很大的優勢。

以下是一個用C語言寫成簡單(幾乎無經過修飾)的開方根的程式:

/* File: c_sqrt.c */
doublesqrt(doublek) {
   int i;
   double x=k;
   for(i=0; i<5; i++)
      x = x - (x*x-k)/(2*x); // Newton's Iteration.
   return x;  
}

接著把這個compile成shared library.

$ gcc c_sqrt.c -shared -fPIC -O2 -oc_sqrt.so

需要注意的是,由於個人的電腦都是Linux/OS X等類Unix系的,所以動態函式庫是.so檔。
而在python中,則直接利用ctypes module來讀檔:

import  ctypes
dyn = ctype.CDLL('./c_sqrt.so')  
dyn.sqrt.argtypes = [ctypes.c_double] # default: None
dyn.sqrt.restype  =  ctypes.c_double  # default: None  
                                      # default: c_int
print( dyn.sqrt(5) )

這段程式造出一個CDLL的物件dyn。
利用dyn去讀取sqrt時,會到c_sqrt.so找到sqrt的symbol,然後加到dyn這個物件裡。

然而預設的情況下,一個由CDLL讀入的C函數是無參數、回傳值為整數的函數。
如果直接呼叫dyn.sqrt(5)也只會得到0而已。
必須修改傳入的參數的型別(如第四行那樣,有多少參數就寫進list裡),
restype則是回傳的型別。

若回傳的值是一個結構包裝的東西,
或許還得要再經過宣告一個新的類別來使用:

from ctypes import *
class foo(Structure):
   _fields_=[("val", c_int),  ("dat", POINTER(c_int))]

參考資料:

  1. Python Document: ctypes — A foreign function library for Python
  2. 程式設計遇上小提琴 - Python的進步: ctypes
  3. (參考) Dynamically Loaded (DL) Libraries ,看看C語言中怎麼做到上面的事情.

參考自  ==> 
https://www.gitbook.com/?utm_source=legacy&utm_medium=redirect&utm_campaign=close_legacy

gcc 編譯 函示庫 建立靜態 和 動態 LIB

 

靜態函式庫

靜態函式庫(static library)就是由一些物件檔案(object files)所構成的封裝檔,通常其檔案名稱都會以 lib 開頭,而副檔名則為 .a

使用靜態連結函式庫的好處就是所有的程式都包裝在執行檔中,不會因為缺少函式庫的檔案而不能執行,不過缺點就是這樣的執行檔大小會比較大,而如果函式庫有更新的話,整個執行檔也要跟著重新編譯。

以下我們示範將 sum.c 的內容製作成一個靜態函式庫的步驟。

Step 1
編譯 sum.c 的程式碼:

gcc -c -o sum.o sum.c

Step 2
使用 ar 指令將 sum.o 打包成 libsum.a 這個靜態連結函式庫:

ar -rcs libsum.a sum.o

Step 3
使用 libsum.a 這個靜態連結函式庫:

gcc main.c -L. -lsum -o main_static

也可以直接把 libsum.a 這個靜態連結函式庫檔案放進去編譯:

gcc main.c libsum.a -o main_static

這樣就可以將 main.c 這個程式編譯成 main_static 這個執行檔,而在編譯的過程中就不需要 sum.c 這個原始碼的檔案。

使用靜態連結函式庫所編譯出來的執行檔可以獨立執行,不需要原本的 libsum.a 檔:

ldd main_static
	linux-vdso.so.1 =>  (0x00007fffb0d1a000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f0411e46000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f0412220000)

共享函式庫

共享函式庫(shared library)是在程式實際開始執行時,才會被載入的函式庫,執行檔本身與共享函式庫是分離的,這樣可以讓執行檔的大小比較小,而且未來共享函式庫在更新之後,執行檔也不需要重新編譯,而缺點則是執行檔在執行時就會需要共享函式庫的檔案,如果缺少了共享函式庫的檔案,就會無法執行。

共享函式庫的檔案通常也是以 lib 開頭,但是其副檔名則為 .so,以下是建立共享函式庫的步驟。

Step 1
編譯時加入 -fPIC 參數,產生共享函式庫所需要的 position independant code:

gcc -c -fPIC -o sum.o sum.c

Step 2
使用 gcc 建立共享函式庫:

gcc -shared -Wl,-soname,libsum.so.1 -o libsum.so.1.0.0 sum.o

這裡的 -Wl 是用來將一些參數設定傳給連結器(linker),所以之後的 -soname 等參數就是傳給連結器的參數。

-soname 指定為 libsum.so.1 是代表函式庫的名稱,以 lib 開頭,接著是自己取的名稱,最後加上 .so 與 version 版本號碼,這一個 version 版本號碼所代表的是函示庫的介面版本,如果介面有改變時就會增加 version 版本號碼,以維護相容性的問題。

而最後產生的實際檔案名稱也跟 soname 類似,不過後面多了 minor 與 release 版本號碼,中間的 minor 號碼是用於標示新增加的介面,而最後面的 release 號碼則是用於程式內容的修正(介面不變的情況)。

如果程式使用到這個共享函式庫,則在執行時就會依據 soname 所指定的名稱來尋找函式庫的檔案,如果想要看共享函式庫的 soname 屬性,可以使用 objdump 指令:

objdump -p libsum.so.1.0.0 | grep SONAME
  SONAME               libsum.so.1

建立好共享函式庫之後,要建立一個不含版本號碼的 .so 連結檔,gcc 在連結時所需要的函式庫檔案是這一個:

ln -s libsum.so.1.0.0 libsum.so

另外再建立一個執行時要用的連結檔:

ln -s libsum.so.1.0.0 libsum.so.1

使用共享函式庫來編譯執行檔:

gcc main.c -L. -lsum -o main_dynamic

或是這樣編譯也可以:

gcc main.c libsum.so -o main_dynamic

編譯完成後,要執行時需要指定 LD_LIBRARY_PATH

LD_LIBRARY_PATH=. ./main_dynamic
2.6 + 4.2 = 6.8

這一個 main_dynamic 執行檔在執行時,會需要 libsum.so.1 這一個共享函式庫檔案:

LD_LIBRARY_PATH=. ldd main_dynamic
	linux-vdso.so.1 =>  (0x00007ffc5f1f7000)
	libsum.so.1 => ./libsum.so.1 (0x00007efd0148d000)
	libc.so.6 => /lib64/libc.so.6 (0x00007efd010b3000)
	/lib64/ld-linux-x86-64.so.2 (0x00007efd01690000)

動態載入函式庫

動態載入函式庫(dynamically loaded library)就類似 Windows 的 dll 檔,是等到程式真正要用到時才會載入函式庫,其實作方式是透過 DL 函式庫配合一般的共享函式庫來處理的,以下是將之前製作的 libsum.so.1 共享函式庫改為動態載入的範例。

#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
int main(int argc, char **argv) {
  void *handle;
  double (*sum)(double, double);
  char *error;

  // 動態開啟共享函式庫
  handle = dlopen ("libsum.so.1", RTLD_LAZY);
  if (!handle) {
    fputs (dlerror(), stderr);
    exit(1);
  }

  // 取得 sum 函數的位址
  sum = dlsym(handle, "sum");
  if ((error = dlerror()) != NULL)  {
    fputs(error, stderr);
    exit(1);
  }

  // 使用 sum 函數
  double a = 2.6, b = 4.2, c;
  c = sum(a, b);
  printf("%.1f + %.1f = %.1f\n", a, b, c);

  // 關閉共享函式庫
  dlclose(handle);
  return 0;
}

將這段程式碼儲存為 main_dl.c 之後,按照一般的方式進行編譯:

gcc main_dl.c -ldl -o main_dl

在使用上跟共享函式庫差不多,一樣要指定 LD_LIBRARY_PATH

LD_LIBRARY_PATH=. ./main_dl
2.6 + 4.2 = 6.8

表面上看起來跟一般的共享函式庫類似,不過這種動態載入函式庫方式與共享函式庫有很大的不同,共享函式庫是在程式一開始執行時就要載入(不管實際上有沒有使用到),而動態載入函式庫的做法則是可以在真正需要用到時才載入(如果沒用到就可以不需要載入)。

參考資料:René NyffeneggerTLDPMURMURING