mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
ctypes buffer (#6311)
* Fix array * from buffer * AsBuffer * Fix instruction * constructable pointer * thunk * in_dll * fix sizeof/alignment
This commit is contained in:
1 parent
e5aec9d7fd
commit
6782fa2219
13 files changed
+2721
-195
No files matched your search
@@ -50,6 +50,7 @@ stackdepth
|
||||
stringlib
|
||||
structseq
|
||||
subparams
|
||||
swappedbytes
|
||||
ticketer
|
||||
tok_oldval
|
||||
tvars
|
||||
|
||||
@@ -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**
|
||||
|
||||
Vendored
+1
-1
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user