91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

C語言的庫封裝發布技術

發布時間:2021-08-24 17:16:20 來源:億速云 閱讀:122 作者:chen 欄目:開發技術

本篇內容介紹了“C語言的庫封裝發布技術”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

目錄
  • 1. C動態鏈接庫是一種即成標準

  • 2. 用C++制作C的庫

    • 2.1 使用void * 作為句柄

    • 2.2 導出這些方法

  • 3. 使用庫

    • 4. 經典的范例:libuhd

      每年實驗課,總有同學問我,如何生成DLL、如何導出類,如何不花很多時間精力,就設計出一個給別人用的爽的功能庫呢?結合這些年的實踐,我們今天就來聊一聊動態鏈接庫的封裝發布。您也可以直接跳到文章最后,去github查看C++/C混合庫的經典案例——Ettus uhd

      要讓自己的庫好用,又通用,該怎么辦?重要的事情說前面:

      • 不要導出類、不要導出變量,僅使用C基礎數據類型。

      • 面向對象實現功能真香,實現接口真要命。

      • 用最棒的語言實現功能,遵循C語言標準實現接口。

      • 非密集吞吐的接口,可以使用json整體交互。密集吞吐,用內存。

      做到了這幾點,即使用戶從VC2010換成了python,庫都不用改。究其原因,C++的類在二進制結構上是缺乏定義的,一個返回值用了std::string,或者參數用了CTime的方法,從VC2010導出的DLL到了VC2017就不一定能用,更別提其他編譯器和語言了。

      1. C動態鏈接庫是一種即成標準

      C語言是一門古老的語言。從六七十年代開始,在Unix/Linux操作系統上,C語言實現了大量的庫,幾乎涵蓋了當代科學涉及的所有領域。從基礎的XML操作,到復雜的數學算法,都能找到對應的C庫。C語言的動態鏈接庫承載了太多的智力遺產,以至于后來的大部分語言都自覺的加入了享用既有C語言動態鏈接庫的能力。

      這種情況使得符合C語言習慣的動態鏈接庫接口1成為了一種即成實事,不同的語言之間,使用C動態鏈接庫的標準交互。盡管這種接口是面向過程的,而可用的參數類型少的可憐,但其簡單、直接,又有大量的歷史資源,使得后來的CORBA、COM也無法取代這種底層的接口方式2。

      這里有幾個概念需要明確:

      • C接口的動態鏈接庫的通用性,一般只和操作系統、運行時(32位還是64位)有關,和具體的編譯器、語言無關。

      • 很多現代編程語言能調用C接口的動態鏈接庫。

      • 部分現代編程語言能生成C接口的動態鏈接庫。

      無論你使用什么語言開發功能,只要提供了符合C語言動態鏈接庫結構的接口,許多其他語言就可以使用你的功能。因此,完全可以用C++語言實現一個C接口的庫,在里面盡情使用STL。

      C語言的庫封裝發布技術

      2. 用C++制作C的庫

      用C++做C的庫,關鍵是用好句柄。

      什么是“句柄(Handle)”?這是個翻譯問題。你可以理解為“把手”或者“提手”更合適。句柄很多時候是一個整數,用于標記一堆運行時資源,實現操作動態庫功能的目的。

      對一個復雜的功能來說,需要很多運行時的參數來支撐。比如FFT,就需要有一個內存區域記錄蝶形運算的單元,以及指向各層單元的索引。對通信中的糾錯譯碼,需要一些內存區域記憶寄存器,以及當前的狀態。所有上述這些狀態,都可以用一個struct 包裹起來,形成一個“箱子”。這個箱子對用戶是透明的,只需要把箱子的把手(Handle)交給用戶手上,用戶在需要的時候,交回箱子并執行任務。

      C語言的庫封裝發布技術

      不難想像,可以同時申請多個箱子,交給不同的線程去執行。庫的設計者要確保Handle標記的參數包之間是獨立的、線程安全的。

      同時,句柄本身可以復刻面向對象的部分功能。如果把Handle作為this指針看待,則C++類可以直接導出為C的函數。只是首個參數要傳入Handle即可。

      2.1 使用void * 作為句柄

      舉個例子,假設手頭有一個實現字符串查找的類,需要向外發布功能。但這個類是C++的,類似:

      //關鍵詞查找器類
      class Findfoo
      {
      public:
      	Findfoo(const std::string & task = "foo");
      	~Findfoo();
      public:
      	void setTask(const std::string & task);
      	const std::string &  task() const;
      	//在rawStr里查找關鍵詞
      	long long Find(const std::string & rawStr);
      private:
      	//用于匹配的關鍵詞
      	std::string m_task = "foo";
      };
      Findfoo::Findfoo(const std::string & task)
      	:m_task(task)
      {}
      Findfoo::~Findfoo()
      {}
      void Findfoo::setTask(const std::string & task)
      {
      	m_task = task;
      }
      const std::string &  Findfoo::task() const
      {
      	return m_task;
      }
      long long Findfoo::Find(const std::string & rawStr)
      {
      	return rawStr.find(m_task);
      }

      此時,可以設置以下接口,把C++的類變成C的方法。一旦變為C的方法,外部就無需知道該類的存在。

      //創建一個查找器,返回句柄。提供的是關鍵詞。
      void * ff_init_task(const char * task)
      {
      	Findfoo * f = new Findfoo(task);
      	return (void *) f;
      }
      //重設關鍵詞
      void ff_reset_task(void * h, const char * task)
      {
      	Findfoo * f = (Findfoo *)(h);
      	assert(f);
      	f->setTask(task);
      }
      //獲取當前關鍵詞
      const char * ff_get_task(void * h)
      {
      	Findfoo * f = (Findfoo *)(h);
      	assert(f);
      	return f->task().c_str();
      }
      //用關鍵詞查找rawStr
      long long ff_find(void * h, const char * rawStr)
      {
      	Findfoo * f = (Findfoo *)(h);
      	assert(f);
      	return f->Find(rawStr);
      }
      //刪除當前查找器
      void ff_fini_task(void * h)
      {
      	Findfoo * f = (Findfoo *)(h);
      	if (f)
      		delete f;
      }

      如此操作,用戶可以完全不知道存在Findfoo類,只用一個void *指針作為操作類的指示。

      上面的例子僅有1個類作為演示。實際開發中,一個工作可能由好幾個類的實例共同協作完成。可以用一個std::map<long long, XXX>來管理各個實例,也可以把實例全部放在一個struct中。如果用std::map,切記多線程下的mutex一致性保護,防止用戶同時在多個線程init好幾組功能實例,導致std::map崩潰。

      從性能角度,建議采用struct來承載所有運行時,而后返回指向該struct的指針。

      2.2 導出這些方法

      上述函數,因為是C++函數,編譯器會對其進行改名,把參數也放進去,以便支持多態(同一個函數名,不同參數)。要導出為C的函數,就不允許編譯器改名字。要用“extern ‘C‘”進行包裝,以便導出這些方法時,函數名不變。

      同時,在Windows下,函數存在多個參數時,棧內的參數順序也有從左開始還是從右壓棧的區別。要做到最大的適應性,需要指定 stdcall開關。

      最后,我們不想為生成庫的工程、用戶的工程準備兩套頭文件,故而需要一些瑣碎的宏定義,以區分當前編譯的是DLL本身,還是使用DLL的用戶工程。

      具體:

      建立一個頭文件,叫做findfoo_global.h。這個頭文件對Linux和windows平臺定義一些宏,用于聲明函數時,指定導出(構造DLL本身)和導入(使用DLL)

      #ifndef FINDFOO_GLOBAL_H
      #define FINDFOO_GLOBAL_H
      #if defined(_MSC_VER) || defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
      #  define Q_DECL_EXPORT __declspec(dllexport)
      #  define Q_DECL_IMPORT __declspec(dllimport)
      #  define FOOCALL __stdcall
      #else
      #  define Q_DECL_EXPORT     __attribute__((visibility("default")))
      #  define Q_DECL_IMPORT     __attribute__((visibility("default")))
      #  define FOOCALL
      #endif
      #if defined(FINDFOO_LIBRARY)
      #  define FINDFOO_EXPORT Q_DECL_EXPORT
      #else
      #  define FINDFOO_EXPORT Q_DECL_IMPORT
      #endif
      //句柄就是void *
      #define FFHANDLE void *
      #endif // FINDFOO_GLOBAL_H

      在DLL的工程中,要定義FINDFOO_LIBRARY宏,這樣,就開啟了導出開關。

      建立頭文件findfoo.h

      #ifndef FINDFOO_H
      #define FINDFOO_H
      #include "findfoo_global.h"
      #ifdef __cplusplus
      extern "C"{
      #endif
      FINDFOO_EXPORT FFHANDLE		FOOCALL		ff_init_task	(const char * task);
      FINDFOO_EXPORT void			FOOCALL		ff_reset_task	(FFHANDLE h	, const char * task);
      FINDFOO_EXPORT const char * FOOCALL		ff_get_task		(FFHANDLE h	);
      FINDFOO_EXPORT long long	FOOCALL		ff_find			(FFHANDLE h	, const char * rawStr);
      FINDFOO_EXPORT void			FOOCALL		ff_fini_task	(FFHANDLE h	);
      #ifdef __cplusplus
      }
      #endif
      #endif // FINDFOO_H

      實現導出方法 findfoo.cpp

      #include "findfoo.h"
      #include <assert.h>
      #include <string>
      class Findfoo
      {
      public:
      	Findfoo(const std::string & task = "foo");
      	~Findfoo();
      public:
      	void setTask(const std::string & task);
      	const std::string &  task() const;
      	long long Find(const std::string & rawStr);
      private:
      	std::string m_task = "foo";
      };
      Findfoo::Findfoo(const std::string & task)
      	:m_task(task){}
      Findfoo::~Findfoo(){}
      void Findfoo::setTask(const std::string & task)
      {
      	m_task = task;
      }
      const std::string &  Findfoo::task() const
      {
      	return m_task;
      }
      long long Findfoo::Find(const std::string & rawStr)
      {
      	return rawStr.find(m_task);
      }
      //-----------
      FINDFOO_EXPORT FFHANDLE FOOCALL ff_init_task(const char * task)
      {
      	Findfoo * f = new Findfoo(task);
      	return (FFHANDLE) f;
      }
      FINDFOO_EXPORT void FOOCALL ff_reset_task(FFHANDLE h, const char * task)
      {
      	Findfoo * f = reinterpret_cast<Findfoo *>(h);
      	assert(f);
      	f->setTask(task);
      }
      FINDFOO_EXPORT const char * FOOCALL ff_get_task(FFHANDLE h)
      {
      	Findfoo * f = reinterpret_cast<Findfoo *>(h);
      	assert(f);
      	return f->task().c_str();
      }
      FINDFOO_EXPORT long long FOOCALL ff_find(FFHANDLE h, const char * rawStr)
      {
      	Findfoo * f = reinterpret_cast<Findfoo *>(h);
      	assert(f);
      	return f->Find(rawStr);
      }
      FINDFOO_EXPORT void FOOCALL ff_fini_task(FFHANDLE h)
      {
      	Findfoo * f = reinterpret_cast<Findfoo *>(h);
      	if (f)
      		delete f;
      }

      3. 使用庫

      一旦導出了上述方法,即可使用庫。

      #include <iostream>
      #include <cassert>
      #include "findfoo.h"
      using namespace std;
      int main()
      {
      	FFHANDLE h = ff_init_task("foobar");
      	assert(h);
      	cout << "Task string:" << ff_get_task(h) << endl;
      	cout << "Input String:";
      	std::string strRaw;
      	cin >> strRaw;
      	cout << ff_find(h,strRaw.c_str());
      	//Delete
      	ff_fini_task(h);
      	h = nullptr;
      	return 0;
      }

      上述是最簡單的例子。當需要處理大量動態內存時,需要注意:內存誰申請,誰釋放。這一點特別容易引起錯誤。

      4. 經典的范例:libuhd

      USRP軟件無線電平臺對應的開源庫libuhd是用C++ boost開發的。但是,為了兼容更多的語言,其進行了封裝,把各個類都用句柄抽象出來了,且是標準C的接口。

      可以去Github的工程頁簽出項目查看,也可以跟蹤代碼查看其原理。

      這個項目是把C、C++的聯合運用發揮的非常棒的例子。

      1包括函數入口點的定位方式、函數命名方式、參數傳遞規則、參數類型。

      2.COM和C接口DLL其實不是一個范疇的東西,這里放在一起,有點粗暴。

      “C語言的庫封裝發布技術”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

      向AI問一下細節

      免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

      AI

      台南县| 宝应县| 卢氏县| 安龙县| 永年县| 滦平县| 河东区| 兴海县| 锦屏县| 西平县| 航空| 林芝县| 长葛市| 和林格尔县| 开阳县| 阿尔山市| 苍溪县| 祁门县| 枣阳市| 和硕县| 莱阳市| 大渡口区| 澎湖县| 财经| 旬阳县| 东城区| 东港市| 太原市| 长阳| 昌江| 南部县| 辉县市| 邯郸市| 西乌| 邹城市| 安溪县| 兰溪市| 白水县| 五华县| 广西| 和顺县|