类型对应关系
1. 基础数据类型
ctypes 类型 | C 类型 | Python 类型 |
c_bool | _Bool(C99+)或 bool | bool |
c_char | char | 单字节字符串(长度1的 bytes) |
c_wchar | wchar_t | 单字符 Unicode 字符串 |
c_byte | signed char | int |
c_ubyte | unsigned char | int |
c_short | short | int |
c_ushort | unsigned short | int |
c_int | int | int |
c_uint | unsigned int | int |
c_long | long | int |
c_ulong | unsigned long | int |
c_longlong | long long(C99+) | int |
c_ulonglong | unsigned long long | int |
c_float | float | float |
c_double | double | float |
c_longdouble | long double | float(具体精度依赖平台) |
c_size_t | size_t | int |
c_ssize_t | ssize_t | int |
2.指针与字符串类型
ctypes 类型 | C 类型 | 说明 |
c_char_p | char* | 以 NUL 结尾的 C 字符串指针(字节流) |
c_wchar_p | wchar_t* | 宽字符字符串指针 |
c_void_p | void* | 通用指针(地址) |
POINTER(type) | type* | 通用指针类型(需指定具体类型) |
3.数组与复合类型
数组:通过 * 运算符定义:
- 如 c_int * 10 对应 C 的 int类型的数组,包含10个int类型;
- 如c_char_p * 10对应C的字符串类型的数组,包含10个char数组(c中的字符串用char数组表示)
结构体:继承 ctypes.Structure 并定义 _fields_ 字段列表:
class MyStruct(ctypes.Structure):
_fields_ = [("x", c_int), ("y", c_float)] # 对应 C 的 `struct { int x; float y; }`
联合体:继承 ctypes.Union,语法类似结构体
4. 特殊整型类型(别名类型)
ctypes 类型 | C 类型 | 说明 |
c_int8 | int8_t | 8 位有符号整型(别名 c_byte) |
c_int16 | int16_t | 16 位有符号整型(别名 c_short) |
c_int32 | int32_t | 32 位有符号整型(别名 c_int) |
c_int64 | int64_t | 64 位有符号整型(别名 c_longlong) |
注意事项
1.平台差异:
- c_long 和 c_int 的位宽可能因平台而异(例如 32 位和 64 位系统)
- c_longdouble 在 sizeof(long double) == sizeof(double) 的平台上等价于 c_double
2. 内存管理:
- 使用 c_char_p 接收 C 返回的字符串时,需确保 C 侧内存由 Python 显式释放(若动态分配)
3. 编码与类型转换:
- 字符串需通过 encode("utf-8") 转为字节流后再传给 c_char_p
- c_void_p 可接受整数表示的地址或 None
4.结构体对齐:
- 可通过 _pack_ 属性调整结构体字段的内存对齐方式(如 _pack_ = 4 表示 4 字节对齐)
使用ctypes调用Go中的方法
Go编译.so文件指令
go build -buildmode=c-shared -o tt.so main.go
c_int类型
Go方法中接收一个c_int类型的数字,处理后,返回一个c_int类型的数字
Go代码
Note:Go语言中int类型跟操作系统有关,在32位系统上通常为32位,在64位系统上通常为64位,C.int转为Go中的int类型,直接使用强转就可以。前提要保证数据不能截断
package main
import "C"
import (
"fmt"
)
//export CintDemo
func CintDemo(number C.int) C.int {
go_number := int(number)
fmt.Printf("c_int = %v\n", go_number)
return number * 2
}
func main() {}
Python代码
from ctypes import CDLL, c_int
lib = CDLL("./tt.so")
lib.CintDemo.argtypes = [c_int]
lib.CintDemo.restype = c_int
res = lib.CintDemo(101)
print(f"response from lib: {res}")
输出结果
c_int = 101
response from lib: 202
c_char类型
Go方法中接收一个c_char类型,处理后,返回一个c_char类型
c中字符串以指针的类型传递,输入输出都采用指针类型
Go代码
package main
import "C"
//export CcharDemo
func CcharDemo(str *C.char) *C.char {
goStr := C.GoString(str)
newStr := goStr + " response from Go"
cStr := C.CString(newStr)
return cStr
}
func main() {}
Python代码
from ctypes import CDLL, c_char_p
lib = CDLL("./tt.so")
lib.CcharDemo.argtypes = [c_char_p]
lib.CcharDemo.restype = c_char_p
res = lib.CcharDemo("str from python".encode("utf8"))
print(f"response from lib: {res.decode('utf8')}")
输出结果
response from lib: str from python response from Go
Process finished with exit code 0
c_double类型
Go方法中接收一个c_double类型,处理后,返回一个c_double类型
Go代码
package main
import "C"
import "fmt"
//export CdoubleDemo
func CdoubleDemo(number C.double) C.double {
goNumber := float64(number)
fmt.Printf("goNumber: %v; type: %T\n", goNumber, goNumber)
return number * 3
}
func main() {}
Python代码
from ctypes import CDLL, c_double
lib = CDLL("./tt.so")
lib.CdoubleDemo.argtypes = [c_double]
lib.CdoubleDemo.restype = c_double
res = lib.CdoubleDemo(66.66)
print(f"response from lib: {res}")
输出结果
goNumber: 66.66; type: float64
response from lib: 199.98
Process finished with exit code 0
c_bool类型
Go方法中接收一个c_bool类型,处理后,返回一个c_bool类型
Go代码
note: 记得引入stdbool.h头文件,在Go中想要直接使用C_bool类型,就要引入该文件,引入后,可以使用bool作为_Bool的别名,使代码更易读懂和与其他语言中布尔值类型标识相统一,同时该头文件还定义了ture跟false两个宏,分别对应_Bool类型的1和0
_Bool类型是C语言中表示布尔值的基本类型,在内存中通常占用1个字节。它只有两个取值,0表示假,非0表示真
C.bool值的写法
package main
/*
# include
*/
import "C"
import "fmt"
//export CboolDemo
func CboolDemo(flag C.bool) C.bool {
fmt.Println("flag from python =", flag)
if flag {
fmt.Println("bool value is true")
flag = false
} else {
fmt.Println("bool value is false")
flag = true
}
// goFlag := bool(flag)
return flag
}
func main() {
C._Bool值的写法
package main
import "C"
import (
"fmt"
"unsafe"
)
//export CboolDemo
func CboolDemo(flag C._Bool) C._Bool {
fmt.Printf("flag from python: %v, size: %v\n", flag, unsafe.Sizeof(flag))
if flag {
fmt.Println("bool value is true")
flag = false
} else {
fmt.Println("bool value is false")
flag = true
}
return flag
}
func main() {}
Python代码
from ctypes import CDLL, c_bool
lib = CDLL("./tt.so")
lib.CboolDemo.argtypes = [c_bool]
lib.CboolDemo.restype = c_bool
res = lib.CboolDemo(False)
print(f"response from lib: {res}")
输出结果
运行C._Bool类型的代码
flag from python: false, size: 1
bool value is false
response from lib: True
Process finished with exit code 0
c_void_p类型
Go方法中接收一个*C.char类型,处理后,返回一个结构体类型的指针
note:在使用指针类型时,由于Go语言有自己的垃圾回收机制,还有就是Go语言可能会将数据在内存中的保存位置进行移动,这样指针就会跟着移动,所以在操作指针类型时,最好使用C库中的malloc分配内存,然后再手动释放内存,记得使用完成后,一定要及时的释放Go中开辟的内存,防止内存泄漏。
Note: 使用C.malloc() 时,不用先定义好结构体,因为如果结构体变量通过声明定义,其内存位于栈区或者静态存储区,声明周期由作用域或者程序运行周期决定,再次执行malloc时,只是在堆区重新开辟了一块空间,所以在处理结构体时,只需要先malloc一个结构体,这样会在堆区为结构体分配好内存,分配好内存以后,通过强制转换,为结构体赋值,结构体处理完成以后,再强转为对应的结构体返回即可
不适用malloc(), Python调用时,会产生如下错误:
panic: runtime error: cgo result is unpinned Go pointer or points to unpinned Go pointer
Go代码
1. 使用头文件,并在Go中提前定义好C语言格式中结构体
package main
/*
#include
#include
typedef struct {
char* result;
} StringResult;
StringResult* CpointerDemo(char* input);
*/
import "C"
import (
"fmt"
"unsafe"
)
//export CpointerDemo
func CpointerDemo(msg *C.char) *C.StringResult {
goStr := C.GoString(msg)
goStr = goStr + " content from Go code"
cStr := C.CString(goStr)
cPtr := C.malloc(C.size_t(unsafe.Sizeof(C.StringResult{})))
(*C.StringResult)(cPtr).result = cStr
return (*C.StringResult)(cPtr)
}
//export FreeeResource
func FreeeResource(handle *C.StringResult) {
gPtr := unsafe.Pointer(handle)
// 如果需要,可以使用如下办法将结构体中的数据再次使用
msgPtr := &(*C.StringResult)(gPtr).result
fmt.Println("msgPtr:", C.GoString(*msgPtr))
// 释放内存
C.free(gPtr)
gPtr = nil
fmt.Println("free memory successfully")
}
func main() {}
2. 使用C语言中的void*
void*被称为无类型指针,它不指向任何特定的数类型的数据,只是一个通用的指针,用于存储内存地址,它可以指向任何类型的数据对象,但在使用时通常需要进行类型转换,以便让编译器知道实际指向的数据类型。
主要用途:
通用数据存储和传递:在函数间传递数据时,如果不确定具体的数据类型,可以使用void*。如malloc函数用于动态分配内存,返回值为void*,可根据需要转换为其他类型的指针,int *ptr = (int*)malloc(sizeof(int));。 实现泛型编程:C语言没有像C++那样的模板机制,但可以使用void*实现类似泛型的功能,如qsort函数可以对不同类型的数据排序:void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));,通过void*指针能处理各种数据类型的数组。 作为函数指针类型:函数指针可以指向不同参数和返回值类型的函数,有时会用void*来表示函数指针类型,尤其是在需要将函数指针作为参数传递给其他函数,且不关心函数具体参数和返回值类型时。
使用注意事项:
类型转换:使用void*指针访问或操作数据时,必须进行显示类型转换,以确保编译器按正确数据类型处理。 指针运算:void*指针不能直接进行指针运算,因为编译器不知道其指向数据类型大小,无法确定指针移动步长,需转换为具体类型指针后再进行运算。
package main
/*
# include
typedef void* PointerDemo;
*/
import "C"
import (
"fmt"
"unsafe"
)
type SturctDemo struct {
Msg *string
}
//export CpointerDemo
func CpointerDemo(msg *C.char) *C.PointerDemo {
goStr := C.GoString(msg)
goStr = goStr + " content from Go code"
// 直接在堆内存为结构体分配空间
ptr := (*SturctDemo)(C.malloc(C.size_t(unsafe.Sizeof(SturctDemo{}))))
if ptr == nil {
panic("malloc failed")
}
ptr.Msg = &goStr
return (*C.PointerDemo)(unsafe.Pointer(ptr))
}
//export FreeeResource
func FreeeResource(handle *C.PointerDemo) {
gPtr := unsafe.Pointer(handle)
// 如果需要,可以使用如下办法将结构体中的数据再次使用
msgPtr := (*SturctDemo)(gPtr).Msg
fmt.Println("msgPtr:", *msgPtr)
// 释放内存
C.free(gPtr)
gPtr = nil
fmt.Println("free memory successfully")
}
func main() {}
Python代码
from ctypes import CDLL, c_char_p, c_void_p
lib = CDLL("./tt.so")
lib.CpointerDemo.argtypes = [c_char_p]
lib.CpointerDemo.restype = c_void_p
lib.FreeeResource.argtypes = [c_void_p]
res = lib.CpointerDemo("message from python".encode("utf8"))
print(f"response from lib: {res}")
lib.FreeeResource(res)
输出结果
response from lib: 2322181954544
msgPtr: message from python content from Go code
free memory successfully
Process finished with exit code 0
int 数组类型
Go方法中接收一个int类型数组,处理后,返回一个int类型数组
Go代码
package main
/*
#include
typedef struct {
int* data;
int len;
} IntArray;
*/
import "C"
import (
"fmt"
"unsafe"
)
//export CintArrayDemo
func CintArrayDemo(cArray *C.int, cLength C.int) *C.IntArray {
// 转换C数组为Go切片
goSlice := (*[1 << 30]C.int)(unsafe.Pointer(cArray))[:cLength:cLength]
// 处理元素,将每个元素*2
result := make([]int, cLength)
for i := 0; i < int(cLength); i++ {
result[i] = int(goSlice[i]) * 2
}
wrapper := (*C.IntArray)(C.malloc(C.size_t(unsafe.Sizeof(C.IntArray{}))))
wrapper.data = (*C.int)(C.malloc(C.size_t(cLength * C.sizeof_int)))
wrapper.len = cLength
for i := 0; i < int(cLength); i++ {
(*[1 << 30]C.int)(unsafe.Pointer(wrapper.data))[i] = C.int(result[i])
}
return wrapper
}
//export FreeeResource
func FreeeResource(handle *C.IntArray) {
C.free(unsafe.Pointer(handle.data))
C.free(unsafe.Pointer(handle))
fmt.Println("free memory successfully")
}
func main() {}
Python代码
arr = (c_int * len(input_data))(*input_data)
这行代码就使用到了“数组与复合类型”章节提到的语法,本质是C语言数组的实例化对象,其行为类似于C语言中的数组名(隐式指向首元素地址的指针),在实际跨语言交互中,arr 会被自动转换为指向首元素的指针(int*),满足与C函数交互的需求。
from ctypes import CDLL, c_int, POINTER, Structure
class IntArray(Structure):
_fields_ = [
("data", POINTER(c_int)),
("len", c_int)
]
lib = CDLL("./tt.so")
lib.CintArrayDemo.argtypes = [POINTER(c_int), c_int]
lib.CintArrayDemo.restype = POINTER(IntArray)
lib.FreeeResource.argtypes = [POINTER(IntArray)]
input_data = [10, 20, 30, 40]
arr = (c_int * len(input_data))(*input_data)
# print(arr)
ptr = lib.CintArrayDemo(arr, len(input_data))
result = [ptr.contents.data[i] for i in range(ptr.contents.len)]
print(f"response from lib: {result}")
lib.FreeeResource(ptr)
运行结果
response from lib: [20, 40, 60, 80]
free memory successfully
字符串数组类型
Go方法中接收一个字符串类型(char*)数组指针(**char),处理后,返回一个字符串(*char)类型数组指针(**char)
Go代码
package main
/*
#include
*/
import "C"
import (
"fmt"
"strings"
"unsafe"
)
//export CcharArrayDemo
func CcharArrayDemo(input **C.char, length C.int) **C.char {
// 将C字符串数组转换为Go的[]string
cStrings := (*[1 << 30]*C.char)(unsafe.Pointer(input))[:length:length]
goStrings := make([]string, length)
for i := 0; i < int(length); i++ {
goStrings[i] = C.GoString(cStrings[i])
}
// 处理字符串数组,将所有的字符串转为大写
for i, v := range goStrings {
goStrings[i] = strings.ToUpper(v)
}
// 将Go字符串数组转回C字符串数组
cArray := C.malloc(C.size_t(length) * C.size_t(unsafe.Sizeof(uintptr(0))))
cStringsOut := (*[1 << 30]*C.char)(cArray)
for i, v := range goStrings {
cStringsOut[i] = C.CString(v)
}
return (**C.char)(cArray)
}
//export FreeeResource
func FreeeResource(cArray **C.char, length C.int) {
cStrings := (*[1 << 30]*C.char)(unsafe.Pointer(cArray))[:length:length]
for i := 0; i < int(length); i++ {
C.free(unsafe.Pointer(cStrings[i]))
}
C.free(unsafe.Pointer(cArray))
fmt.Println("free memory successfully")
}
func main() {}
Python代码
from ctypes import CDLL, c_int, POINTER, c_char_p, string_at
lib = CDLL("./tt.so")
lib.CcharArrayDemo.argtypes = [POINTER(c_char_p), c_int]
lib.CcharArrayDemo.restype = POINTER(c_char_p)
lib.FreeeResource.argtypes = [POINTER(c_char_p), c_int]
input_strings = ["hello", "world"]
input_length = len(input_strings)
c_input = (c_char_p * input_length)()
for i, v in enumerate(input_strings):
c_input[i] = v.encode("utf8")
ptr = lib.CcharArrayDemo(c_input, input_length)
result = []
for x in range(input_length):
result.append(string_at(ptr[x]).decode("utf8"))
print(f"response from lib: {result}")
lib.FreeeResource(ptr, input_length)
运行结果
response from lib: ['HELLO', 'WORLD']
free memory successfully