<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          cgo調(diào)用c動態(tài)庫實戰(zhàn)

          共 8022字,需瀏覽 17分鐘

           ·

          2022-07-13 00:40

          這篇從一個需求開始說起。這個需求也很簡單,就是需要為某個硬件加密算法封裝一個接口,接口的邏輯是傳入唯一標(biāo)識UID,能生成加密后的加密串。

          這篇從一個需求開始說起。這個需求也很簡單,就是需要為某個硬件加密算法封裝一個接口,接口的邏輯是傳入唯一標(biāo)識UID,能生成加密后的加密串。

          但是麻煩的是,這個加密算法是另外一個部門使用C開發(fā)的。而Web是Golang開發(fā)的。所以這個情況,自然就想到了用上cgo。

          “硬件部門提供一個c編譯的動態(tài)庫,web服務(wù)方通過cgo來加載動態(tài)庫,調(diào)用動態(tài)庫中的接口,生成加密串?!?/p>

          這樣通過cgo連接的好處:

          1 web開發(fā)方無需了解任何加密算法邏輯,保證了加密算法的安全。

          2 各司其職,硬件部門負(fù)責(zé)算法開發(fā),web部門負(fù)責(zé)封裝實現(xiàn)。

          那怎么使用cgo來加載動態(tài)庫呢?這里記錄一下。

          假設(shè)硬件部門提供的接口如下:

          int Producer(int in_encrypt_method,
                              const unsigned char* in_uid,
                              unsigned int in_uid_len,
                              unsigned char** out_chip_key,
                              unsigned int* out_chip_key_len);

          還有一個動態(tài)庫so文件 ChipSdk.so,和頭文件 chip_key_producer.h。

          而要用上這個動態(tài)庫的接口,我們弄明白兩個事情就可以了:如何加載動態(tài)庫, 和如何轉(zhuǎn)換數(shù)據(jù)結(jié)構(gòu)。

          加載動態(tài)庫

          cgo是go和c的橋梁,它的功能其實很強(qiáng)大,比如,在c中調(diào)用go寫的函數(shù),在go中調(diào)用c寫的函數(shù)。我們這里就只是用了一種:在go中調(diào)用c寫的動態(tài)庫。

          據(jù)我了解,go中調(diào)用c寫的動態(tài)庫至少有兩種辦法。

          第一種,將動態(tài)庫放在lib目錄中,使用C編譯器選項讓go程序能找到這個lib庫和頭文件。

          其中C編譯器選項CFLAGS和LDFLAGS兩個選項。其中CFLAGS標(biāo)識頭文件的路徑。即

          // #cgo CFLAGS: -I/home/jianfengye/chipkey/
          // #include "chip_key_producer.h"

          表示去/home/jianfengye/chipkey/這個目錄加載"chip_key_producer.h"的頭文件。

          而LDFLAGS標(biāo)識去哪里讀取動態(tài)庫,讀取哪個動態(tài)庫,比如

          // #cgo LDFLAGS: -L/home/jianfengye/chipkey -lChipSdk

          表示讀取 /home/jianfengye/chipkey/ChipSdk.so

          // #cgo CFLAGS: -I/home/jianfengye/chipkey/
          // #cgo LDFLAGS: -L/home/jianfengye/chipkey -lChipSdk
          // #include "chip_key_producer.h"
          import "C"
          import (
           "encoding/base64"
           "errors"
           "fmt"
           ...
          )

          // EncryptChipKey 獲取密鑰
          func EncryptChipKey(uid string, typ int) ([]byte, error) {
           ...
           result, err := C.Producer(C.int(typ), ucharPtr, ulenPtr, outKeyPtr, outKeyLenPtr)
           if result != 0 || err != nil{
            return nil, errors.New(fmt.Sprintf("ProducerCall return %v, err: %v", result, err))
           }
           ...
          }

          上面的代碼例子就表示了加載ChipSdk.so動態(tài)庫,header頭文件未chip_key_producer.h。使用的時候調(diào)用其中的Producer函數(shù)。

          第二種,使用動態(tài)dlopen的方式來加載動態(tài)庫。

          dlopen 能把一個動態(tài)庫加載到內(nèi)存中,返回一個handler,這個handler可以通過dlsym來加載動態(tài)庫中的函數(shù)符號。代碼如如

          /*
          #cgo LDFLAGS: -ldl
          #include <dlfcn.h>

          typedef int (*Producer)(int,
               const unsigned char*,
                              unsigned int,
                              unsigned char**,
                              unsigned int*); // function pointer type

          int ProducerCall(void* f, int in_encrypt_method,
                              const unsigned char* in_uid,
                              unsigned int in_uid_len,
                              unsigned char** out_chip_key,
                              unsigned int* out_chip_key_len) { // wrapper function
              return ((Producer) f)(in_encrypt_method, in_uid, in_uid_len, out_chip_key, out_chip_key_len);
          }

          */
          import "C"
          import (
           "encoding/base64"
           "encoding/hex"
           "errors"
           ...
          )

          func EncryptKey(uid string, typ int) ([]byte, error) {
           ...
           handle := C.dlopen(C.CString("/home/jianfengye/chipkey/ChipSdk.so"), C.RTLD_LAZY)

           funcPtr := C.dlsym(handle, C.CString("Producer"))

           ...
           result, err := C.ProducerCall(funcPtr, C.int(typ), ucharPtr, ulenPtr, outKeyPtr, outKeyLenPtr)
           if result != 0 || err != nil {
            return nil, errors.New(fmt.Sprintf("ProducerCall return %v, err: %v", result, err))
           }
           ...
          }

          我們可以看到,實際上我們用注釋的方式自己寫了一個函數(shù)ProducerCall,它的第一個參數(shù)是void*f 表示的是函數(shù)符號,Producer,就是C.dlsym和C.dlopen獲取出來的函數(shù)符號。

          在go代碼中,我們實際上是調(diào)用了C.ProducerCall這個函數(shù)。

          類型轉(zhuǎn)換

          加載動態(tài)庫的方式使用上面兩種方式任意一種都是可以的,但是類型轉(zhuǎn)換,就是統(tǒng)一的了。我們都要先把go中的某些類型轉(zhuǎn)換為c中的某些類型,同時要把函數(shù)調(diào)用返回的c中的某些類型再變化成go中的類型。

          我們先分析下Producer這個接口的參數(shù),這是標(biāo)準(zhǔn)的c接口,其中的參數(shù)中輸入有3個,一個是encrypt_method標(biāo)識加密類型,另外兩個in_uid 和 in_uid_len 表示的實際是一個字符串,表示的是輸入的設(shè)備的唯一標(biāo)識。而輸出的兩個參數(shù),out_chip_key 和 out_chip_key_len 也是表示一個字符串,分別為這個字符串的首地址指針和長度指針。

          這里主要參考柴大的 https://chai2010.cn/advanced-go-programming-book/ch2-cgo/ch2-03-cgo-types.html 類型轉(zhuǎn)換一篇。

          輸入轉(zhuǎn)換

          先看輸入?yún)?shù),在go中,我們有的是一個string,如何把string變成unsigned char*和unsigned int呢?

          首先和unsigned char* 對應(yīng)的結(jié)構(gòu)是 uint8數(shù)組

          所以我們先把string變成uint8數(shù)組。

          uidUint8 := make([]uint8, 0, len(uid))
          for i := 0; i < len(hexUid); i++ {
           uidUint8 = append(uidUint8, uint8(uid[i]))
          }

          然后,我們從uint8的slice結(jié)構(gòu)中可以獲取到指針信息和lenth信息。

          var arr0Hdr = (*reflect.SliceHeader)(unsafe.Pointer(&uidUint8))
          ucharPtr := (*C.uchar)(unsafe.Pointer(arr0Hdr.Data))
          ulenPtr := C.uint(uint(arr0Hdr.Len))

          這段就有點繞,首先將uidUint8這個slice變化成reflect.SliceHeader結(jié)構(gòu),然后從這個結(jié)構(gòu)中獲取到Data和Len字段。這兩個字段是slice結(jié)構(gòu)的內(nèi)部字段。

          再接著,將Data結(jié)構(gòu)通過unsafe.Pointer,轉(zhuǎn)換為*C.uchar,也就是c中的uchar*。

          同樣,把Len結(jié)構(gòu)轉(zhuǎn)換為C.uint,也就是c中的uint結(jié)構(gòu)。

          這樣ucharPtr和ulenPtr就是C中可以直接使用的數(shù)據(jù)結(jié)構(gòu)了。

          輸出轉(zhuǎn)換

          接著看輸出轉(zhuǎn)換,我們從c函數(shù)中獲取到的是unsigned char** out_chip_keyunsigned int* out_chip_key_len

          實際上我們想要的是返回[]byte,那么我們怎么做呢?

          同樣我們也想到,slice數(shù)組都是(*reflect.SliceHeader)結(jié)構(gòu),那么我們創(chuàng)建一個[]byte,轉(zhuǎn)化為SliceHeader結(jié)構(gòu),然后將這個sliceHeader結(jié)構(gòu)的Data和Len的指針傳遞到c函數(shù)中,c函數(shù)就會修改這兩個指針指向的地址,從而達(dá)到將[]byte賦值的效果。

          outKey := make([]uint8, 0, 0)
          var outKeyHdr = (*reflect.SliceHeader)(unsafe.Pointer(&outKey))
          outKeyPtr := (**C.uchar)(unsafe.Pointer(outKeyHdr.Data))
          outKeyLenPtr := (*C.uint)(unsafe.Pointer(&outKeyHdr.Len))

          result, err := C.ProducerCall(funcPtr, C.int(typ), ucharPtr, ulenPtr, outKeyPtr, outKeyLenPtr)
          if result != 0 || err != nil {
             return nil, errors.New(fmt.Sprintf("ProducerCall return %v, err: %v", result, err))
          }
          outKeyLen := uint32(*outKeyLenPtr)

          outKeyHdr.Data = uintptr(unsafe.Pointer(*outKeyPtr))
          outKeyHdr.Len = int(outKeyLen)
          outKeyHdr.Cap = int(outKeyLen)

          var ret []byte
          for i := 0; i < int(outKeyLen); i++ {
           ret = append(ret, byte(outKey[i]))
          }

          這里我們創(chuàng)建了一個[]uint8的數(shù)組,然后將對應(yīng)的Data,Len的指針通過unsafe.Pointer的方式轉(zhuǎn)換成對應(yīng)的C的指針結(jié)構(gòu)。

          然后傳遞進(jìn)入C的函數(shù),當(dāng)c函數(shù)對指針賦值后,我們再將 (**C.uchar) 指向的 *uchar的地址轉(zhuǎn)換為uintptr賦值給Data。

          同時把(*C.uint)指向的C.uint賦值給Len和Cap。

          這樣C函數(shù)返回的uchar* 就指向給了[]uint8了。

          最后我們再把[]uint8變成[]byte就行了。

          總而言之,不管是輸出轉(zhuǎn)換還是輸入轉(zhuǎn)換,這里就是兩個原則:

          1 基本類型通過 C.xxx來進(jìn)行互相轉(zhuǎn)換

          2 指針類型通過unsafe.Pointer進(jìn)行互相轉(zhuǎn)換

          總結(jié)

          在生產(chǎn)過程中,還要記得cgo也有可能panic,最好在調(diào)用cgo的前后記得recover。

          基本上弄懂了cgo的加載機(jī)制和類型轉(zhuǎn)換兩個邏輯和原理,cgo就算入門了。網(wǎng)上對cgo的使用介紹文章確實不多,不過go編譯器集成cgo已經(jīng)集成很不錯了,一旦你的類型轉(zhuǎn)換不符合要求,編譯的時候都會有很明確的錯誤指示了。

          cgo就像是粘合劑,將golang和c連接起來了。

          Hi,我是軒脈刃,一個名不見經(jīng)傳碼農(nóng),體制內(nèi)的小憤青,躁動的騷年,2022年想堅持寫一些學(xué)習(xí)/工作/思考筆記,謂之倒逼學(xué)習(xí)。歡迎關(guān)注個人公眾號:軒脈刃的刀光劍影。



          推薦閱讀


          福利

          我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號 「polarisxu」,回復(fù) ebook 獲??;還可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

          瀏覽 71
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  高清无码一级 | 北条麻妃在线观看一区二区 | 国产一级二级三级在线观看 | 麻豆AV无码精品一区二区色欲 | 国产女人在线 |