8000 _ctypes pt. 3 by arihant2math · Pull Request #5530 · RustPython/RustPython · GitHub
[go: up one dir, main page]

Skip to content

_ctypes pt. 3 #5530

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Initial CFuncPtr implementation
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
  • Loading branch information
arihant2math committed Mar 2, 2025
commit b42520ed4aad2623915a48a61c74a28fdc0a94fe
34 changes: 17 additions & 17 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

132 changes: 132 additions & 0 deletions extra_tests/snippets/builtins_ctypes.py
8000
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
import os as _os, sys as _sys
import types as _types

from _ctypes import RTLD_LOCAL, RTLD_GLOBAL
from _ctypes import sizeof
from _ctypes import _SimpleCData
from _ctypes import CFuncPtr as _CFuncPtr

from struct import calcsize as _calcsize


DEFAULT_MODE = RTLD_LOCAL
if _os.name == "posix" and _sys.platform == "darwin":
# On OS X 10.3, we use RTLD_GLOBAL as default mode
# because RTLD_LOCAL does not work at least on some
# libraries. OS X 10.3 is Darwin 7, so we check for
# that.

if int(_os.uname().release.split('.')[0]) < 8:
DEFAULT_MODE = RTLD_GLOBAL

from _ctypes import FUNCFLAG_CDECL as _FUNCFLAG_CDECL, \
FUNCFLAG_PYTHONAPI as _FUNCFLAG_PYTHONAPI, \
FUNCFLAG_USE_ERRNO as _FUNCFLAG_USE_ERRNO, \
FUNCFLAG_USE_LASTERROR as _FUNCFLAG_USE_LASTERROR

def create_string_buffer(init, size=None):
"""create_string_buffer(aBytes) -> character array
create_string_buffer(anInteger) -> character array
Expand Down Expand Up @@ -131,3 +151,115 @@ class c_bool(_SimpleCData):
# s = create_string_buffer(b'\000' * 32)
assert i.value == 42
assert abs(f.value - 3.14) < 1e-06

if _os.name == "nt":
from _ctypes import LoadLibrary as _dlopen
from _ctypes import FUNCFLAG_STDCALL as _FUNCFLAG_STDCALL

class CDLL(object):
"""An instance of this class represents a loaded dll/shared
library, exporting functions using the standard C calling
convention (named 'cdecl' on Windows).

The exported functions can be accessed as attributes, or by
indexing with the function name. Examples:

<obj>.qsort -> callable object
<obj>['qsort'] -> callable object

