【C/C++】插件機制:基于工廠函數的動態插件加載

本文介紹了如何通過 C++ 的 工廠函數動態庫(.so 文件)和 dlopen / dlsym 實現插件機制。這個機制允許程序在運行時動態加載和調用插件,而無需在編譯時知道插件的具體類型。


一、 動態插件機制

在現代 C++ 中,插件機制廣泛應用于需要擴展或靈活配置的場景,如:

  • 策略模式:根據需求動態選擇不同策略。

  • 插件化系統:如游戲引擎、服務器系統等,通過動態加載不同功能模塊。

通過使用 動態庫(.so 文件)和 工廠函數,可以實現插件的動態創建和管理。


二、插件機制核心流程

1. 創建插件接口(基類)

定義一個基類 PluginBase,所有插件都繼承自它,實現自己的功能。

// plugin.hpp
class PluginBase {
public:virtual void run() = 0;  // 純虛函數virtual ~PluginBase() {}
};

2. 實現具體插件

每個插件類實現 PluginBase 接口,并提供自己的功能。

#include "plugin.hpp"class PluginA : public PluginBase {
public:void run() override {std::cout << "PluginA is running!" << std::endl;}
};// 工廠函數
extern "C" PluginBase* create_plugin_0() {return new PluginA();
}

編譯命令:

g++ -fPIC -shared plugin_0.cpp plugin_1.cpp -o libplugin.so

將plugin_*.cpp編譯為動態庫:libplugin.so

3. 主程序加載插件

主程序通過 dlopen 加載動態庫,通過 dlsym 查找工廠函數,調用工廠函數創建插件對象,并執行其方法。

// main.cpp
#include <iostream>
#include <dlfcn.h>
#include "plugin.hpp"int main() {void* handle = dlopen("./libplugin.so", RTLD_LAZY);  // 打開動態庫if (!handle) {std::cerr << "? Failed to open plugin: " << dlerror() << std::endl;return -1;}typedef PluginBase* (*CreateFunc)();  // 定義工廠函數指針類型// 查找兩個插件的工廠函數CreateFunc create_plugin_0 = (CreateFunc)dlsym(handle, "create_plugin_0");if (!create_plugin_0) {std::cerr << "? Failed to find symbol: " << dlerror() << std::endl;dlclose(handle);return -1;}CreateFunc create_plugin_1 = (CreateFunc)dlsym(handle, "create_plugin_1");if (!create_plugin_1) {std::cerr << "? Failed to find symbol: " << dlerror() << std::endl;dlclose(handle);return -1;}// 調用工廠函數創建插件對象PluginBase* plugin_0 = create_plugin_0();plugin_0->run();  // 執行插件功能PluginBase* plugin_1 = create_plugin_1();plugin_1->run();  // 執行插件功能// 釋放資源delete plugin_0;delete plugin_1;dlclose(handle);  // 關閉動態庫return 0;
}

編譯命令:

g++ main.cpp -o main -ldl

鏈接libdl.so動態庫,dlopen、dlsym 這類函數屬于 Linux 系統的動態鏈接庫(libdl)


三、核心概念解析

1. typedef 和 函數指針

通過 typedef 為函數指針起別名,使得函數指針的聲明更加簡潔易讀。

typedef PluginBase* (*CreateFunc)();
  • CreateFunc 現在是指向無參、返回 PluginBase* 的函數指針類型。

  • 它允許我們用簡單的名字表示工廠函數類型。

2. dlopendlsym

  • dlopen:打開動態庫并返回一個句柄,程序可以通過該句柄加載庫中的函數。

  • dlsym:根據符號名稱在動態庫中查找對應的函數地址。

這些函數屬于 POSIX 標準,提供了 運行時加載和調用動態庫的能力

3. 工廠函數

工廠函數是動態庫中暴露給主程序的接口,負責創建插件對象實例。通過 extern "C" 來確保該函數不進行 C++ 名字修飾,從而避免不同編譯器或鏈接時產生不同的符號名稱。

extern "C" PluginBase* create_plugin_0() {return new PluginA();
}

5. dlsym 返回值和強制類型轉換

