[Golang] cgo 调用 .so 捕获异常问题

最近需要在 go 中去调用 .so 库去完成一些事情,go 方面,利用 cgo 可以顺利的调用 .so 中的方法,但是有个问题是 go 没法捕获 .so 那边出现的异常。如果 .so 那边异常了,那么会带崩 go 程序,这不是我们想看到的。例如在 服务器应用中,某个异常的请求可能会把服务器进程给弄挂,这不是我们想看到的。

我们最好在可能会崩溃的地方进行异常捕获,可以做一层 wrapper,然后将错误信息传给 go 这边,让 go 去决定异常的处理方式,这里我写了一个简单的 Demo 进行验证。

首先我们写一个简单的 cpp 的库,做成 .so

  • foo 函数增加了 try catch,将异常信息通过 char* 返回给 go;
  • foo1 函数值抛出异常,不捕获;
// clib.h
#ifdef __cplusplus
extern "C" {
#endif

#include <stdlib.h>

typedef struct HANDLE_ERR {
    char *data;
    const char *pstrErr;
} HANDLE_ERR;

HANDLE_ERR foo (const char *pstrArgs);

char* foo1 (const char *pstrArgs);

#ifdef __cplusplus
}
#endif

// clib.cpp
#include <string.h>
#include <stdio.h>
#include <exception>
#include "clib.h"

struct MyException : public std::exception
{
  const char * what () const throw ()
  {
    return "C++ Exception";
  }
};

HANDLE_ERR foo (const char *pstrArgs) {
    HANDLE_ERR result = {0};
    try {
        if (pstrArgs == nullptr)
            throw MyException();
        result.data = "Hello word!";
    } catch(MyException &e) {
        result.data = nullptr;
        result.pstrErr = strdup(e.what());
    }
    return result;
}

char* foo1 (const char *pstrArgs) {
    if (pstrArgs == nullptr)
            throw "exception, nullptr";
    char* data = "Hello word!";
    return data;
}

我们用 g++ 来做成 .so,将上面的 cpp 做成 libclib.so 文件,供 go 来使用,下面是用 g++ 编译的指令命令。

g++ -c -fPIC clib.cpp
g++ -shared -fPIC -o libclib.so clib.o

在做好了 .so 后,我们用 go 来调用

  • passNullptr 传递一个空指针给 clib,clib 会抛出异常;
  • passNormal 传递正常的值
  • passNullptrNoException 传递空指针给 foo1 函数,foo1 没有捕获异常。
package main

// #cgo LDFLAGS: -L. -lclib
// #include <stdlib.h>
// #include "clib.h"
import "C"

import (
	"log"
	"unsafe"
)

func passNullptr() {
	log.Println("1. passNullptr")
	ret := C.foo(nil)
	if ret.pstrErr != nil {
		defer C.free(unsafe.Pointer(ret.pstrErr))
		log.Println("clib error: ", C.GoString(ret.pstrErr))
	} else {
		log.Println("no error! from clib: ", C.GoString(ret.data))
	}
	log.Println("")
}

func passNormal() {
	log.Println("2. passNormal")
	cArgs := C.CString("")
	defer C.free(unsafe.Pointer(cArgs))
	ret := C.foo(cArgs)
	if ret.pstrErr != nil {
		defer C.free(unsafe.Pointer(ret.pstrErr))
		log.Println("clib error: ", C.GoString(ret.pstrErr))
	} else {
		log.Println("no error! from clib: ", C.GoString(ret.data))
	}
	log.Println("")
}

func passNullptrNoException() {
	log.Println("3. passNullptrNoException")
	cArgs := C.CString("")
	defer C.free(unsafe.Pointer(cArgs))
	C.foo1(nil)
	log.Println("")
}

func main() {
	passNullptr()
	passNormal()
	passNullptrNoException()
}

下面是输出的结果

2022/08/27 15:47:21 1. passNullptr
2022/08/27 15:47:21 clib error:  C++ Exception
2022/08/27 15:47:21 
2022/08/27 15:47:21 2. passNormal
2022/08/27 15:47:21 no error! from clib:  Hello word!
2022/08/27 15:47:21 
2022/08/27 15:47:21 3. passNullptrNoException
libc++abi: terminating with uncaught exception of type char const*
SIGABRT: abort
PC=0x7ff811758112 m=0 sigcode=0

goroutine 0 [idle]:
runtime: unknown pc 0x7ff811758112
stack: frame={sp:0x7ff7bfefed28, fp:0x0} stack=[0x7ff7bfe803e8,0x7ff7bfeff450)
...
...
exit status 2

我们可以看到,加了 try catch 后,clib 将 error 信息传递到了 go 侧,如果不 try catch 异常,clib 的异常会把 go 进行给带崩溃,所以在 go 调用 .so 的时候,最好做一层 wrapper 做一下异常处理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章