亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

如何使用 Go 共享庫在 Ruby 中傳遞字符串數組并獲取字符串數組?

如何使用 Go 共享庫在 Ruby 中傳遞字符串數組并獲取字符串數組?

Go
喵喔喔 2023-08-07 10:46:50
我正在嘗試從 Ruby 調用 Go 項目。當我傳遞一個字符串并返回一個字符串時,它確實工作得很好:去:package mainimport "C"import (    "fmt"    "gitlab.com/gogna/gnparser")//export ParseToJSONfunc ParseToJSON(name *C.char) *C.char {    goname := C.GoString(name)    gnp := gnparser.NewGNparser()    parsed, err := gnp.ParseAndFormat(goname)    if err != nil {        fmt.Println(err)        return C.CString("")    }    return C.CString(parsed)}func main() {}我用它編譯go build -buildmode=c-shared -o libgnparser.so main.go紅寶石:require 'ffi'# testmodule GNparser  extend FFI::Library  if Gem.platforms[1].os == 'darwin'      ffi_lib './clib/mac/libgnparser.so'  else      ffi_lib './clib/linux/libgnparser.so'  end  attach_function :ParseToJSON, [:string], :stringendputs GNparser.ParseToJSON("Homo sapiens L.")對于這樣的示例,如何將 Ruby 字符串數組傳遞給 Go 并返回一個字符串數組?(Go項目中有一個方法可以并行處理這樣的數組)
查看完整描述

2 回答

?
四季花海

TA貢獻1811條經驗 獲得超5個贊

這里的主要問題是,該過程中有兩個不同的運行時,Ruby 和 Go,而且它們都不像其他人那樣在其內部進行研究。因此,為了從 Ruby 調用 Go,您必須首先從 Ruby 獲取數據,然后輸入 Go,然后從 Go 獲取結果,然后輸入 Ruby。實際上,即使沒有實際的 C 代碼,您也必須通過 C 從 Ruby 轉到 Go。


從 Go 方面開始,假設您要使用的函數具有如下所示的簽名:


func TheFunc(in []string) []string

您可以將其導出到共享庫中,這將給出以下 C 簽名:


extern GoSlice TheFunc(GoSlice p0);

哪里GoSlice是


typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;

雖然這可能有效,但這會直接訪問 Go 數據,尤其是返回值,因此并不安全。


解決方案是提供一個包裝函數,它接受指向 C 字符串數組的指針(即**char)和數組的長度。然后,該函數可以解壓這些數據并將其轉換為 Go 數組(或切片),并將其傳遞給執行工作的實際函數。該包裝函數還需要一種獲取結果的方法。一種方法是傳入一個指向字符串數組的指針(即***char),函數可以分配該數組,用結果字符串填充它,并將其地址寫入該指針指向的位置。


該解決方案的缺點是在 Go 中分配內存并依賴調用代碼來釋放內存。


這有點混亂,但它看起來像這樣:


// #include <stdlib.h>

import "C"

import "unsafe"


//export CTheFunc

func CTheFunc(in **C.char, len C.int, out ***C.char) {


    inSlice := make([]string, int(len))


    // We need to do some pointer arithmetic.

    start := unsafe.Pointer(in)

    pointerSize := unsafe.Sizeof(in)


    for i := 0; i< int(len); i++ {

        // Copy each input string into a Go string and add it to the slice.

        pointer := (**C.char)(unsafe.Pointer(uintptr(start) + uintptr(i)*pointerSize))

        inSlice[i] = C.GoString(*pointer)

    }


    // Call the real function.

    resultSlice := TheFunc(inSlice)


    // Allocate an array for the string pointers.

    outArray := (C.malloc(C.ulong(len) * C.ulong(pointerSize)))


    // Make the output variable point to this array.

    *out = (**C.char)(outArray)


    // Note this is assuming the input and output arrays are the same size.

    for i := 0; i< int(len); i++ {

        // Find where to store the address of the next string.

        pointer := (**C.char)(unsafe.Pointer(uintptr(outArray) + uintptr(i)*pointerSize))

        // Copy each output string to a C string, and add it to the array.

        // C.CString uses malloc to allocate memory.

        *pointer = C.CString(resultSlice[i])

    }


}

這提供了一個具有以下簽名的 C 函數,我們可以使用 FFI 從 Ruby 訪問該函數。


extern void CDouble(char** p0, int p1, char*** p2);

Ruby 方面的情況非常相似,但方向相反。我們需要將數據復制到 C 數組中,并分配一些內存,我們可以將其地址傳遞給接收結果,然后將數組、其長度和輸出指針傳遞給 Go 函數。當它返回時,我們需要將 C 數據復制回 Ruby 字符串和數組并釋放內存。它可能看起來像這樣:


