[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 做一下異常處理。