@fuCtor
Ruby разработчик

Как прокинуть Callback в Go из под Ruby?

В версии Go 1.5 появилась возможность собирать c-shared библиотеки. На Хабре была статья на эту тему, но там был достаточно примитивный пример. Оттого стало интересно, а можно ли прокинуть в Go callback функцию из под Ruby, чтобы дергать ее по мере необходимости ну или мало ли для чего еще. Погуглил что есть в FFI, что пишет документация в CGO на тему внешних функций и получился следующий код:
package main

import (
	"unsafe"
	"fmt"
	"time"
)


import "C"

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(s)
	}
}

//export asay
func asay(str string) {
    go say(str)
}

//export ssay
func ssay(str string) {
    say(str)
}

//export remote
func remote(pfoo unsafe.Pointer) {
  foo := *(*())(pfoo)
  foo()
}

func main() {}


require 'ffi'

#typedef struct { char *p; GoInt n; } GoString;

module Go
  extend FFI::Library
  ffi_lib 'libadd.so'

  class String < FFI::Struct
    layout :p, :pointer,
           :n, :int
  end

  attach_function :asay, [Go::String.by_value], :void
  attach_function :ssay, [Go::String.by_value], :void
  
  callback :f_function, [], :void
  attach_function :remote, [:f_function], :void

  def self.say(text, async = false)
    btext = text.dup.force_encoding 'binary'
    
    str = Go::String.new
    str[:p] = FFI::MemoryPointer.from_string(btext)
    str[:n] = btext.size
    if async
      self.asay str
    else
      self.ssay str
    end
  end
  
  def self._remote(&block)
    func = FFI::Function.new(:void, []) { puts 'remote call' }
    puts func.inspect
    self.remote func
  end

end


Все компилируется, подключается, но при вызове функции получаю seg fault:
#<FFI::Function address=0x007f85006c8000 size=40>
unexpected fault address 0x0
fatal error: fault
[signal 0xb code=0x80 addr=0x0 pc=0x7f84fc1a2cd9]

goroutine 17 [running, locked to thread]:
runtime.throw(0x7f84fc2a8ac8, 0x5)
        /var/user/.gvm/gos/go1.5/src/runtime/panic.go:527 +0x92 fp=0xc820038e88 sp=0xc820038e70
runtime.sigpanic()
        /var/user/.gvm/gos/go1.5/src/runtime/sigpanic_unix.go:27 +0x2af fp=0xc820038ed8 sp=0xc820038e88
main.remote(0x7f85006c8000)
        /var/user/www/goruby/lib.go:45 +0x19 fp=0xc820038ee0 sp=0xc820038ed8
runtime.call32(0x0, 0x7fff95f89198, 0x7fff95f89220, 0x8)
        /var/user/.gvm/gos/go1.5/src/runtime/asm_amd64.s:437 +0x40 fp=0xc820038f08 sp=0xc820038ee0
runtime.cgocallbackg1()
        /var/user/.gvm/gos/go1.5/src/runtime/cgocall.go:252 +0x110 fp=0xc820038f40 sp=0xc820038f08
runtime.cgocallbackg()
        /var/user/.gvm/gos/go1.5/src/runtime/cgocall.go:177 +0xd9 fp=0xc820038fa0 sp=0xc820038f40
runtime.cgocallback_gofunc(0x0, 0x0, 0x0)
        /var/user/.gvm/gos/go1.5/src/runtime/asm_amd64.s:801 +0x5d fp=0xc820038fb0 sp=0xc820038fa0
runtime.goexit()
        /var/user/.gvm/gos/go1.5/src/runtime/asm_amd64.s:1696 +0x1 fp=0xc820038fb8 sp=0xc820038fb0

goroutine 18 [syscall, locked to thread]:
runtime.goexit()
        /var/user/.gvm/gos/go1.5/src/runtime/asm_amd64.s:1696 +0x1
[1]    22080 abort      ruby run.rb


Судя по строке:
main.remote(0x7f85006c8000)
Указатель передан верно. Есть предположение что все же как-то не так вызываю функцию, хотя в документации по FFI приводится именно такой способ передачи.
  • Вопрос задан
  • 1082 просмотра
Пригласить эксперта
Ответы на вопрос 2
@sichacvah
а зачем строки передавать именно таким способом? Есть же стандартный тип :string.
Я попробовал написать простенький парсер на го и дергать его из руби кода.
Github
Как я понял руби FFI передает C строку и её надо конвертировать в го строку

//export grab
func grab(seedUrlsC *C.char) string {
	seedUrlsString := C.GoString(seedUrlsC)
	seedUrls := strings.Split(seedUrlsString, ",")
....


Руби код:

require 'ffi'
require 'json'

module ParserYandex
  extend FFI::Library
  ffi_lib 'lib/parserlib.so'  
           
  attach_function :grab, [:string], :string
end

def self.getData(urls)
  stringUrls = urls.join ','
  json_data = ParserYandex.grab(stringUrls)
  data = JSON.parse(json_data)
  data.each do |item|
    puts item
  end
end

urls = ["https://auto.yandex.ru/vaz/granta/7684152/complects"]

self.getData(urls)
Ответ написан
@fuCtor Автор вопроса
Ruby разработчик
Сделал на С простейшую обвязку:
typedef void (*r_callback)();
void remote_c(r_callback  pfoo)
{
  pfoo();
  remote(pfoo);
}

void call() {
  printf("c call\n");
}

void main() {
  remote_c(call);
}


Вызов Go функции падает, прокидывание функции из Ruby в C отрабатывает без проблем. Есть подозрение что не правильно готовлю, но делаю как в инструкции указано https://github.com/golang/go/wiki/cgo
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы