溫馨提示×

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

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

C語(yǔ)言的庫(kù)封裝發(fā)布技術(shù)

發(fā)布時(shí)間:2021-08-24 17:16:20 來源:億速云 閱讀:121 作者:chen 欄目:開發(fā)技術(shù)

本篇內(nèi)容介紹了“C語(yǔ)言的庫(kù)封裝發(fā)布技術(shù)”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

目錄
  • 1. C動(dòng)態(tài)鏈接庫(kù)是一種即成標(biāo)準(zhǔn)

  • 2. 用C++制作C的庫(kù)

    • 2.1 使用void * 作為句柄

    • 2.2 導(dǎo)出這些方法

  • 3. 使用庫(kù)

    • 4. 經(jīng)典的范例:libuhd

      每年實(shí)驗(yàn)課,總有同學(xué)問我,如何生成DLL、如何導(dǎo)出類,如何不花很多時(shí)間精力,就設(shè)計(jì)出一個(gè)給別人用的爽的功能庫(kù)呢?結(jié)合這些年的實(shí)踐,我們今天就來聊一聊動(dòng)態(tài)鏈接庫(kù)的封裝發(fā)布。您也可以直接跳到文章最后,去github查看C++/C混合庫(kù)的經(jīng)典案例——Ettus uhd

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

      • 不要導(dǎo)出類、不要導(dǎo)出變量,僅使用C基礎(chǔ)數(shù)據(jù)類型。

      • 面向?qū)ο髮?shí)現(xiàn)功能真香,實(shí)現(xiàn)接口真要命。

      • 用最棒的語(yǔ)言實(shí)現(xiàn)功能,遵循C語(yǔ)言標(biāo)準(zhǔn)實(shí)現(xiàn)接口。

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

      做到了這幾點(diǎn),即使用戶從VC2010換成了python,庫(kù)都不用改。究其原因,C++的類在二進(jìn)制結(jié)構(gòu)上是缺乏定義的,一個(gè)返回值用了std::string,或者參數(shù)用了CTime的方法,從VC2010導(dǎo)出的DLL到了VC2017就不一定能用,更別提其他編譯器和語(yǔ)言了。

      1. C動(dòng)態(tài)鏈接庫(kù)是一種即成標(biāo)準(zhǔn)

      C語(yǔ)言是一門古老的語(yǔ)言。從六七十年代開始,在Unix/Linux操作系統(tǒng)上,C語(yǔ)言實(shí)現(xiàn)了大量的庫(kù),幾乎涵蓋了當(dāng)代科學(xué)涉及的所有領(lǐng)域。從基礎(chǔ)的XML操作,到復(fù)雜的數(shù)學(xué)算法,都能找到對(duì)應(yīng)的C庫(kù)。C語(yǔ)言的動(dòng)態(tài)鏈接庫(kù)承載了太多的智力遺產(chǎn),以至于后來的大部分語(yǔ)言都自覺的加入了享用既有C語(yǔ)言動(dòng)態(tài)鏈接庫(kù)的能力。

      這種情況使得符合C語(yǔ)言習(xí)慣的動(dòng)態(tài)鏈接庫(kù)接口1成為了一種即成實(shí)事,不同的語(yǔ)言之間,使用C動(dòng)態(tài)鏈接庫(kù)的標(biāo)準(zhǔn)交互。盡管這種接口是面向過程的,而可用的參數(shù)類型少的可憐,但其簡(jiǎn)單、直接,又有大量的歷史資源,使得后來的CORBA、COM也無(wú)法取代這種底層的接口方式2。

      這里有幾個(gè)概念需要明確:

      • C接口的動(dòng)態(tài)鏈接庫(kù)的通用性,一般只和操作系統(tǒng)、運(yùn)行時(shí)(32位還是64位)有關(guān),和具體的編譯器、語(yǔ)言無(wú)關(guān)。

      • 很多現(xiàn)代編程語(yǔ)言能調(diào)用C接口的動(dòng)態(tài)鏈接庫(kù)。

      • 部分現(xiàn)代編程語(yǔ)言能生成C接口的動(dòng)態(tài)鏈接庫(kù)。

      無(wú)論你使用什么語(yǔ)言開發(fā)功能,只要提供了符合C語(yǔ)言動(dòng)態(tài)鏈接庫(kù)結(jié)構(gòu)的接口,許多其他語(yǔ)言就可以使用你的功能。因此,完全可以用C++語(yǔ)言實(shí)現(xiàn)一個(gè)C接口的庫(kù),在里面盡情使用STL。

      C語(yǔ)言的庫(kù)封裝發(fā)布技術(shù)

      2. 用C++制作C的庫(kù)

      用C++做C的庫(kù),關(guān)鍵是用好句柄。

      什么是“句柄(Handle)”?這是個(gè)翻譯問題。你可以理解為“把手”或者“提手”更合適。句柄很多時(shí)候是一個(gè)整數(shù),用于標(biāo)記一堆運(yùn)行時(shí)資源,實(shí)現(xiàn)操作動(dòng)態(tài)庫(kù)功能的目的。

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

      C語(yǔ)言的庫(kù)封裝發(fā)布技術(shù)

      不難想像,可以同時(shí)申請(qǐng)多個(gè)箱子,交給不同的線程去執(zhí)行。庫(kù)的設(shè)計(jì)者要確保Handle標(biāo)記的參數(shù)包之間是獨(dú)立的、線程安全的。

      同時(shí),句柄本身可以復(fù)刻面向?qū)ο蟮牟糠止δ?。如果把Handle作為this指針看待,則C++類可以直接導(dǎo)出為C的函數(shù)。只是首個(gè)參數(shù)要傳入Handle即可。

      2.1 使用void * 作為句柄

      舉個(gè)例子,假設(shè)手頭有一個(gè)實(shí)現(xiàn)字符串查找的類,需要向外發(fā)布功能。但這個(gè)類是C++的,類似:

      //關(guān)鍵詞查找器類
      class Findfoo
      {
      public:
      	Findfoo(const std::string & task = "foo");
      	~Findfoo();
      public:
      	void setTask(const std::string & task);
      	const std::string &  task() const;
      	//在rawStr里查找關(guān)鍵詞
      	long long Find(const std::string & rawStr);
      private:
      	//用于匹配的關(guān)鍵詞
      	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);
      }

      此時(shí),可以設(shè)置以下接口,把C++的類變成C的方法。一旦變?yōu)镃的方法,外部就無(wú)需知道該類的存在。

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

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

      上面的例子僅有1個(gè)類作為演示。實(shí)際開發(fā)中,一個(gè)工作可能由好幾個(gè)類的實(shí)例共同協(xié)作完成??梢杂靡粋€(gè)std::map<long long, XXX>來管理各個(gè)實(shí)例,也可以把實(shí)例全部放在一個(gè)struct中。如果用std::map,切記多線程下的mutex一致性保護(hù),防止用戶同時(shí)在多個(gè)線程init好幾組功能實(shí)例,導(dǎo)致std::map崩潰。

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

      2.2 導(dǎo)出這些方法

      上述函數(shù),因?yàn)槭荂++函數(shù),編譯器會(huì)對(duì)其進(jìn)行改名,把參數(shù)也放進(jìn)去,以便支持多態(tài)(同一個(gè)函數(shù)名,不同參數(shù))。要導(dǎo)出為C的函數(shù),就不允許編譯器改名字。要用“extern ‘C‘”進(jìn)行包裝,以便導(dǎo)出這些方法時(shí),函數(shù)名不變。

      同時(shí),在Windows下,函數(shù)存在多個(gè)參數(shù)時(shí),棧內(nèi)的參數(shù)順序也有從左開始還是從右壓棧的區(qū)別。要做到最大的適應(yīng)性,需要指定 stdcall開關(guān)。

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

      具體:

      建立一個(gè)頭文件,叫做findfoo_global.h。這個(gè)頭文件對(duì)Linux和windows平臺(tái)定義一些宏,用于聲明函數(shù)時(shí),指定導(dǎo)出(構(gòu)造DLL本身)和導(dǎo)入(使用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宏,這樣,就開啟了導(dǎo)出開關(guān)。

      建立頭文件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

      實(shí)現(xiàn)導(dǎo)出方法 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. 使用庫(kù)

      一旦導(dǎo)出了上述方法,即可使用庫(kù)。

      #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;
      }

      上述是最簡(jiǎn)單的例子。當(dāng)需要處理大量動(dòng)態(tài)內(nèi)存時(shí),需要注意:內(nèi)存誰(shuí)申請(qǐng),誰(shuí)釋放。這一點(diǎn)特別容易引起錯(cuò)誤。

      4. 經(jīng)典的范例:libuhd

      USRP軟件無(wú)線電平臺(tái)對(duì)應(yīng)的開源庫(kù)libuhd是用C++ boost開發(fā)的。但是,為了兼容更多的語(yǔ)言,其進(jìn)行了封裝,把各個(gè)類都用句柄抽象出來了,且是標(biāo)準(zhǔn)C的接口。

      可以去Github的工程頁(yè)簽出項(xiàng)目查看,也可以跟蹤代碼查看其原理。

      這個(gè)項(xiàng)目是把C、C++的聯(lián)合運(yùn)用發(fā)揮的非常棒的例子。

      1包括函數(shù)入口點(diǎn)的定位方式、函數(shù)命名方式、參數(shù)傳遞規(guī)則、參數(shù)類型。

      2.COM和C接口DLL其實(shí)不是一個(gè)范疇的東西,這里放在一起,有點(diǎn)粗暴。

      “C語(yǔ)言的庫(kù)封裝發(fā)布技術(shù)”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

      向AI問一下細(xì)節(jié)

      免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

      AI