Go 调用 c/c++
大约 4 分钟
Go 调用 c/c++
调用原理
要在 Go 中调用 C++ 代码,通常需要使用 Go 的 CGO 功能。CGO 允许 Go 代码调用 C 代码,而由于 C++ 是 C 的超集,因此也可以用于调用 C++ 代码。首先,编写 C++ 代码并确保使用extern "C"将其导出,然后编译为共享库文件。接着,编写 Go 代码并使用import "C"导入 C 代码,通过 CGO 调用 C++ 函数。最后,通过构建和运行 Go 代码来执行调用。
一、 链接选项调用 .so 文件
1.1 目录结构
├── cpp
│ ├── hello.h
│ ├── libhello.so
│ └── hello.cpp
└── demo
└── main.go
1.2 cpp 代码内容示例
hello.h 文件内容
#ifndef HELLO_H
#define HELLO_H
#ifdef __cplusplus
extern "C" {
#endif
void printHelloWorld();
#ifdef __cplusplus
}
#endif
hello.cpp 文件内容
#include <iostream>
extern "C" {
void printHelloWorld() {
std::cout << "Hello World" << std::endl;
}
}
1.3 编译
g++ -shared -o libhello.so -fPIC hello.cpp
1.4 go 代码内容示例
package main
// #cgo LDFLAGS: -L./cpp -lhello
// #include <stdlib.h>
// extern void printHelloWorld();
import "C"
func main() {
// 调用 C 函数
C.printHelloWorld()
}
解释说明
// #cgo LDFLAGS: -L./cpp -lhello:这是一个 CGO 的指令,用于指定在链接 Go 代码时需要使用的选项。在这里,LDFLAGS指定了链接器的选项。-L./cpp指定了要搜索库文件的路径(如果在当前目录下也要加点),-lhello指定了要链接的库文件名称为libhello.so或libhello.a(根据具体情况而定)。这样,编译器就可以找到并链接名为libhello的库文件,使得 Go 代码能够调用其中的函数。// #include:这是一个 C 语言的#include指令,它告诉编译器在编译时将标准库头文件stdlib.h包含进来。这样,在 Go 代码中通过 CGO 调用 C 函数时,就可以使用stdlib.h中定义的函数和数据类型。extern void printHelloWorld();:这是一个 C 函数的声明,声明了一个名为printHelloWorld的外部函数。extern关键字表明该函数是在别处定义的,这里只是声明了它的存在。这样,Go 代码就可以通过 CGO 调用这个 C 函数,而函数的具体实现可以在 C/C++ 文件中定义
1.5 编译运行
go build -o hello main.go && ./hello
注意
这里会报错:error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory,这是因为: 如果共享库文件未放置在系统预定义的路径中,例如 /usr/local/lib 或 /usr/lib,那么系统默认的动态链接器可能无法找到它
解决办法1:
# 调用方法改为:
LD_LIBRARY_PATH=. ./hello
二、 动态加载调用 .so 文件
2.1 目录结构
├── hello.cpp
├── hello.h
├── libhello.so
└── main.go
2.2 cpp 代码说明
这部分代码和 1.2 的代码示例一样
2.3 go 代码内容示例
package main
/*
#cgo CFLAGS: -I.
#cgo LDFLAGS: -ldl
#include <stdlib.h>
#include <dlfcn.h>
#include <stdio.h>
#include "./hello.h"
typedef void (*printHelloWorld_type)();
void printHelloWorld_wrapper(void* f){
((printHelloWorld_type) f)();
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
// 加载动态链接库
libName := C.CString("./libhello.so")
defer C.free(unsafe.Pointer(libName))
handle := C.dlopen(libName, C.RTLD_LAZY)
defer C.dlclose(handle)
if handle == nil {
fmt.Println(C.GoString(C.dlerror()))
return
}
proc := C.dlsym(handle, C.CString("printHelloWorld"))
C.printHelloWorld_wrapper(proc)
}
这里的复杂之处就是将头文件的函数方法,写到go的注释中,要包装一层进行调用
三、强化训练
上面的动态加载调用示例比较简单,这里再提供一个复杂一点的调用代码示例
c++ 头文件内容
#pragma once
#ifdef __cplusplus
extern "C"
{
#endif
// 结构体定义
typedef struct {
int identify_type;
int recognition_logic;
const char *field_name;
const char *data_content;
} identify_rule_t;
void *identifyRule_New(identify_rule_t rule);
void identifyRule_Delete(void *instance);
const char *identifyRule_Rewrite(void *instance, const char *data);
#ifdef __cplusplus
}
#endif
Go 代码内容
package main
/*
#cgo CFLAGS: -I.
#cgo LDFLAGS: -ldl
#include <stdlib.h>
#include <dlfcn.h>
#include <stdint.h>
#include "./replace.h"
typedef void* (*IdentifyRule_New_type)(identify_rule_t);
void* IdentifyRule_New_wrapper(void* f, identify_rule_t rule) {
return ((IdentifyRule_New_type) f)(rule);
}
typedef void (*IdentifyRule_Delete_type)(void *);
void IdentifyRule_Delete_wrapper(void* f, void* instance) {
return ((IdentifyRule_Delete_type) f)(instance);
}
typedef char* (*IdentifyRule_Rewrite_type)(void*, char*);
char* IdentifyRule_Rewrite_wrapper(void* f,void* instance,char *data) {
return ((IdentifyRule_Rewrite_type) f)(instance, data);
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
// 加载动态链接库
libName := C.CString("./libreplace.so")
defer C.free(unsafe.Pointer(libName))
handle := C.dlopen(libName, C.RTLD_LAZY)
defer C.dlclose(handle)
if handle == nil {
fmt.Println(C.GoString(C.dlerror()))
return
}
newIdentifyRule := C.dlsym(handle, C.CString("identifyRule_New"))
instance := (*C.void)(C.IdentifyRule_New_wrapper(newIdentifyRule, C.identify_rule_t{
identify_type: 3,
recognition_logic: 2,
field_name: C.CString("IPv4,IPv6"),
data_content: C.CString("134.231.87.27"),
}))
jsonStr := `{"IPv4":"134.231.87.27","IPv6":"3e79:a603:d35b:f3c1:9ccb:19db:36aa:a9c2","MAC 地址":"41:2a:12:2e:98:d4",}`
cStr := C.CString(jsonStr)
defer C.free(unsafe.Pointer(cStr))
rewrite := C.dlsym(handle, C.CString("identifyRule_Rewrite"))
res := C.IdentifyRule_Rewrite_wrapper(rewrite, unsafe.Pointer(instance), cStr)
value := C.GoString(res)
fmt.Println(value)
}
四、注意及帮助
- 在动态加载调用中,c++ 的头文件函数名如果是大写字母开头可能在 go 这里出现找不到的情况,所以最好要保证函数名小写字母开头。
- 使用
nm命令可以查询你需要调用的函数在 .so 文件中是否存在。 - 使用
ldd命令可以检查 .so 文件是否缺少依赖,找不到的情况如下图所示: