一個Go和C++多用途工程項目的模型研究

語言: CN / TW / HK

持續創作,加速成長!這是我參與「掘金日新計劃 · 6 月更文挑戰」的第4天,點擊查看活動詳情

本文探討一個使用Go語言和C++語言實現的多用途工程項目的模型,該工程可適用於一些實際工作環境,且能提高開發效率,降低維護成本。

問題提出

筆者負責的一個工程的測試程序,需要有交互環境,類似於 Linux 命令行那樣,比如修改參數A執行一次測試,修改參數B執行一次測試,這類針對測試的使用,用命令行的方式是最快捷的,因為不需要重新初始化。除此外,也會做一些核算驗證工作,但不需要交互,單獨執行即可,比如執行一次全量數據的核算,需要耗時1小時(即使是32核心/64GB內存服務器亦要如此久),則需在後台運行,而不能用命令行方式,如果網絡斷開則會前功盡棄。另外還需有動態庫以提供其它程序使用,當然,“其它程序”亦由筆者負責。

綜上,這個工程最終要產生三種文件:兩個可執行程序文件(包括命令行版和單獨版)以及一個動態庫文件。為能夠複用代碼,減少維護成本——主要是指筆者的維護成本,做到模塊化但又相對獨立,需從較高層面考慮整體的架構。經較長一段時間的探索,以目前的技術水平,以目前的實踐經驗,提出本文所述之模型。

層架圖

層架圖如下圖所示。

image-20220304104419330.png

縱向看,可執行文件foobar和foobar_allone分別有不同的執行流程,而foobar_allone可以認為是動態庫libfoobar.so的整合調用。

橫向看,雖然都有初始化、運行、退出等主要流程,但或多或少有差異。最終執行到的函數,則在全局命令結構體中,在該結構體中,定義了不同的命令名稱及對應的執行函數。

從結果上看,不管哪一種可執行文件形式,真正執行操作的是命令結構體中的函數,只是過程稍有不同而已,最終殊途同歸。

開發相關

上述層架圖涉及的內容,均在同一個工程中,使用不同的適當數量的目錄,不同前綴的文件名稱,這樣做方便項目管理。編碼風格上,使用不同風格的函數以示區別。對外提供的函數,使用大小寫形式,如FoobarInit,而內部函數,一般使用小寫及下劃線形式,比如實際命令的執行函數一般為do_xx

工程概述

  • 命令行版本適用於交互場合;單獨版本適用於單獨運行場合。
  • 不同形式程序,最終本質還是調用不同命令對應的函數。
  • 網頁版本由go語言加載so庫,再調用FoobarXX函數。

整個工程使用 Makefile 編譯,對外輸出文件為foobar、foobar_allone、libfoobar.so,其中前兩者內容完全一致,通過文件名稱實現不同的程序功能。該功能實現不復雜,即在 main 函數中根據運行程序的名稱,從而調用不同的模塊入口函數,以往文章有涉及,有興趣可自行查閲。

命令交互的實現,沿用筆者之前實現的模塊代碼,也有相應的文章。命令結構體定義示例如下:

cmd_tbl_t my_cmd_table[] = {   {"help", CONFIG_SYS_MAXARGS, do_help_default, "print help info."},   {"?", CONFIG_SYS_MAXARGS, do_help_default, "print help info."},   {"exit", 1, do_exit, "quit program"},   {"quit", 1, do_exit, "quit program"},   {"show", 1, do_show, "show param"}, };

其中,命令名稱與實現函數一一對應,如 exit 命令,對應實現函數為do_exit

設計説明

由於功能的實現體現在命令中,因為能夠在一定程度上解耦,要新加功能,直接添加命令即可。

設置全局參數,gConfig結構體,參數可以在命令行中修改。

設置日誌打印標誌 showlog,根據等級不同打印不同日誌。

單獨版本支持多命令輸入,與命令行版本相似。以下2種示例,效果完全一樣。

``` 命令版本: ./foobar

set showlog=1 test ​ 單獨版本: ./foobar_allone "set showlog=1; test" ```

動態庫:

```

ifdef __cplusplus

extern "C" {

endif

​ typedef struct InParam_s{   char logstr; } InParam; ​ typedef struct OutParam_s{   char logstr; } OutParam; ​ int CalFeeRun(InParam inparam, OutParam outparam); ​

ifdef __cplusplus

}

endif

```

Go 和 C++ 交互

交互方式

筆者除了在終端執行外,還需要用網頁做可視化,這樣方便給領導展示,也可給其它同事使用。Go 實現 web 服務比較方便,有很多現成的庫,筆者實際使用 gin 框架。但底層用到的庫還是C++,這樣就涉及兩種語言的交互了。

前面已經提供了動態庫so文件,動態庫是C++語言編寫,但 Go 只支持 C 語言,因此要解決在 Go 中如何使用 C 封裝動態庫的調用。

在數據傳輸上,設計成只有字符串形式而不是結構體。實現簡單,能自由定製內容。

具體看,使用C.CString(instr)將 Go 的字符串轉換成 C 語言字符串,再調用。調用結束後,將返回字符串用outstr = C.GoString(outParam.logstr)轉換成 Go 字符串。

// 結構體指針,傳入傳出 int CalFeeRun(InParam* inparam, OutParam* outparam) {   typedef int (*ptr)(InParam*, OutParam*); ​   ptr fptr = (ptr)dlsym(g_sohandle, "CalFeeRun");       return (*fptr)(inparam, outparam); }

調用示例:

var inParam C.InParam var outParam C.OutParam ​ inParam.logstr = C.CString(instr) ​ ret := C.CalFeeRun(&inParam, &outParam) if ret < 0 { klog.Printf("run cal cmd failed\n") return outstr } ​ outstr = C.GoString(outParam.logstr) ​ // 傳出參數為靜態緩衝區,不在這裏釋放 defer C.free(unsafe.Pointer(inParam.logstr)) ​

數據傳遞

有時數據量較大,可能會溢出或者不完整,前者,可限制固定大小的緩衝區,後者,則可使用crc32或其它方式校驗數據。由於較簡單,因此略去不談。

小結

本文根據筆者需求,提出一種方案,不涉及具體的代碼實現。從實際實施效果看,還是不錯的,當然,因為所有這些工程均是筆者一人維護,是否經得起考驗,那是後來的事了。