go 语言高级特性--CGO的使用

简介: cgo 是 go 语言里面的一个特性,属于 go 的高级用法,我们可以使用 cgo 来实现 go 语言调用 c 语言程序。

介绍

cgo 是 go 语言里面的一个特性,属于 go 的高级用法,我们可以使用 cgo 来实现 go 语言调用 c 语言程序。

1. 简单示例:

packagemain/*#include static void Hello(const char* s) {puts(s);}*/import"C"funcmain() {
hi :=C.CString("Hello World!")
C.Hello(hi)
deferC.free(unsafe.Pointer(hi)) // 释放分配在C中的内存,否则会造成内存泄露}
  • import "C" 语句启用 CGO 特性
  • 引入 C 语言的  头文件
  • CGO 包的 C.CString 函数将 Go 语言字符串转为 C 语言字符串
  • C.CString 创建的 C 语言字符串如果没有被正确释放,会导致内存泄漏,另外程序退出后操作系统也会自动回收程序的所有资源。

2. 工具链

要使用 CGO 特性,需要安装 C/C++ 构建工具链,在 macOS 和 Linux 下是要安装 GCC,在 windows 下是需要安装 MinGW 工具。同时需要保证环境变量 CGO_ENABLED 被设置为 1,这表示 CGO 是被启用的状态。在本地构建时 CGO_ENABLED 默认是启用的,当交叉构建时 CGO 默认是禁止的。比如要交叉构建 ARM 环境运行的 Go 程序,需要手工设置好 C/C++ 交叉构建的工具链,同时开启 CGO_ENABLED 环境变量。然后通过 import "C" 语句启用 CGO 特性。

动态库路径

image.png


1. 编译目标代码时指定的动态库搜索路径(指的是用-wl,rpath或-R选项而不是-L);

example: gcc -Wl,-rpath,/home/arc/test,-rpath,/lib/,-rpath,/usr/lib/,-rpath,/usr/local/lib test.c

2. 环境变量LD_LIBRARY_PATH指定的动态库搜索路径;(例如:export LD_LIBRARY_PATH=/root/test/env/lib

3. 配置文件/etc/ld.so.conf中指定的动态库搜索路径;(更改/etc/ld.so.conf文件后记得一定要执行命令:ldconfig,该命令会将/etc/ld.so.conf文件中所有路径下的库载入内存中)

4. 默认的动态库搜索路径/lib;

5. 默认的动态库搜索路径/usr/lib。

类型转换

// Go 字符串转换为 C 字符串// C 字符串使用 malloc 来分配到 C 堆里。// 调用方需要把它释放掉,比如调用 C.free (如果需要 C.free,确保程序包含 stdlib.h)funcC.CString(string) *C.char// 这里同样需要使用C.free释放内存funcC.CBytes([]byte) unsafe.Pointer// C 字符串转换为 Go 字符串funcC.GoString(*C.char) string// 有明确长度的 C 数据转换为 Go 字符串funcC.GoStringN(*C.char, C.int) string// 有明确长度的 C 数据转换为 Go []bytefuncC.GoBytes(unsafe.Pointer, C.int) []byte

官方提供的函数**计算字符串的长度** 和 **获取字符串的地址**:

size_t_GoStringLen(_GoString_s);
constchar*_GoStringPtr(_GoString_s);


参数类型是_GoString_ s,第一个方法返回Go字符串的长度,第二个方法返回指向这个字符串的char*指针,下面为示例代码  :

packagemain/*#include #include #include // 这两个函数要声明在序言中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"funcmain() {
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,对应关系:

image.png


image.png


示例

1. go 字符串类型 与 c++ char* 互转使用

#ifndef SM4_EX_H#define SM4_EX_Htypedefunsignedcharu_char;
externintg_loadCout;
//#ifdef __cplusplusextern"C" {
#endif///** 对数据进行 sm4 加密* _out     解密的数据* _outLen  输入时,为_out缓冲区的长度。输出时,为_out中返回的数据的长度* _plaint      明文,被加密的数据* _plaintLen   _plaint数据的长度* _key 加密密钥,长度固定为 32 byte*/voidencryptData(char*_out, int*_outLen, constchar*_plaint, int_plaintLen, char*_key, int_keyLen);
//encryptData() 函数的,解密操作voiddecryptData(char*_out, int*_outLen, constchar*_cipher, int_cipherLen, char*_key, int_keyLen);
//#ifdef __cplusplus}
#endif//#endif // SM4_EX_H


packagemain/*#cgo CFLAGS: -I./#cgo LDFLAGS: -L./ -lsm4 -lstdc++ -lcurl //-lsm4 引入对应C程序的动态库文件#include "sm4.h" //引入对应C程序的头文件#include #include */import"C"import (
"encoding/base64""encoding/hex""fmt""unsafe")
funcmain() {
txt :="123"in :=C.CString(txt)
deferC.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) +100outlen :=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))
deferC.free(unsafe.Pointer(entxt2)) // 释放分配在C中的内存,否则会造成内存泄露entxtlen :=C.int(len(string(entxt)))
len2 :=len(string(entxt)) +100out2len :=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))
}

2. go 字符串与 c++ unsigned char 互转

voidsm4_ecb(constunsignedchar*in,
unsignedchar*out,
constunsignedchar*keyValue,
intlength,
intenc);


packagemain/*#cgo CFLAGS: -I./#cgo LDFLAGS: -L./ -lsm4#include "sm4.h"*/import"C"import (
"fmt""unsafe")
funcmain() {
in := []byte("hello world!")
key := []byte("1234567890abcdef")
varout []byteC.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)
}

3. 释放 C 中的内存

packageprint// #include // #include import"C"import"unsafe"funcPrint(sstring) {
cs :=C.CString(s)
deferC.free(unsafe.Pointer(cs))  // 释放分配在C中的内存,否则会造成内存泄露C.fputs(cs, (*C.FILE)(C.stdout))
}

image.png


相关文章
|
2月前
|
存储 安全 Java
【Golang】(4)Go里面的指针如何?函数与方法怎么不一样?带你了解Go不同于其他高级语言的语法
结构体可以存储一组不同类型的数据,是一种符合类型。Go抛弃了类与继承,同时也抛弃了构造方法,刻意弱化了面向对象的功能,Go并非是一个传统OOP的语言,但是Go依旧有着OOP的影子,通过结构体和方法也可以模拟出一个类。
208 1
|
4月前
|
Cloud Native 安全 Java
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
300 1
|
4月前
|
Cloud Native Go API
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
402 0
|
4月前
|
Cloud Native Java Go
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
261 0
|
4月前
|
Cloud Native Java 中间件
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
231 0
|
4月前
|
Cloud Native Java Go
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
335 0
|
4月前
|
数据采集 Go API
Go语言实战案例:多协程并发下载网页内容
本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,讲解如何使用 Goroutine 和 Channel 实现多协程并发抓取网页内容,提升网络请求效率。通过实战掌握高并发编程技巧,构建爬虫、内容聚合器等工具,涵盖 WaitGroup、超时控制、错误处理等核心知识点。
|
10月前
|
编译器 Go
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
|
10月前
|
运维 监控 算法
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
|
4月前
|
数据采集 JSON Go
Go语言实战案例:实现HTTP客户端请求并解析响应
本文是 Go 网络与并发实战系列的第 2 篇,详细介绍如何使用 Go 构建 HTTP 客户端,涵盖请求发送、响应解析、错误处理、Header 与 Body 提取等流程,并通过实战代码演示如何并发请求多个 URL,适合希望掌握 Go 网络编程基础的开发者。

热门文章

最新文章