A
A
Alexey2015-09-08 08:46:37
ruby
Alexey, 2015-09-08 08:46:37

How to throw Callback in Go from under Ruby?

Go 1.5 introduced the ability to build c-shared libraries. On Habré there was an article on this topic, but there was a rather primitive example. That’s why it became interesting, is it possible to throw a callback function into Go from under Ruby in order to pull it as needed, well, or you never know for what else. I googled what is in FFI, what the documentation in CGO writes on the topic of external functions, and the following code came out:

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

Everything is compiled, connected, but when the function is called, I get a 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

Judging by the line:
main.remote(0x7f85006c8000)
The pointer was passed correctly. There is an assumption that somehow I call the function in a wrong way, although the FFI documentation provides just such a method of transfer.

Answer the question

In order to leave comments, you need to log in

2 answer(s)
S
sichacvah, 2015-09-09
@sichacvah

and why to transfer lines in such a way? There is also a standard type :string.
I tried to write a simple parser in Go and pull it from the ruby ​​code.
Github
As I understand it, ruby ​​FFI passes a C string and it must be converted to the th string

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

Ruby code:
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)

A
Alexey, 2015-09-09
@fuCtor

I made the simplest binding in C:

typedef void (*r_callback)();
void remote_c(r_callback  pfoo)
{
  pfoo();
  remote(pfoo);
}

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

void main() {
  remote_c(call);
}

Calling a Go function crashes, passing a function from Ruby to C works without problems. There is a suspicion that I am not cooking correctly, but I am doing as the instructions indicate https://github.com/golang/go/wiki/cgo

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question