CreateFunc create_plugin_0 = (CreateFunc)dlsym(handle, "create_plugin_0");
  • dlsym 返回 void* 類型,表示它是一個通用的指針。void* 是一個 不帶類型信息的指針,它可以指向任何類型的對象或函數。

  • 通過 強制轉換 (CreateFunc),將 void* 轉換為我們預定義的 函數指針類型 CreateFunc
    這樣,通過 create_plugin_0() 等工廠函數返回的插件對象指針,可以調用其定義的 run 等方法。


四、 完整的工作流程

步驟描述
1. 插件開發定義基類接口 PluginBase,實現具體插件類,編寫工廠函數。
2. 編譯插件使用 g++ 編譯源文件為動態庫 .so 文件。
3. 主程序主程序使用 dlopen 加載動態庫,dlsym 查找工廠函數,創建插件對象。
4. 執行功能通過插件對象調用具體功能(如 run())。
5. 釋放資源刪除插件對象,關閉動態庫。

五、完整demo

  1. plugin.hpp
// plugin.hpp
#ifndef PLUGIN_HPP
#define PLUGIN_HPPclass PluginBase {
public:virtual void run() = 0;virtual ~PluginBase() {}
};#endif
  1. plugin_0.cpp
// plugin.cpp
#include <iostream>
#include "plugin.hpp"// 派生類
class MyPlugin_0 : public PluginBase {
public:void run() override {std::cout << "🚀 MyPlugin_0 is running!" << std::endl;}
};// 工廠函數,必須用 C 接口導出,防止 C++ 名字修飾
extern "C" PluginBase* create_plugin_0() {return new MyPlugin_0();
}
  1. plugin_1.cpp
// plugin_1.cpp
#include <iostream>
#include "plugin.hpp"// 派生類
class MyPlugin_1 : public PluginBase {
public:void run() override {std::cout << "🚀 MyPlugin_1 is running!" << std::endl;}
};// 工廠函數,必須用 C 接口導出,防止 C++ 名字修飾
extern "C" PluginBase* create_plugin_1() {return new MyPlugin_1();
}
  1. main.cpp
// main.cpp
#include <iostream>
#include <dlfcn.h>
#include "plugin.hpp"int main() {// 1. 打開動態庫void* handle = dlopen("./libplugin.so", RTLD_LAZY);if (!handle) {std::cerr << "? Failed to open plugin: " << dlerror() << std::endl;return -1;}// 2. 查找工廠函數,先拿到目標函數指針,再基于這個指針創建對象。typedef PluginBase* (*CreateFunc)();  /*  函數指針語法結構:返回類型 (*指針名)(參數列表);typedef 原類型 新類型名;CreateFunc定義了一個指向“PluginBase* (*CreateFunc)()”此類函數的函數指針的別名*/CreateFunc create_plugin_0 = (CreateFunc)dlsym(handle, "create_plugin_0");// dlsym(handle, "create_plugin_0")返回的是一個 void* 也就是一個空句柄,將這個句柄強制轉換為CreateFunc// create_plugin_0即為目標函數(工廠函數)句柄,通過create_plugin_0()即可完成調用。if (!create_plugin_0) {std::cerr << "? Failed to find symbol: " << dlerror() << std::endl;dlclose(handle);return -1;}CreateFunc create_plugin_1 = (CreateFunc)dlsym(handle, "create_plugin_1");if (!create_plugin_1) {std::cerr << "? Failed to find symbol: " << dlerror() << std::endl;dlclose(handle);return -1;}// 3. 創建插件對象并調用PluginBase* plugin_0 = create_plugin_0(); // 將函數指針PluginBase*指向具體的create_plugin_0()plugin_0->run();PluginBase* plugin_1 = create_plugin_1();plugin_1->run();// 4. 釋放資源delete plugin_0;delete plugin_1;dlclose(handle);return 0;
}
  1. build.sh
g++ -fPIC -shared plugin_0.cpp plugin_1.cpp -o libplugin.so # 將plugin_*.cpp編譯為動態庫:libplugin.so 
g++ main.cpp -o main -ldl # 鏈接libdl.so動態庫,dlopen、dlsym 這類函數屬于 Linux 系統的動態鏈接庫(libdl)
echo "Build complete!"# 執行:
# 賦予腳本文件執行權限:chmod +x build.sh 
# 執行編譯腳本./build.sh 
# 運行 ./main

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/80216.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/80216.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/80216.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【音視頻】AAC-ADTS分析

