这里的主要问题是,在该过程中有两个不同的运行时,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数据,尤其是返回值,因此并不十分安全。

**char***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
FFI::Pointerunsafe