ctypes buffer (#6311)

* Fix array

* from buffer

* AsBuffer

* Fix instruction

* constructable pointer

* thunk

* in_dll

* fix sizeof/alignment
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2025-11-29 21:17:04 +09:00
1 parent e5aec9d7fd
commit 6782fa2219
13 files changed
+2721 -195

No files matched your search

+1
View File
@@ -50,6 +50,7 @@ stackdepth
stringlib
structseq
subparams
swappedbytes
ticketer
tok_oldval
tvars
+1 -8
View File
@@ -44,7 +44,7 @@ cargo run -- script.py
cargo run
# With specific features
cargo run --features ssl
cargo run --features jit
# Release mode (recommended for better performance)
cargo run --release -- script.py
@@ -176,13 +176,6 @@ cargo build --target wasm32-wasip1 --no-default-features --features freeze-stdli
cargo run --features jit
```
### SSL Support
```bash
# Enable SSL support
cargo run --features ssl
```
## Test Code Modification Rules
**CRITICAL: Test code modification restrictions**
+1 -1
View File
@@ -1,7 +1,7 @@
# The most useful windows datatypes
import ctypes
BYTE = ctypes.c_byte
BYTE = ctypes.c_ubyte
WORD = ctypes.c_ushort
DWORD = ctypes.c_ulong
+449 -18
View File
@@ -9,12 +9,14 @@ pub(crate) mod pointer;
pub(crate) mod structure;
pub(crate) mod thunk;
pub(crate) mod union;
pub(crate) mod util;
use crate::builtins::PyModule;
use crate::class::PyClassImpl;
use crate::stdlib::ctypes::base::{PyCData, PyCSimple, PyCSimpleType};
use crate::{Py, PyRef, VirtualMachine};
pub use crate::stdlib::ctypes::base::{PyCData, PyCSimple, PyCSimpleType};
pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py<PyModule>) {
let ctx = &vm.ctx;
PyCSimpleType::make_class(ctx);
@@ -45,17 +47,17 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
#[pymodule]
pub(crate) mod _ctypes {
use super::base::PyCSimple;
use super::base::{CDataObject, PyCSimple};
use crate::builtins::PyTypeRef;
use crate::class::StaticType;
use crate::convert::ToPyObject;
use crate::function::{Either, FuncArgs, OptionalArg};
use crate::stdlib::ctypes::library;
use crate::{AsObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine};
use crate::{AsObject, PyObjectRef, PyPayload, PyResult, VirtualMachine};
use crossbeam_utils::atomic::AtomicCell;
use std::ffi::{
c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong,
c_ulonglong,
c_ulonglong, c_ushort,
};
use std::mem;
use widestring::WideChar;
@@ -158,6 +160,192 @@ pub(crate) mod _ctypes {
}
}
/// Get alignment for a simple type - for C types, alignment equals size
pub fn get_align(ty: &str) -> usize {
get_size(ty)
}
/// Get the size of a ctypes type from its type object
#[allow(dead_code)]
pub fn get_size_from_type(cls: &PyTypeRef, vm: &VirtualMachine) -> PyResult<usize> {
// Try to get _type_ attribute for simple types
if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm)
&& let Ok(s) = type_attr.str(vm)
{
let s = s.to_string();
if s.len() == 1 && SIMPLE_TYPE_CHARS.contains(s.as_str()) {
return Ok(get_size(&s));
}
}
// Fall back to sizeof
size_of(cls.clone().into(), vm)
}
/// Convert bytes to appropriate Python object based on ctypes type
pub fn bytes_to_pyobject(
cls: &PyTypeRef,
bytes: &[u8],
vm: &VirtualMachine,
) -> PyResult<PyObjectRef> {
// Try to get _type_ attribute
if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm)
&& let Ok(s) = type_attr.str(vm)
{
let ty = s.to_string();
return match ty.as_str() {
"c" => {
// c_char - single byte
Ok(vm.ctx.new_bytes(bytes.to_vec()).into())
}
"b" => {
// c_byte - signed char
let val = if !bytes.is_empty() { bytes[0] as i8 } else { 0 };
Ok(vm.ctx.new_int(val).into())
}
"B" => {
// c_ubyte - unsigned char
let val = if !bytes.is_empty() { bytes[0] } else { 0 };
Ok(vm.ctx.new_int(val).into())
}
"h" => {
// c_short
const SIZE: usize = mem::size_of::<c_short>();
let val = if bytes.len() >= SIZE {
c_short::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked"))
} else {
0
};
Ok(vm.ctx.new_int(val).into())
}
"H" => {
// c_ushort
const SIZE: usize = mem::size_of::<c_ushort>();
let val = if bytes.len() >= SIZE {
c_ushort::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked"))
} else {
0
};
Ok(vm.ctx.new_int(val).into())
}
"i" => {
// c_int
const SIZE: usize = mem::size_of::<c_int>();
let val = if bytes.len() >= SIZE {
c_int::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked"))
} else {
0
};
Ok(vm.ctx.new_int(val).into())
}
"I" => {
// c_uint
const SIZE: usize = mem::size_of::<c_uint>();
let val = if bytes.len() >= SIZE {
c_uint::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked"))
} else {
0
};
Ok(vm.ctx.new_int(val).into())
}
"l" => {
// c_long
const SIZE: usize = mem::size_of::<c_long>();
let val = if bytes.len() >= SIZE {
c_long::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked"))
} else {
0
};
Ok(vm.ctx.new_int(val).into())
}
"L" => {
// c_ulong
const SIZE: usize = mem::size_of::<c_ulong>();
let val = if bytes.len() >= SIZE {
c_ulong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked"))
} else {
0
};
Ok(vm.ctx.new_int(val).into())
}
"q" => {
// c_longlong
const SIZE: usize = mem::size_of::<c_longlong>();
let val = if bytes.len() >= SIZE {
c_longlong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked"))
} else {
0
};
Ok(vm.ctx.new_int(val).into())
}
"Q" => {
// c_ulonglong
const SIZE: usize = mem::size_of::<c_ulonglong>();
let val = if bytes.len() >= SIZE {
c_ulonglong::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked"))
} else {
0
};
Ok(vm.ctx.new_int(val).into())
}
"f" => {
// c_float
const SIZE: usize = mem::size_of::<c_float>();
let val = if bytes.len() >= SIZE {
c_float::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked"))
} else {
0.0
};
Ok(vm.ctx.new_float(val as f64).into())
}
"d" | "g" => {
// c_double
const SIZE: usize = mem::size_of::<c_double>();
let val = if bytes.len() >= SIZE {
c_double::from_ne_bytes(bytes[..SIZE].try_into().expect("size checked"))
} else {
0.0
};
Ok(vm.ctx.new_float(val).into())
}
"?" => {
// c_bool
let val = !bytes.is_empty() && bytes[0] != 0;
Ok(vm.ctx.new_bool(val).into())
}
"P" | "z" | "Z" => {
// Pointer types - return as integer address
let val = if bytes.len() >= mem::size_of::<libc::uintptr_t>() {
const UINTPTR_LEN: usize = mem::size_of::<libc::uintptr_t>();
let mut arr = [0u8; UINTPTR_LEN];
arr[..bytes.len().min(UINTPTR_LEN)]
.copy_from_slice(&bytes[..bytes.len().min(UINTPTR_LEN)]);
usize::from_ne_bytes(arr)
} else {
0
};
Ok(vm.ctx.new_int(val).into())
}
"u" => {
// c_wchar - wide character
let val = if bytes.len() >= mem::size_of::<WideChar>() {
let wc = if mem::size_of::<WideChar>() == 2 {
u16::from_ne_bytes([bytes[0], bytes[1]]) as u32
} else {
u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
};
char::from_u32(wc).unwrap_or('\0')
} else {
'\0'
};
Ok(vm.ctx.new_str(val.to_string()).into())
}
_ => Ok(vm.ctx.none()),
};
}
// Default: return bytes as-is
Ok(vm.ctx.new_bytes(bytes.to_vec()).into())
}
const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZPqQ?O";
pub fn new_simple_type(
@@ -180,9 +368,14 @@ pub(crate) mod _ctypes {
} else if !SIMPLE_TYPE_CHARS.contains(tp_str.as_str()) {
Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be\n a single character string containing one of {SIMPLE_TYPE_CHARS}, currently it is {tp_str}.")))
} else {
let size = get_size(&tp_str);
Ok(PyCSimple {
_type_: tp_str,
value: AtomicCell::new(vm.ctx.none()),
cdata: rustpython_common::lock::PyRwLock::new(CDataObject::from_bytes(
vec![0u8; size],
None,
)),
})
}
} else {
@@ -193,20 +386,118 @@ pub(crate) mod _ctypes {
}
}
/// Get the size of a ctypes type or instance
#[pyfunction(name = "sizeof")]
pub fn size_of(tp: Either<PyTypeRef, PyObjectRef>, vm: &VirtualMachine) -> PyResult<usize> {
match tp {
Either::A(type_) if type_.fast_issubclass(PyCSimple::static_type()) => {
let zelf = new_simple_type(Either::B(&type_), vm)?;
Ok(get_size(zelf._type_.as_str()))
pub fn size_of(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> {
use super::array::{PyCArray, PyCArrayType};
use super::pointer::PyCPointer;
use super::structure::{PyCStructType, PyCStructure};
use super::union::{PyCUnion, PyCUnionType};
// 1. Instances with stg_info
if obj.fast_isinstance(PyCArray::static_type()) {
// Get stg_info from the type
if let Some(type_obj) = obj.class().as_object().downcast_ref::<PyCArrayType>() {
return Ok(type_obj.stg_info.size);
}
Either::B(obj) if obj.has_attr("size_of_instances", vm)? => {
let size_of_method = obj.get_attr("size_of_instances", vm)?;
let size_of_return = size_of_method.call(vec![], vm)?;
Ok(usize::try_from_object(vm, size_of_return)?)
}
_ => Err(vm.new_type_error("this type has no size")),
}
if let Some(structure) = obj.downcast_ref::<PyCStructure>() {
return Ok(structure.cdata.read().size());
}
if obj.fast_isinstance(PyCUnion::static_type()) {
// Get stg_info from the type
if let Some(type_obj) = obj.class().as_object().downcast_ref::<PyCUnionType>() {
return Ok(type_obj.stg_info.size);
}
}
if let Some(simple) = obj.downcast_ref::<PyCSimple>() {
return Ok(simple.cdata.read().size());
}
if obj.fast_isinstance(PyCPointer::static_type()) {
return Ok(std::mem::size_of::<usize>());
}
// 2. Types (metatypes with stg_info)
if let Some(array_type) = obj.downcast_ref::<PyCArrayType>() {
return Ok(array_type.stg_info.size);
}
// 3. Type objects
if let Ok(type_ref) = obj.clone().downcast::<crate::builtins::PyType>() {
// Structure types - check if metaclass is or inherits from PyCStructType
if type_ref
.class()
.fast_issubclass(PyCStructType::static_type())
{
return calculate_struct_size(&type_ref, vm);
}
// Union types - check if metaclass is or inherits from PyCUnionType
if type_ref
.class()
.fast_issubclass(PyCUnionType::static_type())
{
return calculate_union_size(&type_ref, vm);
}
// Simple types (c_int, c_char, etc.)
if type_ref.fast_issubclass(PyCSimple::static_type()) {
let instance = new_simple_type(Either::B(&type_ref), vm)?;
return Ok(get_size(&instance._type_));
}
// Pointer types
if type_ref.fast_issubclass(PyCPointer::static_type()) {
return Ok(std::mem::size_of::<usize>());
}
}
Err(vm.new_type_error("this type has no size"))
}
/// Calculate Structure type size from _fields_ (sum of field sizes)
fn calculate_struct_size(
cls: &crate::builtins::PyTypeRef,
vm: &VirtualMachine,
) -> PyResult<usize> {
use crate::AsObject;
if let Ok(fields_attr) = cls.as_object().get_attr("_fields_", vm) {
let fields: Vec<PyObjectRef> = fields_attr.try_to_value(vm).unwrap_or_default();
let mut total_size = 0usize;
for field in fields.iter() {
if let Some(tuple) = field.downcast_ref::<crate::builtins::PyTuple>()
&& let Some(field_type) = tuple.get(1)
{
// Recursively calculate field type size
total_size += size_of(field_type.clone(), vm)?;
}
}
return Ok(total_size);
}
Ok(0)
}
/// Calculate Union type size from _fields_ (max field size)
fn calculate_union_size(
cls: &crate::builtins::PyTypeRef,
vm: &VirtualMachine,
) -> PyResult<usize> {
use crate::AsObject;
if let Ok(fields_attr) = cls.as_object().get_attr("_fields_", vm) {
let fields: Vec<PyObjectRef> = fields_attr.try_to_value(vm).unwrap_or_default();
let mut max_size = 0usize;
for field in fields.iter() {
if let Some(tuple) = field.downcast_ref::<crate::builtins::PyTuple>()
&& let Some(field_type) = tuple.get(1)
{
let field_size = size_of(field_type.clone(), vm)?;
max_size = max_size.max(field_size);
}
}
return Ok(max_size);
}
Ok(0)
}
#[cfg(windows)]
@@ -367,9 +658,100 @@ pub(crate) mod _ctypes {
}
#[pyfunction]
fn alignment(_args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
// TODO: RUSTPYTHON
Err(vm.new_value_error("not implemented"))
fn alignment(tp: Either<PyTypeRef, PyObjectRef>, vm: &VirtualMachine) -> PyResult<usize> {
use super::array::{PyCArray, PyCArrayType};
use super::base::PyCSimpleType;
use super::pointer::PyCPointer;
use super::structure::PyCStructure;
use super::union::PyCUnion;
let obj = match &tp {
Either::A(t) => t.as_object(),
Either::B(o) => o.as_ref(),
};
// Try to get alignment from stg_info directly (for instances)
if let Some(array_type) = obj.downcast_ref::<PyCArrayType>() {
return Ok(array_type.stg_info.align);
}
if obj.fast_isinstance(PyCSimple::static_type()) {
// Get stg_info from the type by reading _type_ attribute
let cls = obj.class().to_owned();
let stg_info = PyCSimpleType::get_stg_info(&cls, vm);
return Ok(stg_info.align);
}
if obj.fast_isinstance(PyCArray::static_type()) {
// Get stg_info from the type
if let Some(type_obj) = obj.class().as_object().downcast_ref::<PyCArrayType>() {
return Ok(type_obj.stg_info.align);
}
}
if obj.fast_isinstance(PyCStructure::static_type()) {
// Calculate alignment from _fields_
let cls = obj.class();
return alignment(Either::A(cls.to_owned()), vm);
}
if obj.fast_isinstance(PyCPointer::static_type()) {
// Pointer alignment is always pointer size
return Ok(std::mem::align_of::<usize>());
}
if obj.fast_isinstance(PyCUnion::static_type()) {
// Calculate alignment from _fields_
let cls = obj.class();
return alignment(Either::A(cls.to_owned()), vm);
}
// Get the type object to check
let type_obj: PyObjectRef = match &tp {
Either::A(t) => t.clone().into(),
Either::B(obj) => obj.class().to_owned().into(),
};
// For type objects, try to get alignment from _type_ attribute
if let Ok(type_attr) = type_obj.get_attr("_type_", vm) {
// Array/Pointer: _type_ is the element type (a PyType)
if let Ok(elem_type) = type_attr.clone().downcast::<crate::builtins::PyType>() {
return alignment(Either::A(elem_type), vm);
}
// Simple type: _type_ is a single character string
if let Ok(s) = type_attr.str(vm) {
let ty = s.to_string();
if ty.len() == 1 && SIMPLE_TYPE_CHARS.contains(ty.as_str()) {
return Ok(get_align(&ty));
}
}
}
// Structure/Union: max alignment of fields
if let Ok(fields_attr) = type_obj.get_attr("_fields_", vm)
&& let Ok(fields) = fields_attr.try_to_value::<Vec<PyObjectRef>>(vm)
{
let mut max_align = 1usize;
for field in fields.iter() {
if let Some(tuple) = field.downcast_ref::<crate::builtins::PyTuple>()
&& let Some(field_type) = tuple.get(1)
{
let align =
if let Ok(ft) = field_type.clone().downcast::<crate::builtins::PyType>() {
alignment(Either::A(ft), vm).unwrap_or(1)
} else {
1
};
max_align = max_align.max(align);
}
}
return Ok(max_align);
}
// For instances, delegate to their class
if let Either::B(obj) = &tp
&& !obj.class().is(vm.ctx.types.type_type.as_ref())
{
return alignment(Either::A(obj.class().to_owned()), vm);
}
// No alignment info found
Err(vm.new_type_error("no alignment info"))
}
#[pyfunction]
@@ -439,4 +821,53 @@ pub(crate) mod _ctypes {
// todo!("Implement _cast_addr")
0
}
#[pyfunction(name = "_cast")]
pub fn pycfunction_cast(
obj: PyObjectRef,
_obj2: PyObjectRef,
ctype: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult {
use super::array::PyCArray;
use super::base::PyCData;
use super::pointer::PyCPointer;
use crate::class::StaticType;
// Python signature: _cast(obj, obj, ctype)
// Python passes the same object twice (obj and _obj2 are the same)
// We ignore _obj2 as it's redundant
// Check if this is a pointer type (has _type_ attribute)
if ctype.get_attr("_type_", vm).is_err() {
return Err(vm.new_type_error("cast() argument 2 must be a pointer type".to_string()));
}
// Create an instance of the target pointer type with no arguments
let result = ctype.call((), vm)?;
// Get the pointer value from the source object
// If obj is a CData instance (including arrays), use the object itself
// If obj is an integer, use it directly as the pointer value
let ptr_value: PyObjectRef = if obj.fast_isinstance(PyCData::static_type())
|| obj.fast_isinstance(PyCArray::static_type())
|| obj.fast_isinstance(PyCPointer::static_type())
{
// For CData objects (including arrays and pointers), store the object itself
obj.clone()
} else if let Ok(int_val) = obj.try_int(vm) {
// For integers, treat as pointer address
vm.ctx.new_int(int_val.as_bigint().clone()).into()
} else {
return Err(vm.new_type_error(format!(
"cast() argument 1 must be a ctypes instance or an integer, not {}",
obj.class().name()
)));
};
// Set the contents of the pointer by setting the attribute
result.set_attr("contents", ptr_value, vm)?;
Ok(result)
}
}
+431 -51
View File
@@ -2,8 +2,12 @@ use crate::atomic_func;
use crate::builtins::{PyBytes, PyInt};
use crate::convert::ToPyObject;
use crate::function::FuncArgs;
use crate::protocol::{PyNumberMethods, PySequenceMethods};
use crate::types::{AsNumber, AsSequence, Callable};
use crate::protocol::{
BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods, PySequenceMethods,
};
use crate::stdlib::ctypes::base::CDataObject;
use crate::stdlib::ctypes::util::StgInfo;
use crate::types::{AsBuffer, AsNumber, AsSequence, Callable};
use crate::{AsObject, Py, PyObjectRef, PyPayload};
use crate::{
PyResult, VirtualMachine,
@@ -19,13 +23,17 @@ use rustpython_vm::stdlib::ctypes::base::PyCData;
#[pyclass(name = "PyCArrayType", base = PyType, module = "_ctypes")]
#[derive(PyPayload)]
pub struct PyCArrayType {
pub(super) inner: PyCArray,
pub(super) stg_info: StgInfo,
pub(super) typ: PyRwLock<PyObjectRef>,
pub(super) length: AtomicCell<usize>,
pub(super) element_size: AtomicCell<usize>,
}
impl std::fmt::Debug for PyCArrayType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PyCArrayType")
.field("inner", &self.inner)
.field("typ", &self.typ)
.field("length", &self.length)
.finish()
}
}
@@ -34,9 +42,9 @@ impl Callable for PyCArrayType {
type Args = FuncArgs;
fn call(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult {
// Create an instance of the array
let element_type = zelf.inner.typ.read().clone();
let length = zelf.inner.length.load();
let element_size = zelf.inner.element_size.load();
let element_type = zelf.typ.read().clone();
let length = zelf.length.load();
let element_size = zelf.element_size.load();
let total_size = element_size * length;
let mut buffer = vec![0u8; total_size];
@@ -58,7 +66,7 @@ impl Callable for PyCArrayType {
typ: PyRwLock::new(element_type),
length: AtomicCell::new(length),
element_size: AtomicCell::new(element_size),
buffer: PyRwLock::new(buffer),
cdata: PyRwLock::new(CDataObject::from_bytes(buffer, None)),
}
.into_pyobject(vm))
}
@@ -75,13 +83,13 @@ impl Constructor for PyCArrayType {
#[pyclass(flags(IMMUTABLETYPE), with(Callable, Constructor, AsNumber))]
impl PyCArrayType {
#[pygetset(name = "_type_")]
fn typ(&self) -> PyTypeRef {
self.inner.typ.read().clone()
fn typ(&self) -> PyObjectRef {
self.typ.read().clone()
}
#[pygetset(name = "_length_")]
fn length(&self) -> usize {
self.inner.length.load()
self.length.load()
}
#[pymethod]
@@ -92,28 +100,106 @@ impl PyCArrayType {
// Create a nested array type: (inner_type * inner_length) * n
// The new array has n elements, each element is the current array type
// e.g., (c_int * 5) * 3 = Array of 3 elements, each is (c_int * 5)
let inner_length = zelf.inner.length.load();
let inner_element_size = zelf.inner.element_size.load();
let inner_length = zelf.length.load();
let inner_element_size = zelf.element_size.load();
// The element type of the new array is the current array type itself
let obj_ref: PyObjectRef = zelf.to_owned().into();
let current_array_type = obj_ref
.downcast::<PyType>()
.expect("PyCArrayType should be a PyType");
let current_array_type: PyObjectRef = zelf.as_object().to_owned();
// Element size is the total size of the inner array
let new_element_size = inner_length * inner_element_size;
let total_size = new_element_size * (n as usize);
let stg_info = StgInfo::new(total_size, inner_element_size);
Ok(PyCArrayType {
inner: PyCArray {
typ: PyRwLock::new(current_array_type),
length: AtomicCell::new(n as usize),
element_size: AtomicCell::new(new_element_size),
buffer: PyRwLock::new(vec![]),
},
stg_info,
typ: PyRwLock::new(current_array_type),
length: AtomicCell::new(n as usize),
element_size: AtomicCell::new(new_element_size),
}
.to_pyobject(vm))
}
#[pyclassmethod]
fn in_dll(
zelf: &Py<Self>,
dll: PyObjectRef,
name: crate::builtins::PyStrRef,
vm: &VirtualMachine,
) -> PyResult {
use crate::stdlib::ctypes::_ctypes::size_of;
use libloading::Symbol;
// Get the library handle from dll object
let handle = if let Ok(int_handle) = dll.try_int(vm) {
// dll is an integer handle
int_handle
.as_bigint()
.to_usize()
.ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))?
} else {
// dll is a CDLL/PyDLL/WinDLL object with _handle attribute
dll.get_attr("_handle", vm)?
.try_int(vm)?
.as_bigint()
.to_usize()
.ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))?
};
// Get the library from cache
let library_cache = crate::stdlib::ctypes::library::libcache().read();
let library = library_cache
.get_lib(handle)
.ok_or_else(|| vm.new_attribute_error("Library not found".to_owned()))?;
// Get symbol address from library
let symbol_name = format!("{}\0", name.as_str());
let inner_lib = library.lib.lock();
let symbol_address = if let Some(lib) = &*inner_lib {
unsafe {
// Try to get the symbol from the library
let symbol: Symbol<'_, *mut u8> = lib.get(symbol_name.as_bytes()).map_err(|e| {
vm.new_attribute_error(format!("{}: symbol '{}' not found", e, name.as_str()))
})?;
*symbol as usize
}
} else {
return Err(vm.new_attribute_error("Library is closed".to_owned()));
};
// Get size from the array type
let element_type = zelf.typ.read().clone();
let length = zelf.length.load();
let element_size = size_of(element_type.clone(), vm)?;
let total_size = element_size * length;
// Read data from symbol address
let data = if symbol_address != 0 && total_size > 0 {
unsafe {
let ptr = symbol_address as *const u8;
std::slice::from_raw_parts(ptr, total_size).to_vec()
}
} else {
vec![0; total_size]
};
// Create instance
let instance = PyCArray {
typ: PyRwLock::new(element_type),
length: AtomicCell::new(length),
element_size: AtomicCell::new(element_size),
cdata: PyRwLock::new(CDataObject::from_bytes(data, None)),
}
.into_pyobject(vm);
// Store base reference to keep dll alive
if let Ok(array_ref) = instance.clone().downcast::<PyCArray>() {
array_ref.cdata.write().base = Some(dll);
}
Ok(instance)
}
}
impl AsNumber for PyCArrayType {
@@ -144,10 +230,11 @@ impl AsNumber for PyCArrayType {
)]
#[derive(PyPayload)]
pub struct PyCArray {
pub(super) typ: PyRwLock<PyTypeRef>,
/// Element type - can be a simple type (c_int) or an array type (c_int * 5)
pub(super) typ: PyRwLock<PyObjectRef>,
pub(super) length: AtomicCell<usize>,
pub(super) element_size: AtomicCell<usize>,
pub(super) buffer: PyRwLock<Vec<u8>>,
pub(super) cdata: PyRwLock<CDataObject>,
}
impl std::fmt::Debug for PyCArray {
@@ -207,15 +294,11 @@ impl Constructor for PyCArray {
}
}
let element_type_ref = element_type
.downcast::<PyType>()
.unwrap_or_else(|_| vm.ctx.types.object_type.to_owned());
PyCArray {
typ: PyRwLock::new(element_type_ref),
typ: PyRwLock::new(element_type),
length: AtomicCell::new(length),
element_size: AtomicCell::new(element_size),
buffer: PyRwLock::new(buffer),
cdata: PyRwLock::new(CDataObject::from_bytes(buffer, None)),
}
.into_ref_with_type(vm, cls)
.map(Into::into)
@@ -243,8 +326,16 @@ impl AsSequence for PyCArray {
}
}
#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, AsSequence))]
#[pyclass(
flags(BASETYPE, IMMUTABLETYPE),
with(Constructor, AsSequence, AsBuffer)
)]
impl PyCArray {
#[pygetset]
fn _objects(&self) -> Option<PyObjectRef> {
self.cdata.read().objects.clone()
}
fn int_to_bytes(i: &malachite_bigint::BigInt, size: usize) -> Vec<u8> {
match size {
1 => vec![i.to_i8().unwrap_or(0) as u8],
@@ -285,7 +376,7 @@ impl PyCArray {
let index = index as usize;
let element_size = zelf.element_size.load();
let offset = index * element_size;
let buffer = zelf.buffer.read();
let buffer = zelf.cdata.read().buffer.clone();
if offset + element_size <= buffer.len() {
let bytes = &buffer[offset..offset + element_size];
Ok(Self::bytes_to_int(bytes, element_size, vm))
@@ -312,9 +403,9 @@ impl PyCArray {
let int_val = value.try_int(vm)?;
let bytes = Self::int_to_bytes(int_val.as_bigint(), element_size);
let mut buffer = zelf.buffer.write();
if offset + element_size <= buffer.len() {
buffer[offset..offset + element_size].copy_from_slice(&bytes);
let mut cdata = zelf.cdata.write();
if offset + element_size <= cdata.buffer.len() {
cdata.buffer[offset..offset + element_size].copy_from_slice(&bytes);
}
Ok(())
}
@@ -354,7 +445,7 @@ impl PyCArray {
}
#[pygetset(name = "_type_")]
fn typ(&self) -> PyTypeRef {
fn typ(&self) -> PyObjectRef {
self.typ.read().clone()
}
@@ -366,48 +457,337 @@ impl PyCArray {
#[pygetset]
fn value(&self, vm: &VirtualMachine) -> PyObjectRef {
// Return bytes representation of the buffer
let buffer = self.buffer.read();
let buffer = self.cdata.read().buffer.clone();
vm.ctx.new_bytes(buffer.clone()).into()
}
#[pygetset(setter)]
fn set_value(&self, value: PyObjectRef, _vm: &VirtualMachine) -> PyResult<()> {
if let Some(bytes) = value.downcast_ref::<PyBytes>() {
let mut buffer = self.buffer.write();
let mut cdata = self.cdata.write();
let src = bytes.as_bytes();
let len = std::cmp::min(src.len(), buffer.len());
buffer[..len].copy_from_slice(&src[..len]);
let len = std::cmp::min(src.len(), cdata.buffer.len());
cdata.buffer[..len].copy_from_slice(&src[..len]);
}
Ok(())
}
#[pygetset]
fn raw(&self, vm: &VirtualMachine) -> PyObjectRef {
let buffer = self.buffer.read();
vm.ctx.new_bytes(buffer.clone()).into()
let cdata = self.cdata.read();
vm.ctx.new_bytes(cdata.buffer.clone()).into()
}
#[pygetset(setter)]
fn set_raw(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
if let Some(bytes) = value.downcast_ref::<PyBytes>() {
let mut buffer = self.buffer.write();
let mut cdata = self.cdata.write();
let src = bytes.as_bytes();
let len = std::cmp::min(src.len(), buffer.len());
buffer[..len].copy_from_slice(&src[..len]);
let len = std::cmp::min(src.len(), cdata.buffer.len());
cdata.buffer[..len].copy_from_slice(&src[..len]);
Ok(())
} else {
Err(vm.new_type_error("expected bytes".to_owned()))
}
}
#[pyclassmethod]
fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult {
use crate::stdlib::ctypes::_ctypes::size_of;
// Get size from cls
let size = size_of(cls.clone().into(), vm)?;
// Create instance with data from address
if address == 0 || size == 0 {
return Err(vm.new_value_error("NULL pointer access".to_owned()));
}
unsafe {
let ptr = address as *const u8;
let bytes = std::slice::from_raw_parts(ptr, size);
// Get element type and length from cls
let element_type = cls.as_object().get_attr("_type_", vm)?;
let element_type: PyTypeRef = element_type
.downcast()
.map_err(|_| vm.new_type_error("_type_ must be a type".to_owned()))?;
let length = cls
.as_object()
.get_attr("_length_", vm)?
.try_int(vm)?
.as_bigint()
.to_usize()
.unwrap_or(0);
let element_size = if length > 0 { size / length } else { 0 };
Ok(PyCArray {
typ: PyRwLock::new(element_type.into()),
length: AtomicCell::new(length),
element_size: AtomicCell::new(element_size),
cdata: PyRwLock::new(CDataObject::from_bytes(bytes.to_vec(), None)),
}
.into_pyobject(vm))
}
}
#[pyclassmethod]
fn from_buffer(
cls: PyTypeRef,
source: PyObjectRef,
offset: crate::function::OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult {
use crate::TryFromObject;
use crate::protocol::PyBuffer;
use crate::stdlib::ctypes::_ctypes::size_of;
let offset = offset.unwrap_or(0);
if offset < 0 {
return Err(vm.new_value_error("offset cannot be negative".to_owned()));
}
let offset = offset as usize;
// Get buffer from source
let buffer = PyBuffer::try_from_object(vm, source.clone())?;
// Check if buffer is writable
if buffer.desc.readonly {
return Err(vm.new_type_error("underlying buffer is not writable".to_owned()));
}
// Get size from cls
let size = size_of(cls.clone().into(), vm)?;
// Check if buffer is large enough
let buffer_len = buffer.desc.len;
if offset + size > buffer_len {
return Err(vm.new_value_error(format!(
"Buffer size too small ({} instead of at least {} bytes)",
buffer_len,
offset + size
)));
}
// Read bytes from buffer at offset
let bytes = buffer.obj_bytes();
let data = &bytes[offset..offset + size];
// Get element type and length from cls
let element_type = cls.as_object().get_attr("_type_", vm)?;
let element_type: PyTypeRef = element_type
.downcast()
.map_err(|_| vm.new_type_error("_type_ must be a type".to_owned()))?;
let length = cls
.as_object()
.get_attr("_length_", vm)?
.try_int(vm)?
.as_bigint()
.to_usize()
.unwrap_or(0);
let element_size = if length > 0 { size / length } else { 0 };
Ok(PyCArray {
typ: PyRwLock::new(element_type.into()),
length: AtomicCell::new(length),
element_size: AtomicCell::new(element_size),
cdata: PyRwLock::new(CDataObject::from_bytes(
data.to_vec(),
Some(buffer.obj.clone()),
)),
}
.into_pyobject(vm))
}
#[pyclassmethod]
fn from_buffer_copy(
cls: PyTypeRef,
source: crate::function::ArgBytesLike,
offset: crate::function::OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult {
use crate::stdlib::ctypes::_ctypes::size_of;
let offset = offset.unwrap_or(0);
if offset < 0 {
return Err(vm.new_value_error("offset cannot be negative".to_owned()));
}
let offset = offset as usize;
// Get size from cls
let size = size_of(cls.clone().into(), vm)?;
// Borrow bytes from source
let source_bytes = source.borrow_buf();
let buffer_len = source_bytes.len();
// Check if buffer is large enough
if offset + size > buffer_len {
return Err(vm.new_value_error(format!(
"Buffer size too small ({} instead of at least {} bytes)",
buffer_len,
offset + size
)));
}
// Copy bytes from buffer at offset
let data = &source_bytes[offset..offset + size];
// Get element type and length from cls
let element_type = cls.as_object().get_attr("_type_", vm)?;
let element_type: PyTypeRef = element_type
.downcast()
.map_err(|_| vm.new_type_error("_type_ must be a type".to_owned()))?;
let length = cls
.as_object()
.get_attr("_length_", vm)?
.try_int(vm)?
.as_bigint()
.to_usize()
.unwrap_or(0);
let element_size = if length > 0 { size / length } else { 0 };
Ok(PyCArray {
typ: PyRwLock::new(element_type.into()),
length: AtomicCell::new(length),
element_size: AtomicCell::new(element_size),
cdata: PyRwLock::new(CDataObject::from_bytes(data.to_vec(), None)),
}
.into_pyobject(vm))
}
#[pyclassmethod]
fn in_dll(
cls: PyTypeRef,
dll: PyObjectRef,
name: crate::builtins::PyStrRef,
vm: &VirtualMachine,
) -> PyResult {
use crate::stdlib::ctypes::_ctypes::size_of;
use libloading::Symbol;
// Get the library handle from dll object
let handle = if let Ok(int_handle) = dll.try_int(vm) {
// dll is an integer handle
int_handle
.as_bigint()
.to_usize()
.ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))?
} else {
// dll is a CDLL/PyDLL/WinDLL object with _handle attribute
dll.get_attr("_handle", vm)?
.try_int(vm)?
.as_bigint()
.to_usize()
.ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))?
};
// Get the library from cache
let library_cache = crate::stdlib::ctypes::library::libcache().read();
let library = library_cache
.get_lib(handle)
.ok_or_else(|| vm.new_attribute_error("Library not found".to_owned()))?;
// Get symbol address from library
let symbol_name = format!("{}\0", name.as_str());
let inner_lib = library.lib.lock();
let symbol_address = if let Some(lib) = &*inner_lib {
unsafe {
// Try to get the symbol from the library
let symbol: Symbol<'_, *mut u8> = lib.get(symbol_name.as_bytes()).map_err(|e| {
vm.new_attribute_error(format!("{}: symbol '{}' not found", e, name.as_str()))
})?;
*symbol as usize
}
} else {
return Err(vm.new_attribute_error("Library is closed".to_owned()));
};
// Get size from cls
let size = size_of(cls.clone().into(), vm)?;
// Read data from symbol address
let data = if symbol_address != 0 && size > 0 {
unsafe {
let ptr = symbol_address as *const u8;
std::slice::from_raw_parts(ptr, size).to_vec()
}
} else {
vec![0; size]
};
// Get element type and length from cls
let element_type = cls.as_object().get_attr("_type_", vm)?;
let element_type: PyTypeRef = element_type
.downcast()
.map_err(|_| vm.new_type_error("_type_ must be a type".to_owned()))?;
let length = cls
.as_object()
.get_attr("_length_", vm)?
.try_int(vm)?
.as_bigint()
.to_usize()
.unwrap_or(0);
let element_size = if length > 0 { size / length } else { 0 };
// Create instance
let instance = PyCArray {
typ: PyRwLock::new(element_type.into()),
length: AtomicCell::new(length),
element_size: AtomicCell::new(element_size),
cdata: PyRwLock::new(CDataObject::from_bytes(data, None)),
}
.into_pyobject(vm);
// Store base reference to keep dll alive
if let Ok(array_ref) = instance.clone().downcast::<PyCArray>() {
array_ref.cdata.write().base = Some(dll);
}
Ok(instance)
}
}
impl PyCArray {
#[allow(unused)]
pub fn to_arg(&self, _vm: &VirtualMachine) -> PyResult<libffi::middle::Arg> {
// TODO: This needs a different approach to ensure buffer lifetime
// The buffer must outlive the Arg returned here
let buffer = self.buffer.read();
let ptr = buffer.as_ptr();
Ok(libffi::middle::Arg::new(&ptr))
let cdata = self.cdata.read();
Ok(libffi::middle::Arg::new(&cdata.buffer))
}
}
static ARRAY_BUFFER_METHODS: BufferMethods = BufferMethods {
obj_bytes: |buffer| {
rustpython_common::lock::PyMappedRwLockReadGuard::map(
rustpython_common::lock::PyRwLockReadGuard::map(
buffer.obj_as::<PyCArray>().cdata.read(),
|x: &CDataObject| x,
),
|x: &CDataObject| x.buffer.as_slice(),
)
.into()
},
obj_bytes_mut: |buffer| {
rustpython_common::lock::PyMappedRwLockWriteGuard::map(
rustpython_common::lock::PyRwLockWriteGuard::map(
buffer.obj_as::<PyCArray>().cdata.write(),
|x: &mut CDataObject| x,
),
|x: &mut CDataObject| x.buffer.as_mut_slice(),
)
.into()
},
release: |_| {},
retain: |_| {},
};
impl AsBuffer for PyCArray {
fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> {
let buffer_len = zelf.cdata.read().buffer.len();
let buf = PyBuffer::new(
zelf.to_owned().into(),
BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes
&ARRAY_BUFFER_METHODS,
);
Ok(buf)
}
}
+704 -31
View File
@@ -1,17 +1,27 @@
use super::array::{PyCArray, PyCArrayType};
use crate::builtins::PyType;
use crate::builtins::{PyBytes, PyFloat, PyInt, PyNone, PyStr, PyTypeRef};
use super::_ctypes::bytes_to_pyobject;
use super::array::PyCArrayType;
use super::util::StgInfo;
use crate::builtins::{PyBytes, PyFloat, PyInt, PyNone, PyStr, PyStrRef, PyType, PyTypeRef};
use crate::convert::ToPyObject;
use crate::function::{Either, OptionalArg};
use crate::protocol::PyNumberMethods;
use crate::function::{ArgBytesLike, Either, OptionalArg};
use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods};
use crate::stdlib::ctypes::_ctypes::new_simple_type;
use crate::types::{AsNumber, Constructor};
use crate::types::{AsBuffer, AsNumber, Constructor};
use crate::{AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine};
use crossbeam_utils::atomic::AtomicCell;
use num_traits::ToPrimitive;
use rustpython_common::lock::PyRwLock;
use std::ffi::{c_uint, c_ulong, c_ulonglong, c_ushort};
use std::fmt::Debug;
/// Get the type code string from a ctypes type (e.g., "i" for c_int)
pub fn get_type_code(cls: &PyTypeRef, vm: &VirtualMachine) -> Option<String> {
cls.as_object()
.get_attr("_type_", vm)
.ok()
.and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string()))
}
pub fn ffi_type_from_str(_type_: &str) -> Option<libffi::middle::Type> {
match _type_ {
"c" => Some(libffi::middle::Type::u8()),
@@ -91,7 +101,10 @@ fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyRe
}
}
"f" | "d" | "g" => {
if value.clone().downcast_exact::<PyFloat>(vm).is_ok() {
// float allows int
if value.clone().downcast_exact::<PyFloat>(vm).is_ok()
|| value.clone().downcast_exact::<PyInt>(vm).is_ok()
{
Ok(value.clone())
} else {
Err(vm.new_type_error(format!("must be real number, not {}", value.class().name())))
@@ -102,7 +115,8 @@ fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyRe
)),
"B" => {
if value.clone().downcast_exact::<PyInt>(vm).is_ok() {
Ok(vm.new_pyobj(u8::try_from_object(vm, value.clone())?))
// Store as-is, conversion to unsigned happens in the getter
Ok(value.clone())
} else {
Err(vm.new_type_error(format!("int expected instead of {}", value.class().name())))
}
@@ -142,28 +156,102 @@ fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyRe
}
}
pub struct RawBuffer {
/// Common data object for all ctypes types
#[derive(Debug, Clone)]
pub struct CDataObject {
/// pointer to memory block (b_ptr + b_size)
pub buffer: Vec<u8>,
/// pointer to base object or None (b_base)
#[allow(dead_code)]
pub inner: Box<[u8]>,
pub base: Option<PyObjectRef>,
/// index into base's b_objects list (b_index)
#[allow(dead_code)]
pub size: usize,
pub index: usize,
/// dictionary of references we need to keep (b_objects)
pub objects: Option<PyObjectRef>,
}
impl CDataObject {
/// Create from StgInfo (PyCData_MallocBuffer pattern)
pub fn from_stg_info(stg_info: &StgInfo) -> Self {
CDataObject {
buffer: vec![0u8; stg_info.size],
base: None,
index: 0,
objects: None,
}
}
/// Create from existing bytes (copies data)
pub fn from_bytes(data: Vec<u8>, objects: Option<PyObjectRef>) -> Self {
CDataObject {
buffer: data,
base: None,
index: 0,
objects,
}
}
/// Create from base object (copies data from base's buffer at offset)
#[allow(dead_code)]
pub fn from_base(
base: PyObjectRef,
_offset: usize,
size: usize,
index: usize,
objects: Option<PyObjectRef>,
) -> Self {
CDataObject {
buffer: vec![0u8; size],
base: Some(base),
index,
objects,
}
}
#[inline]
pub fn size(&self) -> usize {
self.buffer.len()
}
}
#[pyclass(name = "_CData", module = "_ctypes")]
#[derive(Debug, PyPayload)]
pub struct PyCData {
_objects: AtomicCell<Vec<PyObjectRef>>,
_buffer: PyRwLock<RawBuffer>,
pub cdata: PyRwLock<CDataObject>,
}
#[pyclass]
impl PyCData {}
#[pyclass(flags(BASETYPE))]
impl PyCData {
#[pygetset]
fn _objects(&self) -> Option<PyObjectRef> {
self.cdata.read().objects.clone()
}
}
#[pyclass(module = "_ctypes", name = "PyCSimpleType", base = PyType)]
#[derive(Debug, PyPayload)]
pub struct PyCSimpleType {}
#[derive(Debug, PyPayload, Default)]
pub struct PyCSimpleType {
#[allow(dead_code)]
pub stg_info: StgInfo,
}
#[pyclass(flags(BASETYPE), with(AsNumber))]
impl PyCSimpleType {
/// Get stg_info for a simple type by reading _type_ attribute
pub fn get_stg_info(cls: &PyTypeRef, vm: &VirtualMachine) -> StgInfo {
if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm)
&& let Ok(type_str) = type_attr.str(vm)
{
let tp_str = type_str.to_string();
if tp_str.len() == 1 {
let size = super::_ctypes::get_size(&tp_str);
let align = super::_ctypes::get_align(&tp_str);
return StgInfo::new(size, align);
}
}
StgInfo::default()
}
#[allow(clippy::new_ret_no_self)]
#[pymethod]
fn new(cls: PyTypeRef, _: OptionalArg, vm: &VirtualMachine) -> PyResult {
@@ -176,17 +264,122 @@ impl PyCSimpleType {
#[pyclassmethod]
fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
// If the value is already an instance of the requested type, return it
// 1. If the value is already an instance of the requested type, return it
if value.fast_isinstance(&cls) {
return Ok(value);
}
// Check for _as_parameter_ attribute
let Ok(as_parameter) = value.get_attr("_as_parameter_", vm) else {
return Err(vm.new_type_error("wrong type"));
};
// 2. Get the type code to determine conversion rules
let type_code = get_type_code(&cls, vm);
PyCSimpleType::from_param(cls, as_parameter, vm)
// 3. Handle None for pointer types (c_char_p, c_wchar_p, c_void_p)
if vm.is_none(&value) && matches!(type_code.as_deref(), Some("z") | Some("Z") | Some("P")) {
return Ok(value);
}
// 4. Try to convert value based on type code
match type_code.as_deref() {
// Integer types: accept integers
Some("b" | "B" | "h" | "H" | "i" | "I" | "l" | "L" | "q" | "Q") => {
if value.try_int(vm).is_ok() {
let simple = new_simple_type(Either::B(&cls), vm)?;
simple.value.store(value.clone());
return simple.into_ref_with_type(vm, cls.clone()).map(Into::into);
}
}
// Float types: accept numbers
Some("f" | "d" | "g") => {
if value.try_float(vm).is_ok() || value.try_int(vm).is_ok() {
let simple = new_simple_type(Either::B(&cls), vm)?;
simple.value.store(value.clone());
return simple.into_ref_with_type(vm, cls.clone()).map(Into::into);
}
}
// c_char: 1 byte character
Some("c") => {
if let Some(bytes) = value.downcast_ref::<PyBytes>()
&& bytes.len() == 1
{
let simple = new_simple_type(Either::B(&cls), vm)?;
simple.value.store(value.clone());
return simple.into_ref_with_type(vm, cls.clone()).map(Into::into);
}
if let Ok(int_val) = value.try_int(vm)
&& int_val.as_bigint().to_u8().is_some()
{
let simple = new_simple_type(Either::B(&cls), vm)?;
simple.value.store(value.clone());
return simple.into_ref_with_type(vm, cls.clone()).map(Into::into);
}
return Err(vm.new_type_error(
"one character bytes, bytearray or integer expected".to_string(),
));
}
// c_wchar: 1 unicode character
Some("u") => {
if let Some(s) = value.downcast_ref::<PyStr>()
&& s.as_str().chars().count() == 1
{
let simple = new_simple_type(Either::B(&cls), vm)?;
simple.value.store(value.clone());
return simple.into_ref_with_type(vm, cls.clone()).map(Into::into);
}
return Err(vm.new_type_error("one character unicode string expected".to_string()));
}
// c_char_p: bytes pointer
Some("z") => {
if value.downcast_ref::<PyBytes>().is_some() {
let simple = new_simple_type(Either::B(&cls), vm)?;
simple.value.store(value.clone());
return simple.into_ref_with_type(vm, cls.clone()).map(Into::into);
}
}
// c_wchar_p: unicode pointer
Some("Z") => {
if value.downcast_ref::<PyStr>().is_some() {
let simple = new_simple_type(Either::B(&cls), vm)?;
simple.value.store(value.clone());
return simple.into_ref_with_type(vm, cls.clone()).map(Into::into);
}
}
// c_void_p: most flexible - accepts int, bytes, str
Some("P") => {
if value.try_int(vm).is_ok()
|| value.downcast_ref::<PyBytes>().is_some()
|| value.downcast_ref::<PyStr>().is_some()
{
let simple = new_simple_type(Either::B(&cls), vm)?;
simple.value.store(value.clone());
return simple.into_ref_with_type(vm, cls.clone()).map(Into::into);
}
}
// c_bool
Some("?") => {
let bool_val = value.is_true(vm)?;
let simple = new_simple_type(Either::B(&cls), vm)?;
simple.value.store(vm.ctx.new_bool(bool_val).into());
return simple.into_ref_with_type(vm, cls.clone()).map(Into::into);
}
_ => {}
}
// 5. Check for _as_parameter_ attribute
if let Ok(as_parameter) = value.get_attr("_as_parameter_", vm) {
return PyCSimpleType::from_param(cls, as_parameter, vm);
}
// 6. Type-specific error messages
match type_code.as_deref() {
Some("z") => Err(vm.new_type_error(format!(
"'{}' object cannot be interpreted as ctypes.c_char_p",
value.class().name()
))),
Some("Z") => Err(vm.new_type_error(format!(
"'{}' object cannot be interpreted as ctypes.c_wchar_p",
value.class().name()
))),
_ => Err(vm.new_type_error("wrong type".to_string())),
}
}
#[pymethod]
@@ -227,6 +420,7 @@ impl AsNumber for PyCSimpleType {
pub struct PyCSimple {
pub _type_: String,
pub value: AtomicCell<PyObjectRef>,
pub cdata: PyRwLock<CDataObject>,
}
impl Debug for PyCSimple {
@@ -237,6 +431,182 @@ impl Debug for PyCSimple {
}
}
fn value_to_bytes_endian(
_type_: &str,
value: &PyObjectRef,
swapped: bool,
vm: &VirtualMachine,
) -> Vec<u8> {
// Helper macro for endian conversion
macro_rules! to_bytes {
($val:expr) => {
if swapped {
// Use opposite endianness
#[cfg(target_endian = "little")]
{
$val.to_be_bytes().to_vec()
}
#[cfg(target_endian = "big")]
{
$val.to_le_bytes().to_vec()
}
} else {
$val.to_ne_bytes().to_vec()
}
};
}
match _type_ {
"c" => {
// c_char - single byte
if let Some(bytes) = value.downcast_ref::<PyBytes>()
&& !bytes.is_empty()
{
return vec![bytes.as_bytes()[0]];
}
if let Ok(int_val) = value.try_int(vm)
&& let Some(v) = int_val.as_bigint().to_u8()
{
return vec![v];
}
vec![0]
}
"u" => {
// c_wchar - 4 bytes (wchar_t on most platforms)
if let Ok(s) = value.str(vm)
&& let Some(c) = s.as_str().chars().next()
{
return to_bytes!(c as u32);
}
vec![0; 4]
}
"b" => {
// c_byte - signed char (1 byte)
if let Ok(int_val) = value.try_int(vm)
&& let Some(v) = int_val.as_bigint().to_i8()
{
return vec![v as u8];
}
vec![0]
}
"B" => {
// c_ubyte - unsigned char (1 byte)
if let Ok(int_val) = value.try_int(vm)
&& let Some(v) = int_val.as_bigint().to_u8()
{
return vec![v];
}
vec![0]
}
"h" => {
// c_short (2 bytes)
if let Ok(int_val) = value.try_int(vm)
&& let Some(v) = int_val.as_bigint().to_i16()
{
return to_bytes!(v);
}
vec![0; 2]
}
"H" => {
// c_ushort (2 bytes)
if let Ok(int_val) = value.try_int(vm)
&& let Some(v) = int_val.as_bigint().to_u16()
{
return to_bytes!(v);
}
vec![0; 2]
}
"i" => {
// c_int (4 bytes)
if let Ok(int_val) = value.try_int(vm)
&& let Some(v) = int_val.as_bigint().to_i32()
{
return to_bytes!(v);
}
vec![0; 4]
}
"I" => {
// c_uint (4 bytes)
if let Ok(int_val) = value.try_int(vm)
&& let Some(v) = int_val.as_bigint().to_u32()
{
return to_bytes!(v);
}
vec![0; 4]
}
"l" => {
// c_long (platform dependent)
if let Ok(int_val) = value.try_to_value::<libc::c_long>(vm) {
return to_bytes!(int_val);
}
const SIZE: usize = std::mem::size_of::<libc::c_long>();
vec![0; SIZE]
}
"L" => {
// c_ulong (platform dependent)
if let Ok(int_val) = value.try_to_value::<libc::c_ulong>(vm) {
return to_bytes!(int_val);
}
const SIZE: usize = std::mem::size_of::<libc::c_ulong>();
vec![0; SIZE]
}
"q" => {
// c_longlong (8 bytes)
if let Ok(int_val) = value.try_int(vm)
&& let Some(v) = int_val.as_bigint().to_i64()
{
return to_bytes!(v);
}
vec![0; 8]
}
"Q" => {
// c_ulonglong (8 bytes)
if let Ok(int_val) = value.try_int(vm)
&& let Some(v) = int_val.as_bigint().to_u64()
{
return to_bytes!(v);
}
vec![0; 8]
}
"f" => {
// c_float (4 bytes) - int도 허용
if let Ok(float_val) = value.try_float(vm) {
return to_bytes!(float_val.to_f64() as f32);
}
if let Ok(int_val) = value.try_int(vm)
&& let Some(v) = int_val.as_bigint().to_f64()
{
return to_bytes!(v as f32);
}
vec![0; 4]
}
"d" | "g" => {
// c_double (8 bytes) - int도 허용
if let Ok(float_val) = value.try_float(vm) {
return to_bytes!(float_val.to_f64());
}
if let Ok(int_val) = value.try_int(vm)
&& let Some(v) = int_val.as_bigint().to_f64()
{
return to_bytes!(v);
}
vec![0; 8]
}
"?" => {
// c_bool (1 byte)
if let Ok(b) = value.clone().try_to_bool(vm) {
return vec![if b { 1 } else { 0 }];
}
vec![0]
}
"P" | "z" | "Z" => {
// Pointer types (platform pointer size)
vec![0; std::mem::size_of::<usize>()]
}
_ => vec![0],
}
}
impl Constructor for PyCSimple {
type Args = (OptionalArg,);
@@ -272,31 +642,99 @@ impl Constructor for PyCSimple {
_ => vm.ctx.none(), // "z" | "Z" | "P"
}
};
// Check if this is a swapped endian type
let swapped = cls
.as_object()
.get_attr("_swappedbytes_", vm)
.map(|v| v.is_true(vm).unwrap_or(false))
.unwrap_or(false);
let buffer = value_to_bytes_endian(&_type_, &value, swapped, vm);
PyCSimple {
_type_,
value: AtomicCell::new(value),
cdata: PyRwLock::new(CDataObject::from_bytes(buffer, None)),
}
.into_ref_with_type(vm, cls)
.map(Into::into)
}
}
#[pyclass(flags(BASETYPE), with(Constructor))]
#[pyclass(flags(BASETYPE), with(Constructor, AsBuffer))]
impl PyCSimple {
#[pygetset]
fn _objects(&self) -> Option<PyObjectRef> {
self.cdata.read().objects.clone()
}
#[pygetset(name = "value")]
pub fn value(instance: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
let zelf: &Py<Self> = instance
.downcast_ref()
.ok_or_else(|| vm.new_type_error("cannot get value of instance"))?;
Ok(unsafe { (*zelf.value.as_ptr()).clone() })
let raw_value = unsafe { (*zelf.value.as_ptr()).clone() };
// Convert to unsigned if needed for unsigned types
match zelf._type_.as_str() {
"B" | "H" | "I" | "L" | "Q" => {
if let Ok(int_val) = raw_value.try_int(vm) {
let n = int_val.as_bigint();
// Use platform-specific C types for correct unsigned conversion
match zelf._type_.as_str() {
"B" => {
if let Some(v) = n.to_i64() {
return Ok(vm.ctx.new_int((v as u8) as u64).into());
}
}
"H" => {
if let Some(v) = n.to_i64() {
return Ok(vm.ctx.new_int((v as c_ushort) as u64).into());
}
}
"I" => {
if let Some(v) = n.to_i64() {
return Ok(vm.ctx.new_int((v as c_uint) as u64).into());
}
}
"L" => {
if let Some(v) = n.to_i128() {
return Ok(vm.ctx.new_int(v as c_ulong).into());
}
}
"Q" => {
if let Some(v) = n.to_i128() {
return Ok(vm.ctx.new_int(v as c_ulonglong).into());
}
}
_ => {}
};
}
Ok(raw_value)
}
_ => Ok(raw_value),
}
}
#[pygetset(name = "value", setter)]
fn set_value(instance: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
let zelf: PyRef<Self> = instance
.clone()
.downcast()
.map_err(|_| vm.new_type_error("cannot set value of instance"))?;
let content = set_primitive(zelf._type_.as_str(), &value, vm)?;
// Check if this is a swapped endian type
let swapped = instance
.class()
.as_object()
.get_attr("_swappedbytes_", vm)
.map(|v| v.is_true(vm).unwrap_or(false))
.unwrap_or(false);
// Update buffer when value changes
let buffer_bytes = value_to_bytes_endian(&zelf._type_, &content, swapped, vm);
zelf.cdata.write().buffer = buffer_bytes;
zelf.value.store(content);
Ok(())
}
@@ -322,16 +760,214 @@ impl PyCSimple {
} else {
std::mem::size_of::<usize>()
};
let total_size = element_size * (n as usize);
let stg_info = super::util::StgInfo::new(total_size, element_size);
Ok(PyCArrayType {
inner: PyCArray {
typ: PyRwLock::new(cls),
length: AtomicCell::new(n as usize),
element_size: AtomicCell::new(element_size),
buffer: PyRwLock::new(vec![]),
},
stg_info,
typ: PyRwLock::new(cls.clone().into()),
length: AtomicCell::new(n as usize),
element_size: AtomicCell::new(element_size),
}
.to_pyobject(vm))
}
#[pyclassmethod]
fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult {
use super::_ctypes::get_size;
// Get _type_ attribute directly
let type_attr = cls
.as_object()
.get_attr("_type_", vm)
.map_err(|_| vm.new_type_error(format!("'{}' has no _type_ attribute", cls.name())))?;
let type_str = type_attr.str(vm)?.to_string();
let size = get_size(&type_str);
// Create instance with value read from address
let value = if address != 0 && size > 0 {
// Safety: This is inherently unsafe - reading from arbitrary memory address
unsafe {
let ptr = address as *const u8;
let bytes = std::slice::from_raw_parts(ptr, size);
// Convert bytes to appropriate Python value based on type
bytes_to_pyobject(&cls, bytes, vm)?
}
} else {
vm.ctx.none()
};
// Create instance using the type's constructor
let instance = PyCSimple::py_new(cls.clone(), (OptionalArg::Present(value),), vm)?;
Ok(instance)
}
#[pyclassmethod]
fn from_buffer(
cls: PyTypeRef,
source: PyObjectRef,
offset: OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult {
use super::_ctypes::get_size;
let offset = offset.unwrap_or(0);
if offset < 0 {
return Err(vm.new_value_error("offset cannot be negative".to_owned()));
}
let offset = offset as usize;
// Get buffer from source
let buffer = PyBuffer::try_from_object(vm, source.clone())?;
// Check if buffer is writable
if buffer.desc.readonly {
return Err(vm.new_type_error("underlying buffer is not writable".to_owned()));
}
// Get _type_ attribute directly
let type_attr = cls
.as_object()
.get_attr("_type_", vm)
.map_err(|_| vm.new_type_error(format!("'{}' has no _type_ attribute", cls.name())))?;
let type_str = type_attr.str(vm)?.to_string();
let size = get_size(&type_str);
// Check if buffer is large enough
let buffer_len = buffer.desc.len;
if offset + size > buffer_len {
return Err(vm.new_value_error(format!(
"Buffer size too small ({} instead of at least {} bytes)",
buffer_len,
offset + size
)));
}
// Read bytes from buffer at offset
let bytes = buffer.obj_bytes();
let data = &bytes[offset..offset + size];
let value = bytes_to_pyobject(&cls, data, vm)?;
// Create instance
let instance = PyCSimple::py_new(cls.clone(), (OptionalArg::Present(value),), vm)?;
// TODO: Store reference to source in _objects to keep buffer alive
Ok(instance)
}
#[pyclassmethod]
fn from_buffer_copy(
cls: PyTypeRef,
source: ArgBytesLike,
offset: OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult {
use super::_ctypes::get_size;
let offset = offset.unwrap_or(0);
if offset < 0 {
return Err(vm.new_value_error("offset cannot be negative".to_owned()));
}
let offset = offset as usize;
// Get _type_ attribute directly for simple types
let type_attr = cls
.as_object()
.get_attr("_type_", vm)
.map_err(|_| vm.new_type_error(format!("'{}' has no _type_ attribute", cls.name())))?;
let type_str = type_attr.str(vm)?.to_string();
let size = get_size(&type_str);
// Borrow bytes from source
let source_bytes = source.borrow_buf();
let buffer_len = source_bytes.len();
// Check if buffer is large enough
if offset + size > buffer_len {
return Err(vm.new_value_error(format!(
"Buffer size too small ({} instead of at least {} bytes)",
buffer_len,
offset + size
)));
}
// Copy bytes from buffer at offset
let data = &source_bytes[offset..offset + size];
let value = bytes_to_pyobject(&cls, data, vm)?;
// Create instance (independent copy, no reference tracking)
PyCSimple::py_new(cls.clone(), (OptionalArg::Present(value),), vm)
}
#[pyclassmethod]
fn in_dll(cls: PyTypeRef, dll: PyObjectRef, name: PyStrRef, vm: &VirtualMachine) -> PyResult {
use super::_ctypes::get_size;
use libloading::Symbol;
// Get the library handle from dll object
let handle = if let Ok(int_handle) = dll.try_int(vm) {
// dll is an integer handle
int_handle
.as_bigint()
.to_usize()
.ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))?
} else {
// dll is a CDLL/PyDLL/WinDLL object with _handle attribute
dll.get_attr("_handle", vm)?
.try_int(vm)?
.as_bigint()
.to_usize()
.ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))?
};
// Get the library from cache
let library_cache = crate::stdlib::ctypes::library::libcache().read();
let library = library_cache
.get_lib(handle)
.ok_or_else(|| vm.new_attribute_error("Library not found".to_owned()))?;
// Get symbol address from library
let symbol_name = format!("{}\0", name.as_str());
let inner_lib = library.lib.lock();
let symbol_address = if let Some(lib) = &*inner_lib {
unsafe {
// Try to get the symbol from the library
let symbol: Symbol<'_, *mut u8> = lib.get(symbol_name.as_bytes()).map_err(|e| {
vm.new_attribute_error(format!("{}: symbol '{}' not found", e, name.as_str()))
})?;
*symbol as usize
}
} else {
return Err(vm.new_attribute_error("Library is closed".to_owned()));
};
// Get _type_ attribute and size
let type_attr = cls
.as_object()
.get_attr("_type_", vm)
.map_err(|_| vm.new_type_error(format!("'{}' has no _type_ attribute", cls.name())))?;
let type_str = type_attr.str(vm)?.to_string();
let size = get_size(&type_str);
// Read value from symbol address
let value = if symbol_address != 0 && size > 0 {
// Safety: Reading from a symbol address provided by dlsym
unsafe {
let ptr = symbol_address as *const u8;
let bytes = std::slice::from_raw_parts(ptr, size);
bytes_to_pyobject(&cls, bytes, vm)?
}
} else {
vm.ctx.none()
};
// Create instance
let instance = PyCSimple::py_new(cls.clone(), (OptionalArg::Present(value),), vm)?;
// Store base reference to keep dll alive
if let Ok(simple_ref) = instance.clone().downcast::<PyCSimple>() {
simple_ref.cdata.write().base = Some(dll);
}
Ok(instance)
}
}
impl PyCSimple {
@@ -372,3 +1008,40 @@ impl PyCSimple {
None
}
}
static SIMPLE_BUFFER_METHODS: BufferMethods = BufferMethods {
obj_bytes: |buffer| {
rustpython_common::lock::PyMappedRwLockReadGuard::map(
rustpython_common::lock::PyRwLockReadGuard::map(
buffer.obj_as::<PyCSimple>().cdata.read(),
|x: &CDataObject| x,
),
|x: &CDataObject| x.buffer.as_slice(),
)
.into()
},
obj_bytes_mut: |buffer| {
rustpython_common::lock::PyMappedRwLockWriteGuard::map(
rustpython_common::lock::PyRwLockWriteGuard::map(
buffer.obj_as::<PyCSimple>().cdata.write(),
|x: &mut CDataObject| x,
),
|x: &mut CDataObject| x.buffer.as_mut_slice(),
)
.into()
},
release: |_| {},
retain: |_| {},
};
impl AsBuffer for PyCSimple {
fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> {
let buffer_len = zelf.cdata.read().buffer.len();
let buf = PyBuffer::new(
zelf.to_owned().into(),
BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes
&SIMPLE_BUFFER_METHODS,
);
Ok(buf)
}
}
+36 -9
View File
@@ -5,6 +5,7 @@ use crate::{AsObject, Py, PyObjectRef, PyResult, VirtualMachine};
use num_traits::ToPrimitive;
use super::structure::PyCStructure;
use super::union::PyCUnion;
#[pyclass(name = "PyCFieldType", base = PyType, module = "_ctypes")]
#[derive(PyPayload, Debug)]
@@ -102,14 +103,23 @@ impl GetDescriptor for PyCField {
_ => return Ok(zelf.into()),
};
// Instance attribute access - read value from the structure's buffer
// Instance attribute access - read value from the structure/union's buffer
if let Some(structure) = obj.downcast_ref::<PyCStructure>() {
let buffer = structure.buffer.read();
let cdata = structure.cdata.read();
let offset = zelf.byte_offset;
let size = zelf.byte_size;
if offset + size <= buffer.len() {
let bytes = &buffer[offset..offset + size];
if offset + size <= cdata.buffer.len() {
let bytes = &cdata.buffer[offset..offset + size];
return PyCField::bytes_to_value(bytes, size, vm);
}
} else if let Some(union) = obj.downcast_ref::<PyCUnion>() {
let cdata = union.cdata.read();
let offset = zelf.byte_offset;
let size = zelf.byte_size;
if offset + size <= cdata.buffer.len() {
let bytes = &cdata.buffer[offset..offset + size];
return PyCField::bytes_to_value(bytes, size, vm);
}
}
@@ -187,7 +197,7 @@ impl PyCField {
.downcast_ref::<PyCField>()
.ok_or_else(|| vm.new_type_error("expected CField".to_owned()))?;
// Get the structure instance - use downcast_ref() to access the struct data
// Get the structure/union instance - use downcast_ref() to access the struct data
if let Some(structure) = obj.downcast_ref::<PyCStructure>() {
match value {
PySetterValue::Assign(value) => {
@@ -195,9 +205,9 @@ impl PyCField {
let size = zelf.byte_size;
let bytes = PyCField::value_to_bytes(&value, size, vm)?;
let mut buffer = structure.buffer.write();
if offset + size <= buffer.len() {
buffer[offset..offset + size].copy_from_slice(&bytes);
let mut cdata = structure.cdata.write();
if offset + size <= cdata.buffer.len() {
cdata.buffer[offset..offset + size].copy_from_slice(&bytes);
}
Ok(())
}
@@ -205,9 +215,26 @@ impl PyCField {
Err(vm.new_type_error("cannot delete structure field".to_owned()))
}
}
} else if let Some(union) = obj.downcast_ref::<PyCUnion>() {
match value {
PySetterValue::Assign(value) => {
let offset = zelf.byte_offset;
let size = zelf.byte_size;
let bytes = PyCField::value_to_bytes(&value, size, vm)?;
let mut cdata = union.cdata.write();
if offset + size <= cdata.buffer.len() {
cdata.buffer[offset..offset + size].copy_from_slice(&bytes);
}
Ok(())
}
PySetterValue::Delete => {
Err(vm.new_type_error("cannot delete union field".to_owned()))
}
}
} else {
Err(vm.new_type_error(format!(
"descriptor works only on Structure instances, got {}",
"descriptor works only on Structure or Union instances, got {}",
obj.class().name()
)))
}
+115 -13
View File
@@ -5,9 +5,10 @@ use crate::convert::ToPyObject;
use crate::function::FuncArgs;
use crate::stdlib::ctypes::PyCData;
use crate::stdlib::ctypes::base::{PyCSimple, ffi_type_from_str};
use crate::stdlib::ctypes::thunk::PyCThunk;
use crate::types::Representable;
use crate::types::{Callable, Constructor};
use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine};
use crate::{AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine};
use crossbeam_utils::atomic::AtomicCell;
use libffi::middle::{Arg, Cif, CodePtr, Type};
use libloading::Symbol;
@@ -76,10 +77,73 @@ impl ReturnType for PyTypeRef {
fn from_ffi_type(
&self,
_value: *mut ffi::c_void,
_vm: &VirtualMachine,
value: *mut ffi::c_void,
vm: &VirtualMachine,
) -> PyResult<Option<PyObjectRef>> {
todo!()
// Get the type code from _type_ attribute
let type_code = self
.get_class_attr(vm.ctx.intern_str("_type_"))
.and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string()));
let result = match type_code.as_deref() {
Some("b") => vm
.ctx
.new_int(unsafe { *(value as *const i8) } as i32)
.into(),
Some("B") => vm
.ctx
.new_int(unsafe { *(value as *const u8) } as i32)
.into(),
Some("c") => vm
.ctx
.new_bytes(vec![unsafe { *(value as *const u8) }])
.into(),
Some("h") => vm
.ctx
.new_int(unsafe { *(value as *const i16) } as i32)
.into(),
Some("H") => vm
.ctx
.new_int(unsafe { *(value as *const u16) } as i32)
.into(),
Some("i") => vm.ctx.new_int(unsafe { *(value as *const i32) }).into(),
Some("I") => vm.ctx.new_int(unsafe { *(value as *const u32) }).into(),
Some("l") => vm
.ctx
.new_int(unsafe { *(value as *const libc::c_long) })
.into(),
Some("L") => vm
.ctx
.new_int(unsafe { *(value as *const libc::c_ulong) })
.into(),
Some("q") => vm
.ctx
.new_int(unsafe { *(value as *const libc::c_longlong) })
.into(),
Some("Q") => vm
.ctx
.new_int(unsafe { *(value as *const libc::c_ulonglong) })
.into(),
Some("f") => vm
.ctx
.new_float(unsafe { *(value as *const f32) } as f64)
.into(),
Some("d") => vm.ctx.new_float(unsafe { *(value as *const f64) }).into(),
Some("P") | Some("z") | Some("Z") => vm.ctx.new_int(value as usize).into(),
Some("?") => vm
.ctx
.new_bool(unsafe { *(value as *const u8) } != 0)
.into(),
None => {
// No _type_ attribute, try to create an instance of the type
// This handles cases like Structure or Array return types
return Ok(Some(
vm.ctx.new_int(unsafe { *(value as *const i32) }).into(),
));
}
_ => return Err(vm.new_type_error("Unsupported return type".to_string())),
};
Ok(Some(result))
}
}
@@ -219,6 +283,49 @@ impl Constructor for PyCFuncPtr {
.map(Into::into);
}
// Check if first argument is a Python callable (callback creation)
if first_arg.is_callable() {
// Get argument types and result type from the class
let argtypes = cls.get_attr(vm.ctx.intern_str("_argtypes_"));
let restype = cls.get_attr(vm.ctx.intern_str("_restype_"));
// Create the thunk (C-callable wrapper for the Python function)
let thunk = PyCThunk::new(first_arg.clone(), argtypes.clone(), restype.clone(), vm)?;
let code_ptr = thunk.code_ptr();
// Parse argument types for storage
let arg_type_vec: Option<Vec<PyTypeRef>> = if let Some(ref args) = argtypes {
if vm.is_none(args) {
None
} else {
let mut types = Vec::new();
for item in args.try_to_value::<Vec<PyObjectRef>>(vm)? {
types.push(item.downcast::<PyType>().map_err(|_| {
vm.new_type_error("_argtypes_ must be a sequence of types".to_string())
})?);
}
Some(types)
}
} else {
None
};
// Store the thunk as a Python object to keep it alive
let thunk_ref: PyRef<PyCThunk> = thunk.into_ref(&vm.ctx);
return Self {
ptr: PyRwLock::new(Some(code_ptr)),
needs_free: AtomicCell::new(true),
arg_types: PyRwLock::new(arg_type_vec),
_flags_: AtomicCell::new(0),
res_type: PyRwLock::new(restype),
name: PyRwLock::new(Some("<callback>".to_string())),
handler: thunk_ref.into(),
}
.into_ref_with_type(vm, cls)
.map(Into::into);
}
Err(vm.new_type_error("Expected an integer address or a tuple"))
}
}
@@ -246,7 +353,8 @@ impl Callable for PyCFuncPtr {
let return_type = zelf.res_type.read();
let ffi_return_type = return_type
.as_ref()
.and_then(|t| ReturnType::to_ffi_type(&t.clone().downcast::<PyType>().unwrap()))
.and_then(|t| t.clone().downcast::<PyType>().ok())
.and_then(|t| ReturnType::to_ffi_type(&t))
.unwrap_or_else(Type::i32);
let cif = Cif::new(ffi_arg_types, ffi_return_type);
@@ -269,14 +377,8 @@ impl Callable for PyCFuncPtr {
let mut output: c_void = unsafe { cif.call(*code_ptr, &ffi_args) };
let return_type = return_type
.as_ref()
.map(|f| {
f.clone()
.downcast::<PyType>()
.unwrap()
.from_ffi_type(&mut output, vm)
.ok()
.flatten()
})
.and_then(|f| f.clone().downcast::<PyType>().ok())
.map(|f| f.from_ffi_type(&mut output, vm).ok().flatten())
.unwrap_or_else(|| Some(vm.ctx.new_int(output as i32).as_object().to_pyobject(vm)));
if let Some(return_type) = return_type {
Ok(return_type)
+196 -12
View File
@@ -6,33 +6,36 @@ use crate::builtins::{PyType, PyTypeRef};
use crate::convert::ToPyObject;
use crate::protocol::PyNumberMethods;
use crate::stdlib::ctypes::PyCData;
use crate::types::AsNumber;
use crate::{PyObjectRef, PyResult, VirtualMachine};
use crate::types::{AsNumber, Constructor};
use crate::{AsObject, PyObjectRef, PyPayload, PyResult, VirtualMachine};
use super::util::StgInfo;
#[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")]
#[derive(PyPayload, Debug)]
pub struct PyCPointerType {
#[allow(dead_code)]
pub(crate) inner: PyCPointer,
pub stg_info: StgInfo,
}
#[pyclass(flags(IMMUTABLETYPE), with(AsNumber))]
impl PyCPointerType {
#[pymethod]
fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult {
use super::array::{PyCArray, PyCArrayType};
use super::array::PyCArrayType;
if n < 0 {
return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}")));
}
// Pointer size
let element_size = std::mem::size_of::<usize>();
let total_size = element_size * (n as usize);
let mut stg_info = super::util::StgInfo::new(total_size, element_size);
stg_info.length = n as usize;
Ok(PyCArrayType {
inner: PyCArray {
typ: PyRwLock::new(cls),
length: AtomicCell::new(n as usize),
element_size: AtomicCell::new(element_size),
buffer: PyRwLock::new(vec![]),
},
stg_info,
typ: PyRwLock::new(cls.as_object().to_owned()),
length: AtomicCell::new(n as usize),
element_size: AtomicCell::new(element_size),
}
.to_pyobject(vm))
}
@@ -69,7 +72,23 @@ pub struct PyCPointer {
contents: PyRwLock<PyObjectRef>,
}
#[pyclass(flags(BASETYPE, IMMUTABLETYPE))]
impl Constructor for PyCPointer {
type Args = (crate::function::OptionalArg<PyObjectRef>,);
fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult {
// Get the initial contents value if provided
let initial_contents = args.0.into_option().unwrap_or_else(|| vm.ctx.none());
// Create a new PyCPointer instance with the provided value
PyCPointer {
contents: PyRwLock::new(initial_contents),
}
.into_ref_with_type(vm, cls)
.map(Into::into)
}
}
#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor))]
impl PyCPointer {
// TODO: not correct
#[pygetset]
@@ -78,8 +97,173 @@ impl PyCPointer {
Ok(contents)
}
#[pygetset(setter)]
fn set_contents(&self, contents: PyObjectRef) -> PyResult<()> {
fn set_contents(&self, contents: PyObjectRef, _vm: &VirtualMachine) -> PyResult<()> {
// Validate that the contents is a CData instance if we have a _type_
// For now, just store it
*self.contents.write() = contents;
Ok(())
}
#[pymethod]
fn __init__(
&self,
value: crate::function::OptionalArg<PyObjectRef>,
_vm: &VirtualMachine,
) -> PyResult<()> {
// Pointer can be initialized with 0 or 1 argument
// If 1 argument is provided, it should be a CData instance
if let crate::function::OptionalArg::Present(val) = value {
*self.contents.write() = val;
}
Ok(())
}
#[pyclassmethod]
fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult {
if address == 0 {
return Err(vm.new_value_error("NULL pointer access".to_owned()));
}
// Pointer just stores the address value
Ok(PyCPointer {
contents: PyRwLock::new(vm.ctx.new_int(address).into()),
}
.into_ref_with_type(vm, cls)?
.into())
}
#[pyclassmethod]
fn from_buffer(
cls: PyTypeRef,
source: PyObjectRef,
offset: crate::function::OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult {
use crate::TryFromObject;
use crate::protocol::PyBuffer;
let offset = offset.unwrap_or(0);
if offset < 0 {
return Err(vm.new_value_error("offset cannot be negative".to_owned()));
}
let offset = offset as usize;
let size = std::mem::size_of::<usize>();
let buffer = PyBuffer::try_from_object(vm, source.clone())?;
if buffer.desc.readonly {
return Err(vm.new_type_error("underlying buffer is not writable".to_owned()));
}
let buffer_len = buffer.desc.len;
if offset + size > buffer_len {
return Err(vm.new_value_error(format!(
"Buffer size too small ({} instead of at least {} bytes)",
buffer_len,
offset + size
)));
}
// Read pointer value from buffer
let bytes = buffer.obj_bytes();
let ptr_bytes = &bytes[offset..offset + size];
let ptr_val = usize::from_ne_bytes(ptr_bytes.try_into().expect("size is checked above"));
Ok(PyCPointer {
contents: PyRwLock::new(vm.ctx.new_int(ptr_val).into()),
}
.into_ref_with_type(vm, cls)?
.into())
}
#[pyclassmethod]
fn from_buffer_copy(
cls: PyTypeRef,
source: crate::function::ArgBytesLike,
offset: crate::function::OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult {
let offset = offset.unwrap_or(0);
if offset < 0 {
return Err(vm.new_value_error("offset cannot be negative".to_owned()));
}
let offset = offset as usize;
let size = std::mem::size_of::<usize>();
let source_bytes = source.borrow_buf();
let buffer_len = source_bytes.len();
if offset + size > buffer_len {
return Err(vm.new_value_error(format!(
"Buffer size too small ({} instead of at least {} bytes)",
buffer_len,
offset + size
)));
}
// Read pointer value from buffer
let ptr_bytes = &source_bytes[offset..offset + size];
let ptr_val = usize::from_ne_bytes(ptr_bytes.try_into().expect("size is checked above"));
Ok(PyCPointer {
contents: PyRwLock::new(vm.ctx.new_int(ptr_val).into()),
}
.into_ref_with_type(vm, cls)?
.into())
}
#[pyclassmethod]
fn in_dll(
cls: PyTypeRef,
dll: PyObjectRef,
name: crate::builtins::PyStrRef,
vm: &VirtualMachine,
) -> PyResult {
use libloading::Symbol;
// Get the library handle from dll object
let handle = if let Ok(int_handle) = dll.try_int(vm) {
// dll is an integer handle
int_handle
.as_bigint()
.to_usize()
.ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))?
} else {
// dll is a CDLL/PyDLL/WinDLL object with _handle attribute
dll.get_attr("_handle", vm)?
.try_int(vm)?
.as_bigint()
.to_usize()
.ok_or_else(|| vm.new_value_error("Invalid library handle".to_owned()))?
};
// Get the library from cache
let library_cache = crate::stdlib::ctypes::library::libcache().read();
let library = library_cache
.get_lib(handle)
.ok_or_else(|| vm.new_attribute_error("Library not found".to_owned()))?;
// Get symbol address from library
let symbol_name = format!("{}\0", name.as_str());
let inner_lib = library.lib.lock();
let symbol_address = if let Some(lib) = &*inner_lib {
unsafe {
// Try to get the symbol from the library
let symbol: Symbol<'_, *mut u8> = lib.get(symbol_name.as_bytes()).map_err(|e| {
vm.new_attribute_error(format!("{}: symbol '{}' not found", e, name.as_str()))
})?;
*symbol as usize
}
} else {
return Err(vm.new_attribute_error("Library is closed".to_owned()));
};
// For pointer types, we return a pointer to the symbol address
Ok(PyCPointer {
contents: PyRwLock::new(vm.ctx.new_int(symbol_address).into()),
}
.into_ref_with_type(vm, cls)?
.into())
}
}
+215 -26
View File
@@ -1,12 +1,13 @@
use super::base::PyCData;
use super::base::{CDataObject, PyCData};
use super::field::PyCField;
use super::util::StgInfo;
use crate::builtins::{PyList, PyStr, PyTuple, PyType, PyTypeRef};
use crate::convert::ToPyObject;
use crate::function::FuncArgs;
use crate::protocol::PyNumberMethods;
use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer, PyNumberMethods};
use crate::stdlib::ctypes::_ctypes::get_size;
use crate::types::{AsNumber, Constructor};
use crate::{AsObject, PyObjectRef, PyPayload, PyResult, VirtualMachine};
use crate::types::{AsBuffer, AsNumber, Constructor};
use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine};
use crossbeam_utils::atomic::AtomicCell;
use indexmap::IndexMap;
use num_traits::ToPrimitive;
@@ -16,7 +17,10 @@ use std::fmt::Debug;
/// PyCStructType - metaclass for Structure
#[pyclass(name = "PyCStructType", base = PyType, module = "_ctypes")]
#[derive(Debug, PyPayload)]
pub struct PyCStructType {}
pub struct PyCStructType {
#[allow(dead_code)]
pub stg_info: StgInfo,
}
impl Constructor for PyCStructType {
type Args = FuncArgs;
@@ -92,10 +96,10 @@ impl PyCStructType {
let size = Self::get_field_size(&field_type, vm)?;
// Create CField descriptor (accepts any ctypes type including arrays)
let cfield = PyCField::new(name.clone(), field_type, offset, size, index);
let c_field = PyCField::new(name.clone(), field_type, offset, size, index);
// Set the CField as a class attribute
cls.set_attr(vm.ctx.intern_str(name), cfield.to_pyobject(vm));
cls.set_attr(vm.ctx.intern_str(name), c_field.to_pyobject(vm));
offset += size;
}
@@ -133,22 +137,46 @@ impl PyCStructType {
Ok(std::mem::size_of::<usize>())
}
/// Get the alignment of a ctypes type
fn get_field_align(field_type: &PyObjectRef, vm: &VirtualMachine) -> usize {
// Try to get _type_ attribute for simple types
if let Some(align) = field_type
.get_attr("_type_", vm)
.ok()
.and_then(|type_attr| type_attr.str(vm).ok())
.and_then(|type_str| {
let s = type_str.to_string();
(s.len() == 1).then(|| get_size(&s)) // alignment == size for simple types
})
{
return align;
}
// Default alignment
1
}
#[pymethod]
fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult {
use super::array::{PyCArray, PyCArrayType};
use super::array::PyCArrayType;
use crate::stdlib::ctypes::_ctypes::size_of;
if n < 0 {
return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}")));
}
// TODO: Calculate element size properly
// For structures, element size is the structure size (sum of field sizes)
let element_size = std::mem::size_of::<usize>(); // Default, should calculate from fields
// Calculate element size from the Structure type
let element_size = size_of(cls.clone().into(), vm)?;
let total_size = element_size
.checked_mul(n as usize)
.ok_or_else(|| vm.new_overflow_error("array size too large".to_owned()))?;
let mut stg_info = super::util::StgInfo::new(total_size, element_size);
stg_info.length = n as usize;
Ok(PyCArrayType {
inner: PyCArray {
typ: PyRwLock::new(cls),
length: AtomicCell::new(n as usize),
element_size: AtomicCell::new(element_size),
buffer: PyRwLock::new(vec![]),
},
stg_info,
typ: PyRwLock::new(cls.clone().into()),
length: AtomicCell::new(n as usize),
element_size: AtomicCell::new(element_size),
}
.to_pyobject(vm))
}
@@ -193,19 +221,17 @@ pub struct FieldInfo {
)]
#[derive(PyPayload)]
pub struct PyCStructure {
/// Raw memory buffer for the structure
pub(super) buffer: PyRwLock<Vec<u8>>,
/// Common CDataObject for memory buffer
pub(super) cdata: PyRwLock<CDataObject>,
/// Field information (name -> FieldInfo)
#[allow(dead_code)]
pub(super) fields: PyRwLock<IndexMap<String, FieldInfo>>,
/// Total size of the structure
pub(super) size: AtomicCell<usize>,
}
impl Debug for PyCStructure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PyCStructure")
.field("size", &self.size.load())
.field("size", &self.cdata.read().size())
.finish()
}
}
@@ -219,6 +245,7 @@ impl Constructor for PyCStructure {
let mut fields_map = IndexMap::new();
let mut total_size = 0usize;
let mut max_align = 1usize;
if let Some(fields_attr) = fields_attr {
let fields: Vec<PyObjectRef> = if let Some(list) = fields_attr.downcast_ref::<PyList>()
@@ -244,6 +271,8 @@ impl Constructor for PyCStructure {
let name = name.to_string();
let field_type = field_tuple.get(1).unwrap().clone();
let size = PyCStructType::get_field_size(&field_type, vm)?;
let field_align = PyCStructType::get_field_align(&field_type, vm);
max_align = max_align.max(field_align);
let type_ref = field_type
.downcast::<PyType>()
@@ -265,12 +294,11 @@ impl Constructor for PyCStructure {
}
// Initialize buffer with zeros
let buffer = vec![0u8; total_size];
let mut stg_info = StgInfo::new(total_size, max_align);
stg_info.length = fields_map.len();
let instance = PyCStructure {
buffer: PyRwLock::new(buffer),
cdata: PyRwLock::new(CDataObject::from_stg_info(&stg_info)),
fields: PyRwLock::new(fields_map.clone()),
size: AtomicCell::new(total_size),
};
// Handle keyword arguments for field initialization
@@ -305,9 +333,170 @@ impl Constructor for PyCStructure {
#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor))]
impl PyCStructure {
#[pygetset]
fn _objects(&self) -> Option<PyObjectRef> {
self.cdata.read().objects.clone()
}
#[pygetset]
fn _fields_(&self, vm: &VirtualMachine) -> PyObjectRef {
// Return the _fields_ from the class, not instance
vm.ctx.none()
}
#[pyclassmethod]
fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult {
use crate::stdlib::ctypes::_ctypes::size_of;
// Get size from cls
let size = size_of(cls.clone().into(), vm)?;
// Read data from address
if address == 0 || size == 0 {
return Err(vm.new_value_error("NULL pointer access".to_owned()));
}
let data = unsafe {
let ptr = address as *const u8;
std::slice::from_raw_parts(ptr, size).to_vec()
};
// Create instance
Ok(PyCStructure {
cdata: PyRwLock::new(CDataObject::from_bytes(data, None)),
fields: PyRwLock::new(IndexMap::new()),
}
.into_ref_with_type(vm, cls)?
.into())
}
#[pyclassmethod]
fn from_buffer(
cls: PyTypeRef,
source: PyObjectRef,
offset: crate::function::OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult {
use crate::TryFromObject;
use crate::protocol::PyBuffer;
use crate::stdlib::ctypes::_ctypes::size_of;
let offset = offset.unwrap_or(0);
if offset < 0 {
return Err(vm.new_value_error("offset cannot be negative".to_owned()));
}
let offset = offset as usize;
// Get buffer from source
let buffer = PyBuffer::try_from_object(vm, source.clone())?;
// Check if buffer is writable
if buffer.desc.readonly {
return Err(vm.new_type_error("underlying buffer is not writable".to_owned()));
}
// Get size from cls
let size = size_of(cls.clone().into(), vm)?;
// Check if buffer is large enough
let buffer_len = buffer.desc.len;
if offset + size > buffer_len {
return Err(vm.new_value_error(format!(
"Buffer size too small ({} instead of at least {} bytes)",
buffer_len,
offset + size
)));
}
// Read bytes from buffer at offset
let bytes = buffer.obj_bytes();
let data = bytes[offset..offset + size].to_vec();
// Create instance
Ok(PyCStructure {
cdata: PyRwLock::new(CDataObject::from_bytes(data, Some(source))),
fields: PyRwLock::new(IndexMap::new()),
}
.into_ref_with_type(vm, cls)?
.into())
}
#[pyclassmethod]
fn from_buffer_copy(
cls: PyTypeRef,
source: crate::function::ArgBytesLike,
offset: crate::function::OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult {
use crate::stdlib::ctypes::_ctypes::size_of;
let offset = offset.unwrap_or(0);
if offset < 0 {
return Err(vm.new_value_error("offset cannot be negative".to_owned()));
}
let offset = offset as usize;
// Get size from cls
let size = size_of(cls.clone().into(), vm)?;
// Borrow bytes from source
let source_bytes = source.borrow_buf();
let buffer_len = source_bytes.len();
// Check if buffer is large enough
if offset + size > buffer_len {
return Err(vm.new_value_error(format!(
"Buffer size too small ({} instead of at least {} bytes)",
buffer_len,
offset + size
)));
}
// Copy bytes from buffer at offset
let data = source_bytes[offset..offset + size].to_vec();
// Create instance
Ok(PyCStructure {
cdata: PyRwLock::new(CDataObject::from_bytes(data, None)),
fields: PyRwLock::new(IndexMap::new()),
}
.into_ref_with_type(vm, cls)?
.into())
}
}
static STRUCTURE_BUFFER_METHODS: BufferMethods = BufferMethods {
obj_bytes: |buffer| {
rustpython_common::lock::PyMappedRwLockReadGuard::map(
rustpython_common::lock::PyRwLockReadGuard::map(
buffer.obj_as::<PyCStructure>().cdata.read(),
|x: &CDataObject| x,
),
|x: &CDataObject| x.buffer.as_slice(),
)
.into()
},
obj_bytes_mut: |buffer| {
rustpython_common::lock::PyMappedRwLockWriteGuard::map(
rustpython_common::lock::PyRwLockWriteGuard::map(
buffer.obj_as::<PyCStructure>().cdata.write(),
|x: &mut CDataObject| x,
),
|x: &mut CDataObject| x.buffer.as_mut_slice(),
)
.into()
},
release: |_| {},
retain: |_| {},
};
impl AsBuffer for PyCStructure {
fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> {
let buffer_len = zelf.cdata.read().buffer.len();
let buf = PyBuffer::new(
zelf.to_owned().into(),
BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes
&STRUCTURE_BUFFER_METHODS,
);
Ok(buf)
}
}
+314 -17
View File
@@ -1,22 +1,319 @@
//! Yes, really, this is not a typo.
//! FFI callback (thunk) implementation for ctypes.
//!
//! This module implements CThunkObject which wraps Python callables
//! to be callable from C code via libffi closures.
// typedef struct {
// PyObject_VAR_HEAD
// ffi_closure *pcl_write; /* the C callable, writeable */
// void *pcl_exec; /* the C callable, executable */
// ffi_cif cif;
// int flags;
// PyObject *converters;
// PyObject *callable;
// PyObject *restype;
// SETFUNC setfunc;
// ffi_type *ffi_restype;
// ffi_type *atypes[1];
// } CThunkObject;
use crate::builtins::{PyStr, PyType, PyTypeRef};
use crate::vm::thread::with_current_vm;
use crate::{PyObjectRef, PyPayload, PyResult, VirtualMachine};
use libffi::low;
use libffi::middle::{Cif, Closure, CodePtr, Type};
use num_traits::ToPrimitive;
use rustpython_common::lock::PyRwLock;
use std::ffi::c_void;
use std::fmt::Debug;
use super::base::ffi_type_from_str;
/// Userdata passed to the libffi callback.
/// This contains everything needed to invoke the Python callable.
pub struct ThunkUserData {
/// The Python callable to invoke
pub callable: PyObjectRef,
/// Argument types for conversion
pub arg_types: Vec<PyTypeRef>,
/// Result type for conversion (None means void)
pub res_type: Option<PyTypeRef>,
}
/// Get the type code string from a ctypes type
fn get_type_code(ty: &PyTypeRef, vm: &VirtualMachine) -> Option<String> {
ty.get_attr(vm.ctx.intern_str("_type_"))
.and_then(|t| t.downcast_ref::<PyStr>().map(|s| s.to_string()))
}
/// Convert a C value to a Python object based on the type code
fn ffi_to_python(ty: &PyTypeRef, ptr: *const c_void, vm: &VirtualMachine) -> PyObjectRef {
let type_code = get_type_code(ty, vm);
// SAFETY: ptr is guaranteed to be valid by libffi calling convention
unsafe {
match type_code.as_deref() {
Some("b") => vm.ctx.new_int(*(ptr as *const i8) as i32).into(),
Some("B") => vm.ctx.new_int(*(ptr as *const u8) as i32).into(),
Some("c") => vm.ctx.new_bytes(vec![*(ptr as *const u8)]).into(),
Some("h") => vm.ctx.new_int(*(ptr as *const i16) as i32).into(),
Some("H") => vm.ctx.new_int(*(ptr as *const u16) as i32).into(),
Some("i") => vm.ctx.new_int(*(ptr as *const i32)).into(),
Some("I") => vm.ctx.new_int(*(ptr as *const u32)).into(),
Some("l") => vm.ctx.new_int(*(ptr as *const libc::c_long)).into(),
Some("L") => vm.ctx.new_int(*(ptr as *const libc::c_ulong)).into(),
Some("q") => vm.ctx.new_int(*(ptr as *const libc::c_longlong)).into(),
Some("Q") => vm.ctx.new_int(*(ptr as *const libc::c_ulonglong)).into(),
Some("f") => vm.ctx.new_float(*(ptr as *const f32) as f64).into(),
Some("d") => vm.ctx.new_float(*(ptr as *const f64)).into(),
Some("P") | Some("z") | Some("Z") => vm.ctx.new_int(ptr as usize).into(),
_ => vm.ctx.none(),
}
}
}
/// Convert a Python object to a C value and store it at the result pointer
fn python_to_ffi(obj: PyResult, ty: &PyTypeRef, result: *mut c_void, vm: &VirtualMachine) {
let obj = match obj {
Ok(o) => o,
Err(_) => return, // Exception occurred, leave result as-is
};
let type_code = get_type_code(ty, vm);
// SAFETY: result is guaranteed to be valid by libffi calling convention
unsafe {
match type_code.as_deref() {
Some("b") => {
if let Ok(i) = obj.try_int(vm) {
*(result as *mut i8) = i.as_bigint().to_i8().unwrap_or(0);
}
}
Some("B") => {
if let Ok(i) = obj.try_int(vm) {
*(result as *mut u8) = i.as_bigint().to_u8().unwrap_or(0);
}
}
Some("c") => {
if let Ok(i) = obj.try_int(vm) {
*(result as *mut u8) = i.as_bigint().to_u8().unwrap_or(0);
}
}
Some("h") => {
if let Ok(i) = obj.try_int(vm) {
*(result as *mut i16) = i.as_bigint().to_i16().unwrap_or(0);
}
}
Some("H") => {
if let Ok(i) = obj.try_int(vm) {
*(result as *mut u16) = i.as_bigint().to_u16().unwrap_or(0);
}
}
Some("i") => {
if let Ok(i) = obj.try_int(vm) {
*(result as *mut i32) = i.as_bigint().to_i32().unwrap_or(0);
}
}
Some("I") => {
if let Ok(i) = obj.try_int(vm) {
*(result as *mut u32) = i.as_bigint().to_u32().unwrap_or(0);
}
}
Some("l") | Some("q") => {
if let Ok(i) = obj.try_int(vm) {
*(result as *mut i64) = i.as_bigint().to_i64().unwrap_or(0);
}
}
Some("L") | Some("Q") => {
if let Ok(i) = obj.try_int(vm) {
*(result as *mut u64) = i.as_bigint().to_u64().unwrap_or(0);
}
}
Some("f") => {
if let Ok(f) = obj.try_float(vm) {
*(result as *mut f32) = f.to_f64() as f32;
}
}
Some("d") => {
if let Ok(f) = obj.try_float(vm) {
*(result as *mut f64) = f.to_f64();
}
}
Some("P") | Some("z") | Some("Z") => {
if let Ok(i) = obj.try_int(vm) {
*(result as *mut usize) = i.as_bigint().to_usize().unwrap_or(0);
}
}
_ => {}
}
}
}
/// The callback function that libffi calls when the closure is invoked.
/// This function converts C arguments to Python objects, calls the Python
/// callable, and converts the result back to C.
unsafe extern "C" fn thunk_callback(
_cif: &low::ffi_cif,
result: &mut c_void,
args: *const *const c_void,
userdata: &ThunkUserData,
) {
with_current_vm(|vm| {
// Convert C arguments to Python objects
let py_args: Vec<PyObjectRef> = userdata
.arg_types
.iter()
.enumerate()
.map(|(i, ty)| {
let arg_ptr = unsafe { *args.add(i) };
ffi_to_python(ty, arg_ptr, vm)
})
.collect();
// Call the Python callable
let py_result = userdata.callable.call(py_args, vm);
// Convert result back to C type
if let Some(ref res_type) = userdata.res_type {
python_to_ffi(py_result, res_type, result as *mut c_void, vm);
}
});
}
/// Holds the closure and userdata together to ensure proper lifetime.
/// The userdata is leaked to create a 'static reference that the closure can use.
struct ThunkData {
#[allow(dead_code)]
closure: Closure<'static>,
/// Raw pointer to the leaked userdata, for cleanup
userdata_ptr: *mut ThunkUserData,
}
impl Drop for ThunkData {
fn drop(&mut self) {
// SAFETY: We created this with Box::into_raw, so we can reclaim it
unsafe {
drop(Box::from_raw(self.userdata_ptr));
}
}
}
/// CThunkObject wraps a Python callable to make it callable from C code.
#[pyclass(name = "CThunkObject", module = "_ctypes")]
#[derive(Debug, PyPayload)]
pub struct PyCThunk {}
#[derive(PyPayload)]
pub struct PyCThunk {
/// The Python callable
callable: PyObjectRef,
/// The libffi closure (must be kept alive)
#[allow(dead_code)]
thunk_data: PyRwLock<Option<ThunkData>>,
/// The code pointer for the closure
code_ptr: CodePtr,
}
impl Debug for PyCThunk {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PyCThunk")
.field("callable", &self.callable)
.finish()
}
}
impl PyCThunk {
/// Create a new thunk wrapping a Python callable.
///
/// # Arguments
/// * `callable` - The Python callable to wrap
/// * `arg_types` - Optional sequence of argument types
/// * `res_type` - Optional result type
/// * `vm` - The virtual machine
pub fn new(
callable: PyObjectRef,
arg_types: Option<PyObjectRef>,
res_type: Option<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<Self> {
// Parse argument types
let arg_type_vec: Vec<PyTypeRef> = if let Some(args) = arg_types {
if vm.is_none(&args) {
Vec::new()
} else {
let mut types = Vec::new();
for item in args.try_to_value::<Vec<PyObjectRef>>(vm)? {
types.push(item.downcast::<PyType>().map_err(|_| {
vm.new_type_error("_argtypes_ must be a sequence of types".to_string())
})?);
}
types
}
} else {
Vec::new()
};
// Parse result type
let res_type_ref: Option<PyTypeRef> =
if let Some(ref rt) = res_type {
if vm.is_none(rt) {
None
} else {
Some(rt.clone().downcast::<PyType>().map_err(|_| {
vm.new_type_error("restype must be a ctypes type".to_string())
})?)
}
} else {
None
};
// Build FFI types
let ffi_arg_types: Vec<Type> = arg_type_vec
.iter()
.map(|ty| {
get_type_code(ty, vm)
.and_then(|code| ffi_type_from_str(&code))
.unwrap_or(Type::pointer())
})
.collect();
let ffi_res_type = res_type_ref
.as_ref()
.and_then(|ty| get_type_code(ty, vm))
.and_then(|code| ffi_type_from_str(&code))
.unwrap_or(Type::void());
// Create the CIF
let cif = Cif::new(ffi_arg_types, ffi_res_type);
// Create userdata and leak it to get a 'static reference
let userdata = Box::new(ThunkUserData {
callable: callable.clone(),
arg_types: arg_type_vec,
res_type: res_type_ref,
});
let userdata_ptr = Box::into_raw(userdata);
// SAFETY: We maintain the userdata lifetime by storing it in ThunkData
// and cleaning it up in Drop
let userdata_ref: &'static ThunkUserData = unsafe { &*userdata_ptr };
// Create the closure
let closure = Closure::new(cif, thunk_callback, userdata_ref);
// Get the code pointer
let code_ptr = CodePtr(*closure.code_ptr() as *mut _);
// Store closure and userdata together
let thunk_data = ThunkData {
closure,
userdata_ptr,
};
Ok(Self {
callable,
thunk_data: PyRwLock::new(Some(thunk_data)),
code_ptr,
})
}
/// Get the code pointer for this thunk
pub fn code_ptr(&self) -> CodePtr {
self.code_ptr
}
}
// SAFETY: PyCThunk is safe to send/sync because:
// - callable is a PyObjectRef which is Send+Sync
// - thunk_data contains the libffi closure which is heap-allocated
// - code_ptr is just a pointer to executable memory
unsafe impl Send for PyCThunk {}
unsafe impl Sync for PyCThunk {}
#[pyclass]
impl PyCThunk {}
impl PyCThunk {
#[pygetset]
fn callable(&self) -> PyObjectRef {
self.callable.clone()
}
}
+217 -9
View File
@@ -1,17 +1,30 @@
use super::base::PyCData;
use super::base::{CDataObject, PyCData};
use super::field::PyCField;
use super::util::StgInfo;
use crate::builtins::{PyList, PyStr, PyTuple, PyType, PyTypeRef};
use crate::convert::ToPyObject;
use crate::function::FuncArgs;
use crate::protocol::{BufferDescriptor, BufferMethods, PyBuffer as ProtocolPyBuffer};
use crate::stdlib::ctypes::_ctypes::get_size;
use crate::types::Constructor;
use crate::{PyObjectRef, PyPayload, PyResult, VirtualMachine};
use crate::types::{AsBuffer, Constructor};
use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine};
use num_traits::ToPrimitive;
use rustpython_common::lock::PyRwLock;
/// PyCUnionType - metaclass for Union
#[pyclass(name = "UnionType", base = PyType, module = "_ctypes")]
#[derive(Debug, PyPayload)]
pub struct PyCUnionType {}
pub struct PyCUnionType {
pub stg_info: StgInfo,
}
impl Default for PyCUnionType {
fn default() -> Self {
PyCUnionType {
stg_info: StgInfo::new(0, 1),
}
}
}
impl Constructor for PyCUnionType {
type Args = FuncArgs;
@@ -74,9 +87,9 @@ impl PyCUnionType {
// For Union, all fields start at offset 0
// Create CField descriptor (accepts any ctypes type including arrays)
let cfield = PyCField::new(name.clone(), field_type, 0, size, index);
let c_field = PyCField::new(name.clone(), field_type, 0, size, index);
cls.set_attr(vm.ctx.intern_str(name), cfield.to_pyobject(vm));
cls.set_attr(vm.ctx.intern_str(name), c_field.to_pyobject(vm));
}
Ok(())
@@ -114,7 +127,202 @@ impl PyCUnionType {}
/// PyCUnion - base class for Union
#[pyclass(module = "_ctypes", name = "Union", base = PyCData, metaclass = "PyCUnionType")]
pub struct PyCUnion {}
#[derive(PyPayload)]
pub struct PyCUnion {
/// Common CDataObject for memory buffer
pub(super) cdata: PyRwLock<CDataObject>,
}
#[pyclass(flags(BASETYPE, IMMUTABLETYPE))]
impl PyCUnion {}
impl std::fmt::Debug for PyCUnion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PyCUnion")
.field("size", &self.cdata.read().size())
.finish()
}
}
impl Constructor for PyCUnion {
type Args = FuncArgs;
fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult {
// Get _fields_ from the class
let fields_attr = cls.as_object().get_attr("_fields_", vm).ok();
// Calculate union size (max of all field sizes) and alignment
let mut max_size = 0usize;
let mut max_align = 1usize;
if let Some(fields_attr) = fields_attr {
let fields: Vec<PyObjectRef> = if let Some(list) = fields_attr.downcast_ref::<PyList>()
{
list.borrow_vec().to_vec()
} else if let Some(tuple) = fields_attr.downcast_ref::<PyTuple>() {
tuple.to_vec()
} else {
vec![]
};
for field in fields.iter() {
let Some(field_tuple) = field.downcast_ref::<PyTuple>() else {
continue;
};
if field_tuple.len() < 2 {
continue;
}
let field_type = field_tuple.get(1).unwrap().clone();
let size = PyCUnionType::get_field_size(&field_type, vm)?;
max_size = max_size.max(size);
// For simple types, alignment == size
max_align = max_align.max(size);
}
}
// Initialize buffer with zeros
let stg_info = StgInfo::new(max_size, max_align);
PyCUnion {
cdata: PyRwLock::new(CDataObject::from_stg_info(&stg_info)),
}
.into_ref_with_type(vm, cls)
.map(Into::into)
}
}
#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, AsBuffer))]
impl PyCUnion {
#[pygetset]
fn _objects(&self) -> Option<PyObjectRef> {
self.cdata.read().objects.clone()
}
#[pyclassmethod]
fn from_address(cls: PyTypeRef, address: isize, vm: &VirtualMachine) -> PyResult {
use crate::stdlib::ctypes::_ctypes::size_of;
// Get size from cls
let size = size_of(cls.clone().into(), vm)?;
// Create instance with data from address
if address == 0 || size == 0 {
return Err(vm.new_value_error("NULL pointer access".to_owned()));
}
let stg_info = StgInfo::new(size, 1);
Ok(PyCUnion {
cdata: PyRwLock::new(CDataObject::from_stg_info(&stg_info)),
}
.into_ref_with_type(vm, cls)?
.into())
}
#[pyclassmethod]
fn from_buffer(
cls: PyTypeRef,
source: PyObjectRef,
offset: crate::function::OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult {
use crate::TryFromObject;
use crate::protocol::PyBuffer;
use crate::stdlib::ctypes::_ctypes::size_of;
let offset = offset.unwrap_or(0);
if offset < 0 {
return Err(vm.new_value_error("offset cannot be negative".to_owned()));
}
let offset = offset as usize;
let buffer = PyBuffer::try_from_object(vm, source.clone())?;
if buffer.desc.readonly {
return Err(vm.new_type_error("underlying buffer is not writable".to_owned()));
}
let size = size_of(cls.clone().into(), vm)?;
let buffer_len = buffer.desc.len;
if offset + size > buffer_len {
return Err(vm.new_value_error(format!(
"Buffer size too small ({} instead of at least {} bytes)",
buffer_len,
offset + size
)));
}
// Copy data from source buffer
let bytes = buffer.obj_bytes();
let data = bytes[offset..offset + size].to_vec();
Ok(PyCUnion {
cdata: PyRwLock::new(CDataObject::from_bytes(data, None)),
}
.into_ref_with_type(vm, cls)?
.into())
}
#[pyclassmethod]
fn from_buffer_copy(
cls: PyTypeRef,
source: crate::function::ArgBytesLike,
offset: crate::function::OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult {
use crate::stdlib::ctypes::_ctypes::size_of;
let offset = offset.unwrap_or(0);
if offset < 0 {
return Err(vm.new_value_error("offset cannot be negative".to_owned()));
}
let offset = offset as usize;
let size = size_of(cls.clone().into(), vm)?;
let source_bytes = source.borrow_buf();
let buffer_len = source_bytes.len();
if offset + size > buffer_len {
return Err(vm.new_value_error(format!(
"Buffer size too small ({} instead of at least {} bytes)",
buffer_len,
offset + size
)));
}
// Copy data from source
let data = source_bytes[offset..offset + size].to_vec();
Ok(PyCUnion {
cdata: PyRwLock::new(CDataObject::from_bytes(data, None)),
}
.into_ref_with_type(vm, cls)?
.into())
}
}
static UNION_BUFFER_METHODS: BufferMethods = BufferMethods {
obj_bytes: |buffer| {
rustpython_common::lock::PyRwLockReadGuard::map(
buffer.obj_as::<PyCUnion>().cdata.read(),
|x: &CDataObject| x.buffer.as_slice(),
)
.into()
},
obj_bytes_mut: |buffer| {
rustpython_common::lock::PyRwLockWriteGuard::map(
buffer.obj_as::<PyCUnion>().cdata.write(),
|x: &mut CDataObject| x.buffer.as_mut_slice(),
)
.into()
},
release: |_| {},
retain: |_| {},
};
impl AsBuffer for PyCUnion {
fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<ProtocolPyBuffer> {
let buffer_len = zelf.cdata.read().buffer.len();
let buf = ProtocolPyBuffer::new(
zelf.to_owned().into(),
BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes
&UNION_BUFFER_METHODS,
);
Ok(buf)
}
}
+41
View File
@@ -0,0 +1,41 @@
use crate::PyObjectRef;
/// Storage information for ctypes types
#[derive(Debug, Clone)]
pub struct StgInfo {
#[allow(dead_code)]
pub initialized: bool,
pub size: usize, // number of bytes
pub align: usize, // alignment requirements
pub length: usize, // number of fields (for arrays/structures)
#[allow(dead_code)]
pub proto: Option<PyObjectRef>, // Only for Pointer/ArrayObject
#[allow(dead_code)]
pub flags: i32, // calling convention and such
}
impl Default for StgInfo {
fn default() -> Self {
StgInfo {
initialized: false,
size: 0,
align: 1,
length: 0,
proto: None,
flags: 0,
}
}
}
impl StgInfo {
pub fn new(size: usize, align: usize) -> Self {
StgInfo {
initialized: true,
size,
align,
length: 0,
proto: None,
flags: 0,
}
}
}