AAC-ADTS 格式分析 AAC?頻格式&#xff1a;Advanced Audio Coding(?級?頻解碼)&#xff0c;是?種由MPEG-4標準定義的有損?頻壓縮格式&#xff0c;由Fraunhofer發展&#xff0c;Dolby, Sony和AT&T是主 要的貢獻者。 ADIF&#xff1a;Audio Data Interchange Format ?…

機器學習 Day12 集成學習簡單介紹

1.集成學習概述 1.1. 什么是集成學習 集成學習是一種通過組合多個模型來提高預測性能的機器學習方法。它類似于&#xff1a; 超級個體 vs 弱者聯盟 單個復雜模型(如9次多項式函數)可能能力過強但容易過擬合 組合多個簡單模型(如一堆1次函數)可以增強能力而不易過擬合 集成…

通過爬蟲方式實現頭條號發布視頻(2025年4月)

1、將真實的cookie貼到代碼目錄中toutiaohao_cookie.txt文件里,修改python代碼里的user_agent和video_path, cover_path等變量的值,最后運行python腳本即可; 2、運行之前根據import提示安裝一些常見依賴,比如requests等; 3、2025年4月份最新版; 代碼如下: import js…

Linux ssh免密登陸設置

使用 ssh-copy-id 命令來設置 SSH 免密登錄&#xff0c;并確保所有相關文件和目錄權限正確設置&#xff0c;可以按照以下步驟進行&#xff1a; 步驟 1&#xff1a;在源服務器&#xff08;198.120.1.109&#xff09;生成 SSH 密鑰對 如果還沒有生成 SSH 密鑰對&#xff0c;首先…

《讓機器人讀懂你的心:情感分析技術融合奧秘》

機器人早已不再局限于執行簡單機械的任務&#xff0c;人們期望它們能像人類伙伴一樣&#xff0c;理解我們的喜怒哀樂&#xff0c;實現更自然、溫暖的互動。情感分析技術&#xff0c;正是賦予機器人這種“理解人類情緒”能力的關鍵鑰匙&#xff0c;它的融入將徹底革新機器人與人…

Linux筆記---進程間通信:匿名管道

1. 管道通信 1.1 管道的概念與分類 管道&#xff08;Pipe&#xff09; 是進程間通信&#xff08;IPC&#xff09;的一種基礎機制&#xff0c;主要用于在具有親緣關系的進程&#xff08;如父子進程、兄弟進程&#xff09;之間傳遞數據&#xff0c;其核心特性是通過內核緩沖區實…

Ollama API 應用指南

1. 基礎信息 默認地址: http://localhost:11434/api數據格式: application/json支持方法: POST&#xff08;主要&#xff09;、GET&#xff08;部分接口&#xff09; 2. 模型管理 API (1) 列出本地模型 端點: GET /api/tags功能: 獲取已下載的模型列表。示例:curl http://lo…

【OSCP-vulnhub】Raven-2

目錄 端口掃描 本地/etc/hosts文件解析 目錄掃描&#xff1a; 第一個flag 利用msf下載exp flag2 flag3 Mysql登錄 查看mysql的運行權限 MySql提權&#xff1a;UDF 查看數據庫寫入條件 查看插件目錄 查看是否可以遠程登錄 gcc編譯.o文件 創建so文件 創建臨時監聽…

Podman Desktop:現代輕量容器管理利器(Podman與Docker)

前言 什么是 Podman Desktop&#xff1f; Podman Desktop 是基于 Podman CLI 的圖形化開源容器管理工具&#xff0c;運行在 Windows&#xff08;或 macOS&#xff09;上&#xff0c;默認集成 Fedora Linux&#xff08;WSL 2 環境&#xff09;。它提供與 Docker 類似的使用體驗…

極狐GitLab 權限和角色如何設置?

極狐GitLab 是 GitLab 在中國的發行版&#xff0c;關于中文參考文檔和資料有&#xff1a; 極狐GitLab 中文文檔極狐GitLab 中文論壇極狐GitLab 官網 權限和角色 (BASIC ALL) 將用戶添加到項目或群組時&#xff0c;您可以為他們分配角色。該角色決定他們在極狐GitLab 中可以執…

