如何使用 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

红宝石:


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项目中有一个方法可以并行处理这样的数组)


喵喔喔
浏览 128回答 2
2回答

四季花海

这里的主要问题是,该过程中有两个不同的运行时,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 CTheFuncfunc CTheFunc(in **C.char, len C.int, out ***C.char) {&nbsp; &nbsp; inSlice := make([]string, int(len))&nbsp; &nbsp; // We need to do some pointer arithmetic.&nbsp; &nbsp; start := unsafe.Pointer(in)&nbsp; &nbsp; pointerSize := unsafe.Sizeof(in)&nbsp; &nbsp; for i := 0; i< int(len); i++ {&nbsp; &nbsp; &nbsp; &nbsp; // Copy each input string into a Go string and add it to the slice.&nbsp; &nbsp; &nbsp; &nbsp; pointer := (**C.char)(unsafe.Pointer(uintptr(start) + uintptr(i)*pointerSize))&nbsp; &nbsp; &nbsp; &nbsp; inSlice[i] = C.GoString(*pointer)&nbsp; &nbsp; }&nbsp; &nbsp; // Call the real function.&nbsp; &nbsp; resultSlice := TheFunc(inSlice)&nbsp; &nbsp; // Allocate an array for the string pointers.&nbsp; &nbsp; outArray := (C.malloc(C.ulong(len) * C.ulong(pointerSize)))&nbsp; &nbsp; // Make the output variable point to this array.&nbsp; &nbsp; *out = (**C.char)(outArray)&nbsp; &nbsp; // Note this is assuming the input and output arrays are the same size.&nbsp; &nbsp; for i := 0; i< int(len); i++ {&nbsp; &nbsp; &nbsp; &nbsp; // Find where to store the address of the next string.&nbsp; &nbsp; &nbsp; &nbsp; pointer := (**C.char)(unsafe.Pointer(uintptr(outArray) + uintptr(i)*pointerSize))&nbsp; &nbsp; &nbsp; &nbsp; // Copy each output string to a C string, and add it to the array.&nbsp; &nbsp; &nbsp; &nbsp; // C.CString uses malloc to allocate memory.&nbsp; &nbsp; &nbsp; &nbsp; *pointer = C.CString(resultSlice[i])&nbsp; &nbsp; }}这提供了一个具有以下签名的 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&nbsp; extend FFI::Library&nbsp; ffi_lib FFI::Library::LIBC&nbsp; attach_function :free, [:pointer], :voidendclass GoCaller&nbsp; extend FFI::Library&nbsp; ffi_lib "myamazinggolibrary.so"&nbsp; POINTER_SIZE = FFI.type_size(:pointer)&nbsp; attach_function :CTheFunc, [:pointer, :int, :pointer], :void&nbsp; # Wrapper method that prepares the data and calls the Go function.&nbsp; def self.the_func(ary)&nbsp; &nbsp; # Allocate a buffer to hold the input pointers.&nbsp; &nbsp; in_ptr = FFI::MemoryPointer.new(:pointer, ary.length)&nbsp; &nbsp; # Copy the input strings to C strings, and write the pointers to in_ptr.&nbsp; &nbsp; in_ptr.write_array_of_pointer(ary.map {|s| FFI::MemoryPointer.from_string(s)})&nbsp; &nbsp; # Allocate some memory to receive the address of the output array.&nbsp; &nbsp; out_var = FFI::MemoryPointer.new(:pointer)&nbsp; &nbsp; # Call the actual function.&nbsp; &nbsp; CTheFunc(in_ptr, ary.length, out_var)&nbsp; &nbsp; # Follow the pointer in out_var, and convert to an array of Ruby strings.&nbsp; &nbsp; # This is the return value.&nbsp; &nbsp; out_var.read_pointer.get_array_of_string(0, ary.length)&nbsp; ensure&nbsp; &nbsp; # Free the memory allocated in the Go code. We don’t need to free&nbsp; &nbsp; # the memory in the MemoryPointers, it is done automatically.&nbsp; &nbsp; out_var.read_pointer.get_array_of_pointer(0, ary.length).each {|p| CLib.free(p)}&nbsp; &nbsp; CLib.free(out_var.read_pointer)&nbsp; endend这确实涉及在每个方向将数据复制两次,从 Ruby 中复制出来(或复制到 Ruby 中),然后复制到 Go 中(或从 Go 中复制出来),但我认为如果没有运行时(尤其是垃圾收集器),不可能以任何其他方式完成此操作)互相绊倒。也许可以将数据直接存储在某个共享区域并对其进行操作,而无需FFI::Pointer在 Ruby 和 Go 中使用 s进行复制unsafe,但这首先就违背了使用这些语言的目的。

DIEA

我不确定这是正确的方法,但在这个解决方案中,要传递的参数是用 ruby 编码的 json,然后用 go 解码的 json。该解决方案可能效率低下,但是很安全。我稍微改变了 ruby 程序require 'ffi'require 'json'# testmodule GNparser&nbsp; extend FFI::Library&nbsp; ffi_lib './libgnparser.so'&nbsp; attach_function :ParseToJSON, [:string], :stringendputs GNparser.ParseToJSON(["Homo","sapiens","L."].to_json)和 go 程序package mainimport "C"import (&nbsp; &nbsp; "encoding/json"&nbsp; &nbsp; "fmt")// "gitlab.com/gogna/gnparser"// ParseToJSON exports ParseToJSON//export ParseToJSONfunc ParseToJSON(name *C.char) *C.char {&nbsp; &nbsp; goname := C.GoString(name)&nbsp; &nbsp; dec := []string{}&nbsp; &nbsp; json.Unmarshal([]byte(goname), &dec)&nbsp; &nbsp; // gnp := gnparser.NewGNparser()&nbsp; &nbsp; // parsed, err := gnp.ParseAndFormat(goname)&nbsp; &nbsp; // if err != nil {&nbsp; &nbsp; //&nbsp; fmt.Println(err)&nbsp; &nbsp; //&nbsp; return C.CString("")&nbsp; &nbsp; // }&nbsp; &nbsp; goname = fmt.Sprint(len(dec))&nbsp; &nbsp; 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 Parse000000000012fb40 g&nbsp; &nbsp; DF .text&nbsp; 0000000000000042&nbsp; Base&nbsp; &nbsp; &nbsp; &nbsp; ParseToJSON000000000012f780 g&nbsp; &nbsp; DF .text&nbsp; 0000000000000051&nbsp; Base&nbsp; &nbsp; &nbsp; &nbsp; _cgoexp_fcc5458c4ebb_ParseToJSON[mh-cbon@Host-001 rubycgo] $ ruby main.rb&nbsp;3[mh-cbon@Host-001 rubycgo] $ lltotal 3008-rw-rw-r-- 1 mh-cbon mh-cbon&nbsp; &nbsp; 1639 17 nov.&nbsp; 13:12 libgnparser.h-rw-rw-r-- 1 mh-cbon mh-cbon 3063856 17 nov.&nbsp; 13:12 libgnparser.so-rw-rw-r-- 1 mh-cbon mh-cbon&nbsp; &nbsp; &nbsp;504 17 nov.&nbsp; 13:12 main.go-rw-rw-r-- 1 mh-cbon mh-cbon&nbsp; &nbsp; &nbsp;219 17 nov.&nbsp; 13:03 main.rb
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go