持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第27天,点击查看活动详情
介绍
cgo 是 go 语言里面的一个特性,属于 go 的高级用法,我们可以使用 cgo 来实现 go 语言调用 c 语言程序。
- 简单示例:
package main
/*
#include <stdio.h>
static void Hello(const char* s) {
puts(s);
}
*/
import "C"
func main() {
hi := C.CString("Hello World!")
C.Hello(hi)
defer C.free(unsafe.Pointer(hi)) // 释放分配在C中的内存,否则会造成内存泄露
}
- import "C" 语句启用 CGO 特性
- 引入 C 语言的 <stdio.h> 头文件
- CGO 包的 C.CString 函数将 Go 语言字符串转为 C 语言字符串
- C.CString 创建的 C 语言字符串如果没有被正确释放,会导致内存泄漏,另外程序退出后操作系统也会自动回收程序的所有资源。
- 工具链
要使用 CGO 特性,需要安装 C/C++ 构建工具链,在 macOS 和 Linux 下是要安装 GCC,在 windows 下是需要安装 MinGW 工具。同时需要保证环境变量 CGO_ENABLED 被设置为 1,这表示 CGO 是被启用的状态。在本地构建时 CGO_ENABLED 默认是启用的,当交叉构建时 CGO 默认是禁止的。比如要交叉构建 ARM 环境运行的 Go 程序,需要手工设置好 C/C++ 交叉构建的工具链,同时开启 CGO_ENABLED 环境变量。然后通过 import "C" 语句启用 CGO 特性。
动态库路径
- 编译目标代码时指定的动态库搜索路径(指的是用-wl,rpath或-R选项而不是-L);
example: gcc -Wl,-rpath,/home/arc/test,-rpath,/lib/,-rpath,/usr/lib/,-rpath,/usr/local/lib test.c
- 环境变量
LD_LIBRARY_PATH****指定的动态库搜索路径;(例如:export LD_LIBRARY_PATH=/root/test/env/lib) - 配置文件/etc/ld.so.conf中指定的动态库搜索路径;(更改/etc/ld.so.conf文件后记得一定要执行命令:
ldconfig,该命令会将/etc/ld.so.conf文件中所有路径下的库载入内存中) - 默认的动态库搜索路径/lib;
- 默认的动态库搜索路径/usr/lib。
类型转换
// Go 字符串转换为 C 字符串
// C 字符串使用 malloc 来分配到 C 堆里。
// 调用方需要把它释放掉,比如调用 C.free (如果需要 C.free,确保程序包含 stdlib.h)
func C.CString(string) *C.char
// 这里同样需要使用C.free释放内存
func C.CBytes([]byte) unsafe.Pointer
// C 字符串转换为 Go 字符串
func C.GoString(*C.char) string
// 有明确长度的 C 数据转换为 Go 字符串
func C.GoStringN(*C.char, C.int) string
// 有明确长度的 C 数据转换为 Go []byte
func C.GoBytes(unsafe.Pointer, C.int) []byte
官方提供的函数计算字符串的长度 和 获取字符串的地址:
size_t _GoStringLen(_GoString_ s);
const char *_GoStringPtr(_GoString_ s);
参数类型是_GoString_ s,第一个方法返回Go字符串的长度,第二个方法返回指向这个字符串的char*指针,下面为示例代码 :
package main
/*
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 这两个函数要声明在序言中
size_t _GoStringLen(_GoString_ s);
const char *_GoStringPtr(_GoString_ s);
void PrintGoStringInfo(char* s, size_t count)
{
// 注意,s尾部没有"\0", 在C中直接当字符串处理会出错
char* buf = malloc((count + 1) * sizeof(char)); //
memset(buf, 0, count + 1);
memcpy(buf, s, count);
printf("%s\n", buf);
printf("sizeof goString: %ld\n", count);
free(buf);
}
*/
import "C"
func main() {
str := "hello world"
C.PrintGoStringInfo(C._GoStringPtr(str), C._GoStringLen(str))
}
- 需要注意的是 _GoStringPtr 返回的 char* 尾部是不包含的
\0的,在 C 中直接当字符串处理会出错 - 这两个函数仅可在序言中使用,不能在其他的C文件中使用,C代码绝不能通过 _GoStringPtr 返回的指针修改其指向的内容
注意:这两个函数可以很方便的将Go string转换为C的char*,如果使用 _GoStringPtr 传入一个临时的 string 到C中,在C中应拷贝一份副本到C内存中,尤其是在一些异步调用的过程中,从官方关于cgo的文档看来,这两个函数似乎并不保证传入的临时Go string类型不会被gc回收
GO 语言中访问 C 语言的 struct ,union,enum,对应关系:
示例
- go 字符串类型 与 c++ char* 互转使用
#ifndef SM4_EX_H
#define SM4_EX_H
typedef unsigned char u_char;
extern int g_loadCout;
//
#ifdef __cplusplus
extern "C" {
#endif
//
/*
* 对数据进行 sm4 加密
* _out 解密的数据
* _outLen 输入时,为_out缓冲区的长度。输出时,为_out中返回的数据的长度
* _plaint 明文,被加密的数据
* _plaintLen _plaint数据的长度
* _key 加密密钥,长度固定为 32 byte
*/
void encryptData(char *_out, int *_outLen, const char *_plaint, int _plaintLen, char *_key, int _keyLen);
//encryptData() 函数的,解密操作
void decryptData(char *_out, int *_outLen, const char *_cipher, int _cipherLen, char *_key, int _keyLen);
//
#ifdef __cplusplus
}
#endif
//
#endif // SM4_EX_H
package main
/*
#cgo CFLAGS: -I./
#cgo LDFLAGS: -L./ -lsm4 -lstdc++ -lcurl //-lsm4 引入对应C程序的动态库文件
#include "sm4.h" //引入对应C程序的头文件
#include <stdlib.h>
#include <stdio.h>
*/
import "C"
import (
"encoding/base64"
"encoding/hex"
"fmt"
"unsafe"
)
func main() {
txt := "123"
in := C.CString(txt)
defer C.free(unsafe.Pointer(in)) // 释放分配在C中的内存,否则会造成内存泄露
inlen := C.int(len(txt))
keytxt := "123456789"
b, _ := hex.DecodeString(keytxt)
keylen := C.int(len(keytxt))
key := (*C.char)(unsafe.Pointer(&b[0]))
len1 := len(txt) + 100
outlen := C.int(len1)
outtxt := make([]byte, len1)
out := ((*C.char)(unsafe.Pointer(&outtxt[0])))
C.encryptData(out, &outlen, in, inlen, key, keylen/2)
fmt.Printf("明文:<%s>长度<%d>;加密后密文:<%v>长度<%d>\n", txt, int(inlen), C.GoStringN(out, outlen), int(outlen))
encoded := base64.StdEncoding.EncodeToString([]byte(C.GoStringN(out, outlen)))
fmt.Printf("加密后密文base64:<%s>\n", encoded)
d := hex.EncodeToString([]byte(C.GoStringN(out, outlen)))
fmt.Printf("加密后密文转16进制<%s>\n:", d)
//----------------解密-----------------------------
fmt.Println("------------------")
entxt, _ := base64.StdEncoding.DecodeString(encoded) //密文base64解码
entxt2 := C.CString(string(entxt))
defer C.free(unsafe.Pointer(entxt2)) // 释放分配在C中的内存,否则会造成内存泄露
entxtlen := C.int(len(string(entxt)))
len2 := len(string(entxt)) + 100
out2len := C.int(len2)
outtxt2 := make([]byte, len2)
out2 := ((*C.char)(unsafe.Pointer(&outtxt2[0])))
C.decryptData(out2, &out2len, entxt2, entxtlen, key, keylen/2)
fmt.Println("输入密文长度:", int(entxtlen), entxtlen)
fmt.Printf("解密后明文:<%s>,长度<%d>\n\n", C.GoStringN(out2, out2len), int(out2len))
}
- go 字符串与 c++ unsigned char 互转
void sm4_ecb(const unsigned char *in,
unsigned char *out,
const unsigned char *keyValue,
int length,
int enc);
package main
/*
#cgo CFLAGS: -I./
#cgo LDFLAGS: -L./ -lsm4
#include "sm4.h"
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
in := []byte("hello world!")
key := []byte("1234567890abcdef")
var out []byte
C.sm4_ecb((*C.uchar)(unsafe.Pointer(&in[0])),
(*C.uchar)(unsafe.Pointer(&out)),
(*C.uchar)(unsafe.Pointer(&key[0])),
(C.int)(len(in)),
(C.int)(0),
)
fmt.Println("out:", out)
}
- 释放 C 中的内存
package print
// #include <stdio.h>
// #include <stdlib.h>
import "C"
import "unsafe"
func Print(s string) {
cs := C.CString(s)
defer C.free(unsafe.Pointer(cs)) // 释放分配在C中的内存,否则会造成内存泄露
C.fputs(cs, (*C.FILE)(C.stdout))
}