解鎖現代生活健康密碼,開啟養生新方式

在科技飛速發展的當下&#xff0c;我們享受著便捷生活&#xff0c;卻也面臨諸多健康隱患。想要維持良好狀態&#xff0c;不妨從這些細節入手&#xff0c;解鎖科學養生之道。? 腸道是人體重要的消化器官&#xff0c;也是最大的免疫器官&#xff0c;養護腸道至關重要。日常可多…

Kafka 主題設計與數據接入機制

一、前言&#xff1a;萬物皆流&#xff0c;Kafka 是入口 在構建實時數倉時&#xff0c;Kafka 既是 數據流動的起點&#xff0c;也是后續流處理系統&#xff08;如 Flink&#xff09;賴以為生的數據源。 但“消息進來了” ≠ “你就能處理好了”——不合理的 Topic 設計、接入方…

【繪制圖像輪廓|凸包特征檢測】圖像處理(OpenCV) -part7

15 繪制圖像輪廓 15.1 什么是輪廓 輪廓是一系列相連的點組成的曲線&#xff0c;代表了物體的基本外形。相對于邊緣&#xff0c;輪廓是連續的&#xff0c;邊緣不一定連續&#xff0c;如下圖所示。輪廓是一個閉合的、封閉的形狀。 輪廓的作用&#xff1a; 形狀分析 目標識別 …

uniapp中使用<cover-view>標簽

文章背景&#xff1a; uniapp中遇到了原生組件(canvas)優先級過高覆蓋vant組件 解決辦法&#xff1a; 使用<cover-view>標簽 踩坑&#xff1a; 我想實現的是一個vant組件庫中動作面板的效果&#xff0c;能夠從底部彈出框&#xff0c;讓用戶進行選擇&#xff0c;我直…

Kafka常見問題及解決方案

Kafka 是一個強大的分布式流處理平臺&#xff0c;廣泛用于高吞吐量的數據流處理&#xff0c;但在實際使用過程中&#xff0c;也會遇到一些常見問題。以下是一些常見的 Kafka 問題及其對應的解決辦法的詳細解答&#xff1a; 消息丟失 一、原因 1.生產端 網絡故障、生產者超時…

leetcode 二分查找應用

34. Find First and Last Position of Element in Sorted Array 代碼&#xff1a; class Solution { public:vector<int> searchRange(vector<int>& nums, int target) {int low lowwer_bound(nums,target);int high upper_bound(nums,target);if(low high…

【Docker】在容器中使用 NVIDIA GPU

解決容器 GPU 設備映射問題&#xff0c;實現 AI 應用加速 &#x1f517; 官方文檔&#xff1a;NVIDIA Container Toolkit GitHub 常見錯誤排查 若在運行測試容器時遇到以下錯誤&#xff1a; docker: Error response from daemon: could not select device driver ""…

通過Quartus II實現Nios II編程

目錄 一、認識Nios II二、使用Quartus II 18.0Lite搭建Nios II硬件部分三、軟件部分四、運行項目 一、認識Nios II Nios II軟核處理器簡介 Nios II是Altera公司推出的一款32位RISC嵌入式處理器&#xff0c;專門設計用于在FPGA上運行。作為軟核處理器&#xff0c;Nios II可以通…

JAVA設計模式——(三)橋接模式

JAVA設計模式——&#xff08;三&#xff09;橋接模式&#xff08;Bridge Pattern&#xff09; 介紹理解實現武器抽象類武器實現類涂裝顏色的行為接口具體顏色的行為實現讓行為影響武器修改武器抽象類修改實現類 測試 適用性 介紹 將抽象和實現解耦&#xff0c;使兩者可以獨立…

k8s 證書相關問題

1.重新生成新證書 kubeadm init phase certs apiserver-etcd-client --config ~/kubeadm.yaml這個命令表示生成 kube-apiserver 連接 etcd 使用的證書,生成后如下 -rw------- 1 root root 1.7K Apr 23 16:35 apiserver-etcd-client.key -rw-r--r-- 1 root root 1.2K Apr 23 …