通过SWIG从C++调用Go回调函数

huangapple go评论105阅读模式
英文:

Calling a Go callback function from C++ through SWIG

问题

我正在尝试从Go代码中调用C++函数:

void TestFunc(void(*f)(void)) { f(); }

我希望只需将Go函数传递给该函数。我知道我可以将其包装成一个类,并使用%feature("director")来解决,但在我的情况下这不是最佳解决方案。

根据我在这个页面上看到的,Go中的函数指针应该与C++中的函数指针相同,所以我尝试了以下.swig文件:

%{
#include "test.h"
%}

%typemap(gotype) FUNC* "func()"
%typemap(in) FUNC* {
  $1 = (void(*)(void))$input;
}
%apply FUNC* { void(*)(void) };

%include "test.h"

起初我很惊讶它居然起作用了,但后来注意到它并不总是起作用 :(。

例如,在这个Go代码中,它按预期工作:

import "fmt"
import "test_wrap"

func main() {
  b := false
  test_wrap.TestFunc(func() { b = true })
  fmt.Println(b)  // 这实际上确实打印出了"true"!
}

但在其他情况下,它不起作用。例如,在这里:

import "fmt"
import "test_wrap"

func main() {
  test_wrap.TestFunc(func() { fmt.Println("SUCCESS") })
  fmt.Println("Done")
}

我实际上得到了:

SUCCESS
SIGILL: illegal instruction
PC=0x4c20005d000


goroutine 1 [syscall]:
test_wrap._swig_wrap_TestFunc(0x400cb0, 0x400c2a)
        base_go_test__wrap_gc.c:33 +0x32
test_wrap.TestFunc(0x400cb0, 0x2)
        base_go_test__wrap.go:37 +0x25
main.main()
        test.go:8 +0x2a

goroutine 2 [syscall]:
created by runtime.main
        go/gc/src/pkg/runtime/proc.c:225
rax     0x0
rbx     0x0
rcx     0x0
rdx     0x8
rdi     0x4c200073050
rsi     0x4c20004c0f0
rbp     0x0
rsp     0x4c20004c100
r8      0x2
r9      0x4b0ae0
r10     0x4f5620
r11     0x4dbb88
r12     0x4f5530
r13     0x7fad5977f9c0
r14     0x0
r15     0x3
rip     0x4c20005d000
rflags  0x10202
cs      0x33
fs      0x0
gs      0x0

请注意,它确实打印了"SUCCESS",这意味着函数确实运行了,即使我将更复杂(且较长)的代码放入此函数中,它也能完美执行,但它没有返回 :(。

请告诉我您的想法,以及如何解决这个问题。
我不介意在C++部分添加一些代码,但我真的希望Go部分看起来"干净"。

谢谢。

英文:

I'm trying to call the C++ function:

void TestFunc(void(*f)(void)) { f(); }

From Go Code.

I would really want it to be that I just pass a Go function to that function. I know that I can wrap it into a class, and solve it using %feature("director"), but that's not the optimal solution in my case.

From what I saw in this page, the pointers to function in Go, should be the same as in C++, so I tried the following .swig file:

%{
#include "test.h"
%}

%typemap(gotype) FUNC* "func()"
%typemap(in) FUNC* {
  $1 = (void(*)(void))$input;
}
%apply FUNC* { void(*)(void) };

%include "test.h"

I was quiet surprised that it worked at first, but then noticed that it doesn't always work :(.

For example, in this Go code, it works as expected:

import "fmt"
import "test_wrap"

func main() {
  b := false
  test_wrap.TestFunc(func() { b = true })
  fmt.Println(b)  // This actually DOES print "true"!
}

But in other cases, it doesn't work. For example, here:

import "fmt"
import "test_wrap"

func main() {
  test_wrap.TestFunc(func() { fmt.Println("SUCCESS") })
  fmt.Println("Done")
}

I actually get:

SUCCESS
SIGILL: illegal instruction
PC=0x4c20005d000


goroutine 1 [syscall]:
test_wrap._swig_wrap_TestFunc(0x400cb0, 0x400c2a)
        base_go_test__wrap_gc.c:33 +0x32
test_wrap.TestFunc(0x400cb0, 0x2)
        base_go_test__wrap.go:37 +0x25
main.main()
        test.go:8 +0x2a

goroutine 2 [syscall]:
created by runtime.main
        go/gc/src/pkg/runtime/proc.c:225
rax     0x0
rbx     0x0
rcx     0x0
rdx     0x8
rdi     0x4c200073050
rsi     0x4c20004c0f0
rbp     0x0
rsp     0x4c20004c100
r8      0x2
r9      0x4b0ae0
r10     0x4f5620
r11     0x4dbb88
r12     0x4f5530
r13     0x7fad5977f9c0
r14     0x0
r15     0x3
rip     0x4c20005d000
rflags  0x10202
cs      0x33
fs      0x0
gs      0x0

Please notice that it did print "SUCCESS", this means that the function DID run, and even if I put more complex (and long) code into this function, it does execute perfectly, but it didn't come back :(.

Please let me know what you think, and how I can solve this problem.
I don't mind adding some code on the C++ part, but I really want the Go part look "clean".

Thank you.

答案1

得分: 5

成功!我有一个可行的解决方案:

我所做的是将回调函数包装在“directors”中,并将Go函数指针“return”回Go,以便在该上下文中运行。

下面的解决方案并不完美,但对我的需求来说足够接近,并且很容易从这里完善。

C++文件:

class Callback {
 public:
  virtual void Run(void(*f)(void)) = 0;
  virtual ~Callback() {}
};

Callback* GlobalCallback;

void TestFunc(void(*f)(void)) {
  GlobalCallback->Run(f);
}

我添加了一个名为Callback的类,它将在Go中进行“扩展”(使用Swig directors),并且我将拥有这个扩展类的全局实例。因此,调用该实例的Run()将调用一个Go函数,该函数将接收一个函数指针。

请注意,我的TestFunc现在不仅仅运行f(),而是通过GlobalCallback运行它。通过添加另一个函数来修复很容易,该函数返回一个指向运行GlobalCallback->Run(f)的函数的指针,并将此指针传递给函数而不是*f。

我的Swig文件:

%{
#include "test.h"
%}

%module(directors="1") Callback
%feature("director");

%typemap(gotype) FUNC* "func()"
%typemap(in) FUNC* {
  $1 = (void(*)(void))$input;
}
%apply FUNC* { void(*)(void) };

%include "test.h";

%insert(go_wrapper) %{
type go_callback struct { }

func (c* go_callback) Run(f func()) {
  f()
}

func init() {
  SetGlobalCallback(NewDirectorCallback(&go_callback{}))                                                                                                     
}
%}

请注意,我添加了一个init()函数,它使用一个运行指针的Go函数设置GlobalCallback。

就是这样,Go代码与之前一样,而且它可以工作 通过SWIG从C++调用Go回调函数

英文:

Success! I have a solution that works:

The idea of what I did, is to wrap the callback with "directors", and "return" the Go function pointer back to Go, so it could be run in that context.

The solution below is not perfect, but it's close enough for my needs, and it's pretty easy to make it perfect from here on.

The C++ file:

class Callback {
 public:
  virtual void Run(void(*f)(void)) = 0;
  virtual ~Callback() {}
};

Callback* GlobalCallback;

void TestFunc(void(*f)(void)) {
  GlobalCallback->Run(f);
}

I've added a class Callback, which will be "extended" in Go (using Swig directors), and I'll have a global instance of this extended class. Thus, calling Run() of that instance, would call a Go function which will receive a function pointer.

Please notice that my TestFunc now instead of just running f(), runs it through the GlobalCallback. It's easy to fix by adding another function that returns a pointer to a function that runs GlobalCallback->Run(f), and pass this pointer to the function instead of *f.

My Swig file:

%{
#include "test.h"
%}

%module(directors="1") Callback
%feature("director");

%typemap(gotype) FUNC* "func()"
%typemap(in) FUNC* {
  $1 = (void(*)(void))$input;
}
%apply FUNC* { void(*)(void) };

%include "test.h"

%insert(go_wrapper) %{
type go_callback struct { }

func (c* go_callback) Run(f func()) {
  f()
}

func init() {
  SetGlobalCallback(NewDirectorCallback(&go_callback{}))                                                                                                     
}
%}

Notice that i've added an init() function that sets the GlobalCallback with a Go function that runs the pointer.

That's it, the Go code is as it was, and it works 通过SWIG从C++调用Go回调函数

huangapple
  • 本文由 发表于 2012年12月22日 21:05:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/14003078.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定