本文最后更新于:February 25, 2025 pm
本文作者:[wangwenhai] # 概要:Golang+CGO技术的应用。
在Go语言开发中,有时候需要借助C语言已有的库来实现一些功能,或者使用一些底层的系统调用,这时Cgo就派上了用场。Cgo是Go语言提供的一种机制,它允许Go代码调用C代码,从而将Go语言的高效并发和C语言的底层性能优势结合起来。本文将详细介绍Cgo的基本语法、类型转换以及实际应用。
Cgo技术的应用
一、引言
在Go语言开发中,有时候需要借助C语言已有的库来实现一些功能,或者使用一些底层的系统调用,这时Cgo就派上了用场。Cgo是Go语言提供的一种机制,它允许Go代码调用C代码,从而将Go语言的高效并发和C语言的底层性能优势结合起来。本文将详细介绍Cgo的基本语法、类型转换以及实际应用。
二、Cgo基本语法
2.1 简单示例
下面是一个简单的Cgo示例,展示了如何在Go代码中调用C函数:
package main
/*
#include <stdio.h>
void printHello() {
printf("Hello from C!\n");
}
*/
import "C"
func main() {
C.printHello()
}
在这个示例中,我们在Go代码文件中使用注释块 /* ... */
包含了C代码,其中定义了一个简单的函数 printHello
。然后通过 import "C"
引入Cgo支持,就可以在Go代码中直接调用这个C函数。
2.2 注意事项
- 注释块位置:包含C代码的注释块必须紧跟在
package
声明之后,并且在import "C"
之前。 - 编译环境:Cgo依赖于C编译器,因此需要确保系统中已经安装了合适的C编译器(如GCC)。
三、Cgo中的类型转换
3.1 基本数据类型转换
C和Go有各自的基本数据类型,在使用Cgo时需要进行相应的转换。以下是一些常见的基本数据类型转换示例:
C 类型 | Go 类型 |
---|---|
int |
C.int 转换为 int :goInt := int(cInt) int 转换为 C.int :cInt := C.int(goInt) |
float |
C.float 转换为 float32 :goFloat := float32(cFloat) float32 转换为 C.float :cFloat := C.float(goFloat) |
double |
C.double 转换为 float64 :goDouble := float64(cDouble) float64 转换为 C.double :cDouble := C.double(goDouble) |
char |
C.char 转换为 byte :goByte := byte(cChar) byte 转换为 C.char :cChar := C.char(goByte) |
3.2 字符串类型转换
C语言使用以 '\0'
结尾的字符数组来表示字符串,而Go使用 string
类型。在Cgo中进行字符串转换时,需要注意内存管理。
3.2.1 Go字符串转C字符串
package main
/*
#include <stdio.h>
#include <stdlib.h>
void printCString(const char* str) {
printf("C received: %s\n", str);
}
*/
import "C"
import (
"unsafe"
)
func main() {
goStr := "Hello from Go!"
cStr := C.CString(goStr)
defer C.free(unsafe.Pointer(cStr))
C.printCString(cStr)
}
在这个示例中,使用 C.CString
函数将Go字符串转换为C字符串。需要注意的是,C.CString
会分配新的内存,因此在使用完后需要调用 C.free
释放内存,避免内存泄漏。
3.2.2 C字符串转Go字符串
package main
/*
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* getCString() {
char* str = (char*)malloc(12 * sizeof(char));
strcpy(str, "Hello from C");
return str;
}
*/
import "C"
import (
"unsafe"
)
func main() {
cStr := C.getCString()
defer C.free(unsafe.Pointer(cStr))
goStr := C.GoString(cStr)
println(goStr)
}
这里使用 C.GoString
函数将C字符串转换为Go字符串。同样,由于C字符串是动态分配的内存,使用完后需要释放。
3.3 指针类型转换
在Cgo中,指针类型的转换需要特别小心,因为涉及到内存管理和指针操作。
3.3.1 Go指针转C指针
package main
/*
#include <stdio.h>
void printIntPointer(int* ptr) {
printf("Value at pointer: %d\n", *ptr);
}
*/
import "C"
import (
"unsafe"
)
func main() {
goInt := 42
cPtr := (*C.int)(unsafe.Pointer(&goInt))
C.printIntPointer(cPtr)
}
在这个示例中,使用 unsafe.Pointer
作为中间类型,将Go整数的指针转换为C整数的指针。
3.3.2 C指针转Go指针
package main
/*
#include <stdio.h>
#include <stdlib.h>
int* getIntPointer() {
int* ptr = (int*)malloc(sizeof(int));
*ptr = 100;
return ptr;
}
*/
import "C"
import (
"unsafe"
)
func main() {
cPtr := C.getIntPointer()
defer C.free(unsafe.Pointer(cPtr))
goPtr := (*int)(unsafe.Pointer(cPtr))
println(*goPtr)
}
这里将C整数指针转换为Go整数指针,同样要注意内存释放。
四、Cgo的实际应用场景
4.1 调用系统库
在某些情况下,Go语言可能没有直接提供某些系统功能的支持,这时可以通过Cgo调用C语言的系统库来实现。例如,在Linux系统中调用 getuid
函数获取当前用户的UID:
package main
/*
#include <unistd.h>
*/
import "C"
import "fmt"
func main() {
uid := C.getuid()
fmt.Printf("Current user ID: %d\n", int(uid))
}
4.2 复用已有的C库
如果已经有一些成熟的C库,不想重新用Go语言实现,可以使用Cgo在Go项目中复用这些库。例如,使用C语言的OpenSSL库进行加密操作:
package main
/*
#include <openssl/md5.h>
#include <stdio.h>
#include <string.h>
void md5(const char* input, unsigned char* output) {
MD5_CTX md5Context;
MD5_Init(&md5Context);
MD5_Update(&md5Context, input, strlen(input));
MD5_Final(output, &md5Context);
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
input := "Hello, World!"
cInput := C.CString(input)
defer C.free(unsafe.Pointer(cInput))
var output [16]byte
C.md5(cInput, (*C.uchar)(&output[0]))
for i := 0; i < 16; i++ {
fmt.Printf("%02x", output[i])
}
fmt.Println()
}
实战:3轴加速度传感器IIC协议读取数据
畅维通达EN系列网关是一款面向工业、农业、交通运输、科研等领域,集数据采集、通讯、边缘计算于一体的通用设备。搭载了LIS3DHTR三轴加速度传感器,通过IIC协议进行数据读取。
三轴的预设场景:
- 电梯运行异常检测
- 贵重物品异常挪动
- 车载异常速度检测
文件列表
lis3dhtr.h
#ifndef LIS3DHTR_H #define LIS3DHTR_H #include <stdint.h> // 翻译加速度值 double _translateAccValue(uint8_t data); // 四舍五入到指定精度 double roundToPrecision(double value, int precisionFactor); // 读取加速度数据 void readAcceleration(double *xData, double *yData, double *zData); #endif
lis3dhtr.c
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <linux/i2c-dev.h> #include <sys/ioctl.h> #include "lis3dhtr.h" #define LIS3DHTR_ADDR 24 #define DEVICE_ID_REG 0x0F #define CTRL_REG0 0x1E #define OUT_X_MSB_REG 0x29 #define OUT_X_LSB_REG 0x28 #define OUT_Y_MSB_REG 0x2B #define OUT_Y_LSB_REG 0x2A #define OUT_Z_MSB_REG 0x2D #define OUT_Z_LSB_REG 0x2C #define CTRL_REG1_REG 0x20 #define CTRL_REG4_REG 0x23 // 翻译加速度值 double _translateAccValue(uint8_t data) { double resAcc = 0; if (data & 0x80) { // sign, < 0 resAcc = ((0x7F - (data & 0x7F) + 1) / 64.0) * (-1); } else { // > 0 resAcc = (double)data / 64.0; } return resAcc; } // 四舍五入到指定精度 double roundToPrecision(double value, int precisionFactor) { return (double)((int)(value * precisionFactor + 0.5)) / precisionFactor; } // 读取加速度数据 void readAcceleration(double *xData, double *yData, double *zData) { int file; char *filename = "/dev/i2c-1"; if ((file = open(filename, O_RDWR)) < 0) { perror("Failed to open the i2c bus"); return; } if (ioctl(file, I2C_SLAVE, LIS3DHTR_ADDR) < 0) { perror("Failed to acquire bus access and/or talk to slave"); close(file); return; } // 0. test uint8_t rawData; if (read(file, &rawData, 1) != 1) { perror("Failed to read from the i2c bus"); close(file); return; } // 1. set Data rate and low-power mode uint8_t ctrlData = 0x9F; // Low-power mode (5.376 kHz) if (write(file, &ctrlData, 1) != 1) { perror("Failed to write to the i2c bus"); close(file); return; } // 2. set Full-scale and high-resolution disabled ctrlData = 0x00; if (write(file, &ctrlData, 1) != 1) { perror("Failed to write to the i2c bus"); close(file); return; } // 3. read acceleration data uint8_t buffer[5]; if (read(file, buffer, 5) != 5) { perror("Failed to read acceleration data"); close(file); return; } close(file); const int precisionFactor = 10000; *xData = roundToPrecision(_translateAccValue(buffer[0]), precisionFactor); *yData = roundToPrecision(_translateAccValue(buffer[2]), precisionFactor); *zData = roundToPrecision(_translateAccValue(buffer[4]), precisionFactor); }
lis3dhtr.go
package lis3dhtr /* #include "lis3dhtr.h" */ import "C" import "unsafe" // AccelerationData 存储加速度数据 type AccelerationData struct { X float64 Y float64 Z float64 } // ReadAcceleration 读取加速度数据 func ReadAcceleration() AccelerationData { var x, y, z C.double C.readAcceleration(&x, &y, &z) return AccelerationData{ X: float64(x), Y: float64(y), Z: float64(z), } }
main.go
package main import ( "fmt" ) func main() { data := ReadAcceleration() fmt.Printf("X: %.4f, Y: %.4f, Z: %.4f\n", data.X, data.Y, data.Z) }
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!