如何使用Go共享库在Ruby中传递字符串数组并获取字符串数组?

我正在尝试从Ruby调用Go项目。当我传递一个字符串并取回一个字符串时,它工作得很好:

开始:

package main

import "C"
import (
    "fmt"

    "gitlab.com/gogna/gnparser"
)

//export ParseToJSON
func 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

Ruby:

require 'ffi'

# test
module 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],:string
end

puts GNparser.ParseToJSON("Homo sapiens L.")

如何将Ruby的字符串数组传递给Go并返回该示例的字符串数组? (Go项目中有一种方法可以并行处理这样的数组)

gk77338869 回答:如何使用Go共享库在Ruby中传递字符串数组并获取字符串数组?

这里的主要问题是,在这个过程中有两个不同的运行时,Ruby和Go,它们都不像其他内部运行时那样。因此,要从Ruby调用到Go,您必须首先从Ruby中获取数据,然后再进入Go,然后从Go中获取结果,然后再获取Ruby。实际上,即使没有实际的C代码,您也必须从Ruby通过C转到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),但是我认为如果没有运行时,则无法以任何其他方式进行复制(尤其是垃圾收集器)彼此绊倒。可能可以将数据直接存储在某个共享区域中并对其进行操作,而无需在Ruby中使用FFI::Pointer和在Go中使用unsafe进行复制,但是这将使第一种使用这些语言的目的无法实现。地方。

,

我不确定这是正确的方法,但是在此解决方案中,要传递的参数是使用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)

然后转到

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
本文链接:https://www.f2er.com/3099184.html

大家都在问