8000 Initial CFuncPtr implementation · RustPython/RustPython@ab1f597 · GitHub
[go: up one dir, main page]

Skip to content

Commit ab1f597

Browse files
committed
Initial CFuncPtr implementation
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
1 parent 65dcf1c commit ab1f597

File tree

5 files changed

+251
-29
lines changed

5 files changed

+251
-29
lines changed

Cargo.lock

Lines changed: 18 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extra_tests/snippets/builtins_ctypes.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,29 @@
11
import os as _os, sys as _sys
2+
import types as _types
23

4+
from _ctypes import RTLD_LOCAL, RTLD_GLOBAL
35
from _ctypes import sizeof
46
from _ctypes import _SimpleCData
7+
from _ctypes import CFuncPtr as _CFuncPtr
8+
59
from struct import calcsize as _calcsize
610

11+
12+
DEFAULT_MODE = RTLD_LOCAL
13+
if _os.name == "posix" and _sys.platform == "darwin":
14+
# On OS X 10.3, we use RTLD_GLOBAL as default mode
15+
# because RTLD_LOCAL does not work at least on some
16+
# libraries. OS X 10.3 is Darwin 7, so we check for
17+
# that.
18+
19+
if int(_os.uname().release.split('.')[0]) < 8:
20+
DEFAULT_MODE = RTLD_GLOBAL
21+
22+
from _ctypes import FUNCFLAG_CDECL as _FUNCFLAG_CDECL, \
23+
FUNCFLAG_PYTHONAPI as _FUNCFLAG_PYTHONAPI, \
24+
FUNCFLAG_USE_ERRNO as _FUNCFLAG_USE_ERRNO, \
25+
FUNCFLAG_USE_LASTERROR as _FUNCFLAG_USE_LASTERROR
26+
727
def create_string_buffer(init, size=None):
828
"""create_string_buffer(aBytes) -> character array
929
create_string_buffer(anInteger) -> character array
@@ -131,3 +151,115 @@ class c_bool(_SimpleCData):
131151
# s = create_string_buffer(b'\000' * 32)
132152
assert i.value == 42
133153
assert abs(f.value - 3.14) < 1e-06
154+
155+
if _os.name == "nt":
156+
from _ctypes import LoadLibrary as _dlopen
157+
from _ctypes import FUNCFLAG_STDCALL as _FUNCFLAG_STDCALL
158+
159+
class CDLL(object):
160+
"""An instance of this class represents a loaded dll/shared
161+
library, exporting functions using the standard C calling
162+
convention (named 'cdecl' on Windows).
163+
164+
The exported functions can be accessed as attributes, or by
165+
indexing with the function name. Examples:
166+
167+
<obj>.qsort -> callable object
168+
<obj>['qsort'] -> callable object
169+
170+
Calling the functions releases the Python GIL during the call and
171+
reacquires it afterwards.
172+
"""
173+
_func_flags_ = _FUNCFLAG_CDECL
174+
_func_restype_ = c_int
175+
# default values for repr
176+
_name = '<uninitialized>'
177+
_handle = 0
178+
_FuncPtr = None
179+
180+
def __init__(self, name, mode=DEFAULT_MODE, handle=None,
181+
use_errno=False,
182+
use_last_error=False,
183+
winmode=None):
184+
self._name = name
185+
flags = self._func_flags_
186+
if use_errno:
187+
flags |= _FUNCFLAG_USE_ERRNO
188+
if use_last_error:
189+
flags |= _FUNCFLAG_USE_LASTERROR
190+
if _sys.platform.startswith("aix"):
191+
"""When the name contains ".a(" and ends with ")",
192+
e.g., "libFOO.a(libFOO.so)" - this is taken to be an
193+
archive(member) syntax for dlopen(), and the mode is adjusted.
194+
Otherwise, name is presented to dlopen() as a file argument.
195+
"""
196+
if name and name.endswith(")") and ".a(" in name:
197+
mode |= ( _os.RTLD_MEMBER | _os.RTLD_NOW )
198+
if _os.name == "nt":
199+
if winmode is not None:
200+
mode = winmode
201+
else:
202+
import nt
203+
mode = 4096
204+
if '/' in name or '\\' in name:
205+
self._name = nt._getfullpathname(self._name)
206+
mode |= nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
207+
208+
class _FuncPtr(_CFuncPtr):
209+
_flags_ = flags
210+
_restype_ = self._func_restype_
211+
self._FuncPtr = _FuncPtr
212+
213+
if handle is None:
214+
self._handle = _dlopen(self._name, mode)
215+
else:
216+
self._handle = handle
217+
218+
def __repr__(self):
219+
return "<%s '%s', handle %x at %#x>" % \
220+
(self.__class__.__name__, self._name,
221+
(self._handle & (_sys.maxsize*2 + 1)),
222+
id(self) & (_sys.maxsize*2 + 1))
223+
224+
def __getattr__(self, name):
225+
if name.startswith('__') and name.endswith('__'):
226+
raise AttributeError(name)
227+
func = self.__getitem__(name)
228+
setattr(self, name, func)
229+
return func
230+
231+
def __getitem__(self, name_or_ordinal):
232+
func = self._FuncPtr((name_or_ordinal, self))
233+
if not isinstance(name_or_ordinal, int):
234+
func.__name__ = name_or_ordinal
235+
return func
236+
237+
class LibraryLoader(object):
238+
def __init__(self, dlltype):
239+
self._dlltype = dlltype
240+
241+
def __getattr__(self, name):
242+
if name[0] == '_':
243+
raise AttributeError(name)
244+
try:
245+
dll = self._dlltype(name)
246+
except OSError:
247+
raise AttributeError(name)
248+
setattr(self, name, dll)
249+
return dll
250+
251+
def __getitem__(self, name):
252+
return getattr(self, name)
253+
254+
def LoadLibrary(self, name):
255+
return self._dlltype(name)
256+
257+
__class_getitem__ = classmethod(_types.GenericAlias)
258+
259+
cdll = LibraryLoader(CDLL)
260+
261+
if _os.name == "posix" and _sys.platform == "darwin":
262+
pass
263+
else:
264+
libc = cdll.msvcrt
265+
print("rand", libc.rand())

vm/src/stdlib/ctypes.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pub(crate) mod _ctypes {
3737
use super::base::PyCSimple;
3838
use crate::builtins::PyTypeRef;
3939
use crate::class::StaticType;
40-
use crate::function::Either;
40+
use crate::function::{Either, OptionalArg};
4141
use crate::stdlib::ctypes::library;
4242
use crate::{AsObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine};
4343
use crossbeam_utils::atomic::AtomicCell;
@@ -171,8 +171,9 @@ pub(crate) mod _ctypes {
171171
}
172172

173173
#[pyfunction(name = "LoadLibrary")]
174-
fn load_library(name: String, vm: &VirtualMachine) -> PyResult<usize> {
174+
fn load_library(name: String, load_flags: OptionalArg<i32>, vm: &VirtualMachine) -> PyResult<usize> {
175175
// TODO: audit functions first
176+
// TODO: load_flags
176177
let cache = library::libcache();
177178
let mut cache_write = cache.write();
178179
let lib_ref = cache_write.get_or_insert_lib(&name, vm).unwrap();

vm/src/stdlib/ctypes/function.rs

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,68 @@
11
use crate::stdlib::ctypes::PyCData;
2-
use crate::PyObjectRef;
2+
use crate::{Py, PyObjectRef, PyResult, VirtualMachine};
33
use crossbeam_utils::atomic::AtomicCell;
4-
use rustpython_common::lock::PyRwLock;
4+
use rustpython_common::lock::{PyMutex, PyRwLock};
55
use std::ffi::c_void;
6+
use std::fmt::Debug;
7+
use std::sync::Arc;
8+
use crate::builtins::PyTypeRef;
9+
use crate::types::{Callable, Constructor};
10+
11+
// https://github.com/python/cpython/blob/4f8bb3947cfbc20f970ff9d9531e1132a9e95396/Modules/_ctypes/callproc.c#L15
12+
13+
#[derive(Debug)]
14+
pub enum FunctionArgument {
15+
Float(std::ffi::c_float),
16+
Double(std::ffi::c_double),
17+
// TODO: Duplicate char stuff
18+
UChar(std::ffi::c_uchar),
19+
SChar(std::ffi::c_schar),
20+
Char(std::ffi::c_char),
21+
UShort(std::ffi::c_ushort),
22+
Short(std::ffi::c_short),
23+
UInt(std::ffi::c_uint),
24+
Int(std::ffi::c_int),
25+
ULong(std::ffi::c_ulong),
26+
Long(std::ffi::c_long),
27+
ULongLong(std::ffi::c_ulonglong),
28+
LongLong(std::ffi::c_longlong),
29+
}
630

731
#[derive(Debug)]
832
pub struct Function {
9-
_pointer: *mut c_void,
10-
_arguments: Vec<()>,
11-
_return_type: Box<()>,
33+
// TODO: no protection from use-after-free
34+
pointer: Arc<PyMutex<*mut c_void>>,
35+
arguments: Vec<FunctionArgument>,
36+
return_type: PyTypeRef,
37+
}
38+
39+
unsafe impl Send for Function {}
40+
unsafe impl Sync for Function {}
41+
42+
impl Function {
43+
pub unsafe fn load(library: &libloading::Library, function: &str, args: Vec<PyObjectRef>, return_type: PyTypeRef) -> PyResult<Self> {
44+
let terminated = format!("{}\0", function);
45+
let pointer = library.get(terminated.as_bytes()).map_err(|err| err.to_string()).unwrap();
46+
Ok(Function {
47+
pointer: Arc::new(PyMutex::new(*pointer)),
48+
arguments: args.iter().map(|arg| {
49+
todo!("convert PyObjectRef to FunctionArgument")
50+
}).collect(),
51+
return_type,
52+
})
53+
}
54+
55+
pub unsafe fn call(&self, vm: &VirtualMachine) -> PyObjectRef {
56+
// assemble function type signature
57+
let pointer = self.pointer.lock();
58+
let f: extern "C" fn() = std::mem::transmute(*pointer);
59+
f();
60+
vm.ctx.none()
61+
}
1262
}
1363

1464
#[pyclass(module = "_ctypes", name = "CFuncPtr", base = "PyCData")]
65+
#[derive(PyPayload)]
1566
pub struct PyCFuncPtr {
1667
pub _name_: String,
1768
pub _argtypes_: AtomicCell<Vec<PyObjectRef>>,
@@ -20,5 +71,30 @@ pub struct PyCFuncPtr {
2071
_f: PyRwLock<Function>,
2172
}
2273

23-
#[pyclass]
24-
impl PyCFuncPtr {}
74+
impl Debug for PyCFuncPtr {
75+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76+
f.debug_struct("PyCFuncPtr")
77+
.field("_name_", &self._name_)
78+< B5C2 /span>
.finish()
79+
}
80+
}
81+
82+
impl Constructor for PyCFuncPtr {
83+
type Args = ();
84+
85+
fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult {
86+
todo!("PyCFuncPtr::py_new")
87+
}
88+
}
89+
90+
impl Callable for PyCFuncPtr {
91+
type Args = Vec<PyObjectRef>;
92+
93+
fn call(zelf: &Py<Self>, _args: Self::Args, vm: &VirtualMachine) -> PyResult {
94+
todo!()
95+
}
96+
}
97+
98+
#[pyclass(flags(BASETYPE), with(Callable, Constructor))]
99+
impl PyCFuncPtr {
100+
}

0 commit comments

Comments
 (0)
0