require 'ffi'


# We need this to be able to use free to tidy up.

class CLib

  extend FFI::Library

  ffi_lib FFI::Library::LIBC

  attach_function :free, [:pointer], :void

end


class GoCaller

  extend FFI::Library

  ffi_lib "myamazinggolibrary.so"

  POINTER_SIZE = FFI.type_size(:pointer)


  attach_function :CTheFunc, [:pointer, :int, :pointer], :void


  # Wrapper method that prepares the data and calls the Go function.

  def self.the_func(ary)

    # Allocate a buffer to hold the input pointers.

    in_ptr = FFI::MemoryPointer.new(:pointer, ary.length)

    # Copy the input strings to C strings, and write the pointers to in_ptr.

    in_ptr.write_array_of_pointer(ary.map {|s| FFI::MemoryPointer.from_string(s)})


    # Allocate some memory to receive the address of the output array.

    out_var = FFI::MemoryPointer.new(:pointer)


    # Call the actual function.

    CTheFunc(in_ptr, ary.length, out_var)


    # Follow the pointer in out_var, and convert to an array of Ruby strings.

    # This is the return value.

    out_var.read_pointer.get_array_of_string(0, ary.length)

  ensure

    # Free the memory allocated in the Go code. We don’t need to free

    # the memory in the MemoryPointers, it is done automatically.

    out_var.read_pointer.get_array_of_pointer(0, ary.length).each {|p| CLib.free(p)}

    CLib.free(out_var.read_pointer)

  end

end

這確實涉及在每個方向將數據復制兩次,從 Ruby 中復制出來(或復制到 Ruby 中),然后復制到 Go 中(或從 Go 中復制出來),但我認為如果沒有運行時(尤其是垃圾收集器),不可能以任何其他方式完成此操作)互相絆倒。也許可以將數據直接存儲在某個共享區域并對其進行操作,而無需FFI::Pointer在 Ruby 和 Go 中使用 s進行復制unsafe,但這首先就違背了使用這些語言的目的。


查看完整回答
反對 回復 2023-08-07
?
DIEA

TA貢獻1820條經驗 獲得超2個贊

我不確定這是正確的方法,但在這個解決方案中,要傳遞的參數是用 ruby 編碼的 json,然后用 go 解碼的 json。


該解決方案可能效率低下,但是很安全。


我稍微改變了 ruby 程序


require 'ffi'

require 'json'


# test

module GNparser

  extend FFI::Library

  ffi_lib './libgnparser.so'

  attach_function :ParseToJSON, [:string], :string

end


puts GNparser.ParseToJSON(["Homo","sapiens","L."].to_json)

和 go 程序


package main


import "C"

import (

    "encoding/json"

    "fmt"

)


// "gitlab.com/gogna/gnparser"


// ParseToJSON exports ParseToJSON

//export ParseToJSON

func ParseToJSON(name *C.char) *C.char {

    goname := C.GoString(name)

    dec := []string{}

    json.Unmarshal([]byte(goname), &dec)

    // gnp := gnparser.NewGNparser()

    // parsed, err := gnp.ParseAndFormat(goname)

    // if err != nil {

    //  fmt.Println(err)

    //  return C.CString("")

    // }

    goname = fmt.Sprint(len(dec))

    return C.CString(goname)

}


func main() {}

注意添加// export comment,否則符號不會導出,并且 ruby 程序無法訪問它。


[mh-cbon@Host-001 rubycgo] $ go build -buildmode=c-shared -o libgnparser.so main.go

[mh-cbon@Host-001 rubycgo] $ objdump -TC libgnparser.so | grep Parse

000000000012fb40 g    DF .text  0000000000000042  Base        ParseToJSON

000000000012f780 g    DF .text  0000000000000051  Base        _cgoexp_fcc5458c4ebb_ParseToJSON

[mh-cbon@Host-001 rubycgo] $ ruby main.rb 

3

[mh-cbon@Host-001 rubycgo] $ ll

total 3008

-rw-rw-r-- 1 mh-cbon mh-cbon    1639 17 nov.  13:12 libgnparser.h

-rw-rw-r-- 1 mh-cbon mh-cbon 3063856 17 nov.  13:12 libgnparser.so

-rw-rw-r-- 1 mh-cbon mh-cbon     504 17 nov.  13:12 main.go

-rw-rw-r-- 1 mh-cbon mh-cbon     219 17 nov.  13:03 main.rb


查看完整回答
反對 回復 2023-08-07
  • 2 回答
  • 0 關注
  • 181 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號