Calling the functions releases the Python GIL during the call and
reacquires it afterwards.
"""
_func_flags_ = _FUNCFLAG_CDECL
_func_restype_ = c_int
# default values for repr
_name = '<uninitialized>'
_handle = 0
_FuncPtr = None

def __init__(self, name, mode=DEFAULT_MODE, handle=None,
use_errno=False,
use_last_error=False,
winmode=None):
self._name = name
flags = self._func_flags_
if use_errno:
flags |= _FUNCFLAG_USE_ERRNO
if use_last_error:
flags |= _FUNCFLAG_USE_LASTERROR
if _sys.platform.startswith("aix"):
"""When the name contains ".a(" and ends with ")",
e.g., "libFOO.a(libFOO.so)" - this is taken to be an
archive(member) syntax for dlopen(), and the mode is adjusted.
Otherwise, name is presented to dlopen() as a file argument.
"""
if name and name.endswith(")") and ".a(" in name:
mode |= ( _os.RTLD_MEMBER | _os.RTLD_NOW )
if _os.name == "nt":
if winmode is not None:
mode = winmode
else:
import nt
mode = 4096
if '/' in name or '\\' in name:
self._name = nt._getfullpathname(self._name)
mode |= nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR

class _FuncPtr(_CFuncPtr):
_flags_ = flags
_restype_ = self._func_restype_
self._FuncPtr = _FuncPtr

if handle is None:
self._handle = _dlopen(self._name, mode)
else:
self._handle = handle

def __repr__(self):
return "<%s '%s', handle %x at %#x>" % \
(self.__class__.__name__, self._name,
(self._handle & (_sys.maxsize*2 + 1)),
id(self) & (_sys.maxsize*2 + 1))

def __getattr__(self, name):
if name.startswith('__') and name.endswith('__'):
raise AttributeError(name)
func = self.__getitem__(name)
setattr(self, name, func)
return func

def __getitem__(self, name_or_ordinal):
func = self._FuncPtr((name_or_ordinal, self))
if not isinstance(name_or_ordinal, int):
func.__name__ = name_or_ordinal
return func

class LibraryLoader(object):
def __init__(self, dlltype):
self._dlltype = dlltype

def __getattr__(self, name):
if name[0] == '_':
raise AttributeError(name)
try:
dll = self._dlltype(name)
except OSError:
raise AttributeError(name)
setattr(self, name, dll)
return dll

def __getitem__(self, name):
return getattr(self, name)

def LoadLibrary(self, name):
return self._dlltype(name)

__class_getitem__ = classmethod(_types.GenericAlias)

cdll = LibraryLoader(CDLL)

if _os.name == "posix" and _sys.platform == "darwin":
pass
else:
libc = cdll.msvcrt
print("rand", libc.rand())
9 changes: 7 additions & 2 deletions vm/src/stdlib/ctypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub(crate) mod _ctypes {
use super::base::PyCSimple;
use crate::builtins::PyTypeRef;
use crate::class::StaticType;
use crate::function::Either;
use crate::function::{Either, OptionalArg};
use crate::stdlib::ctypes::library;
use crate::{AsObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine};
use crossbeam_utils::atomic::AtomicCell;
Expand Down Expand Up @@ -180,8 +180,13 @@ pub(crate) mod _ctypes {
}

#[pyfunction(name = "LoadLibrary")]
fn load_library(name: String, vm: &VirtualMachine) -> PyResult<usize> {
fn load_library(
name: String,
load_flags: OptionalArg<i32>,
vm: &VirtualMachine,
) -> PyResult<usize> {
// TODO: audit functions first
// TODO: load_flags
let cache = library::libcache();
let mut cache_write = cache.write();
let lib_ref = cache_write.get_or_insert_lib(&name, vm).unwrap();
Expand Down
96 changes: 90 additions & 6 deletions vm/src/stdlib/ctypes/function.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,77 @@
use crate::PyObjectRef;
use crate::builtins::PyTypeRef;
use crate::stdlib::ctypes::PyCData;
use crate::types::{Callable, Constructor};
use crate::{Py, PyObjectRef, PyResult, VirtualMachine};
use crossbeam_utils::atomic::AtomicCell;
use rustpython_common::lock::PyRwLock;
use rustpython_common::lock::{PyMutex, PyRwLock};
use std::ffi::c_void;
use std::fmt::Debug;
use std::sync::Arc;

// https://github.com/python/cpython/blob/4f8bb3947cfbc20f970ff9d9531e1132a9e95396/Modules/_ctypes/callproc.c#L15

#[derive(Debug)]
pub enum FunctionArgument {
Float(std::ffi::c_float),
Double(std::ffi::c_double),
// TODO: Duplicate char stuff
UChar(std::ffi::c_uchar),
SChar(std::ffi::c_schar),
Char(std::ffi::c_char),
UShort(std::ffi::c_ushort),
Short(std::ffi::c_short),
UInt(std::ffi::c_uint),
Int(std::ffi::c_int),
ULong(std::ffi::c_ulong),
Long(std::ffi::c_long),
ULongLong(std::ffi::c_ulonglong),
LongLong(std::ffi::c_longlong),
}

#[derive(Debug)]
pub struct Function {
_pointer: *mut c_void,
_arguments: Vec<()>,
_return_type: Box<()>,
// TODO: no protection from use-after-free
pointer: Arc<PyMutex<*mut c_void>>,
arguments: Vec<FunctionArgument>,
return_type: PyTypeRef,
}

unsafe impl Send for Function {}
unsafe impl Sync for Function {}

impl Function {
pub unsafe fn load(
library: &libloading::Library,
function: &str,
args: Vec<PyObjectRef>,
return_type: PyTypeRef,
) -> PyResult<Self> {
let terminated = format!("{}\0", function);
let pointer = library
.get(terminated.as_bytes())
.map_err(|err| err.to_string())
.unwrap();
Ok(Function {
pointer: Arc::new(PyMutex::new(*pointer)),
arguments: args
.iter()
.map(|arg| todo!("convert PyObjectRef to FunctionArgument"))
.collect(),
return_type,
})
}

pub unsafe fn call(&self, vm: &VirtualMachine) -> PyObjectRef {
// assemble function type signature
let pointer = self.pointer.lock();
let f: extern "C" fn() = std::mem::transmute(*pointer);
f();
vm.ctx.none()
}
}

#[pyclass(module = "_ctypes", name = "CFuncPtr", base = "PyCData")]
#[derive(PyPayload)]
pub struct PyCFuncPtr {
pub _name_: String,
pub _argtypes_: AtomicCell<Vec<PyObjectRef>>,
Expand All @@ -20,5 +80,29 @@ pub struct PyCFuncPtr {
_f: PyRwLock<Function>,
}

#[pyclass]
impl Debug for PyCFuncPtr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PyCFuncPtr")
.field("_name_", &self._name_)
.finish()
}
}

impl Constructor for PyCFuncPtr {
type Args = ();

fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult {
todo!("PyCFuncPtr::py_new")
}
}

impl Callable for PyCFuncPtr {
type Args = Vec<PyObjectRef>;

fn call(zelf: &Py<Self>, _args: Self::Args, vm: &VirtualMachine) -> PyResult {
todo!()
}
}

#[pyclass(flags(BASETYPE), with(Callable, Constructor))]
impl PyCFuncPtr {}
Loading
0