百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Python ctypes详解(ctypes numpy)

itomcoil 2025-03-25 14:27 22 浏览

类型对应关系

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.数组与复合类型

数组:通过 * 运算符定义:

  1. 如 c_int * 10 对应 C 的 int类型的数组,包含10个int类型;
  2. 如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

相关推荐

python创建文件夹,轻松搞定,喝咖啡去了

最近经常在录视频课程,一个课程下面往往有许多小课,需要分多个文件夹来放视频、PPT和案例,这下可好了,一个一个手工创建,手酸了都做不完。别急,来段PYTHON代码,轻松搞定,喝咖啡去了!import...

如何编写第一个Python程序_pycharm写第一个python程序

一、第一个python程序[掌握]python:python解释器,将python代码解释成计算机认识的语言pycharm:IDE(集成开发环境),写代码的一个软件,集成了写代码,...

Python文件怎么打包为exe程序?_python3.8打包成exe文件

PyInstaller是一个Python应用程序打包工具,它可以将Python程序打包为单个独立可执行文件。要使用PyInstaller打包Python程序,需要在命令行中使用py...

官方的Python环境_python环境版本

Python是一种解释型编程开发语言,根据Python语法编写出来的程序,需要经过Python解释器来进行执行。打开Python官网(https://www.python.org),找到下载页面,选择...

[编程基础] Python配置文件读取库ConfigParser总结

PythonConfigParser教程显示了如何使用ConfigParser在Python中使用配置文件。文章目录1介绍1.1PythonConfigParser读取文件1.2Python...

Python打包exe软件,用这个库真的很容易

初学Python的人会觉得开发一个exe软件非常复杂,其实不然,从.py到.exe文件的过程很简单。你甚至可以在一天之内用Python开发一个能正常运行的exe软件,因为Python有专门exe打包库...

2025 PyInstaller 打包说明(中文指南),python 打包成exe 都在这里

点赞标记,明天就能用上这几个技巧!linux运维、shell、python、网络爬虫、数据采集等定定做,请私信。。。PyInstaller打包说明(中文指南)下面按准备→基本使用→常用...

Python自动化办公应用学习笔记40—文件路径2

4.特殊路径操作用户主目录·获取当前用户的主目录路径非常常用:frompathlibimportPathhome_dir=Path.home()#返回当前用户主目录的Path对象...

Python内置tempfile模块: 生成临时文件和目录详解

1.引言在Python开发中,临时文件和目录的创建和管理是一个常见的需求。Python提供了内置模块tempfile,用于生成临时文件和目录。本文将详细介绍tempfile模块的使用方法、原理及相关...

python代码实现读取文件并生成韦恩图

00、背景今天战略解码,有同学用韦恩图展示各个产品线的占比,效果不错。韦恩图(Venndiagram),是在集合论数学分支中,在不太严格的意义下用以表示集合的一种图解。它们用于展示在不同的事物群组之...

Python技术解放双手,一键搞定海量文件重命名,一周工作量秒搞定

摘要:想象一下,周五傍晚,办公室的同事们纷纷准备享受周末,而你,面对着堆积如山的文件,需要将它们的文件名从美国日期格式改为欧洲日期格式,这似乎注定了你将与加班为伍。但别担心,Python自动化办公来...

Python路径操作的一些基础方法_python路径文件

带你走进@机器人时代Discover点击上面蓝色文字,关注我们Python自动化操作文件避开不了路径操作方法,今天我们来学习一下路径操作的一些基础。Pathlib库模块提供的路径操作包括路径的...

Python爬取下载m3u8加密视频,原来这么简单

1.前言爬取视频的时候发现,现在的视频都是经过加密(m3u8),不再是mp4或者avi链接直接在网页显示,都是经过加密形成ts文件分段进行播放。今天就教大家如果通过python爬取下载m3u8加密视频...

探秘 shutil:Python 高级文件操作的得力助手

在Python的标准库中,shutil模块犹如一位技艺精湛的工匠,为我们处理文件和目录提供了一系列高级操作功能。无论是文件的复制、移动、删除,还是归档与解压缩,shutil都能以简洁高效的方式完成...

怎么把 Python + Flet 开发的程序,打包为 exe ?这个方法很简单!

前面用Python+Flet开发的“我的计算器v3”,怎么打包为exe文件呢?这样才能分发给他人,直接“双击”运行使用啊!今天我给大家分享一个简单的、可用的,把Flet开发的程序打包为...