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

Skip to content

Commit 856bbd5

Browse files
committed
Initial CFuncPtr implementation
Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
1 parent b2abb1a commit 856bbd5

File tree

5 files changed

+261
-27
lines changed

5 files changed

+261
-27
lines changed

Cargo.lock

Lines changed: 17 additions & 17 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: 7 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;
@@ -180,8 +180,13 @@ pub(crate) mod _ctypes {
180180
}
181181

182182
#[pyfunction(name = "LoadLibrary")]
183-
fn load_library(name: String, vm: &VirtualMachine) -> PyResult<usize> {
183+
fn load_library(
184+
name: String,
185+
load_flags: OptionalArg<i32>,
186+
vm: &VirtualMachine,
187+
) -> PyResult<usize> {
184188
// TODO: audit functions first
189+
// TODO: load_flags
185190
let cache = library::libcache();
186191
let mut cache_write = cache.write();
187192
let lib_ref = cache_write.get_or_insert_lib(&name, vm).unwrap();

vm/src/stdlib/ctypes/function.rs

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,77 @@
1-
use crate::PyObjectRef;
1+
use crate::builtins::PyTypeRef;
22
use crate::stdlib::ctypes::PyCData;
3+
use crate::types::{Callable, Constructor};
4+
use crate::{Py, PyObjectRef, PyResult, VirtualMachine};
35
use crossbeam_utils::atomic::AtomicCell;
4-
use rustpython_common::lock::PyRwLock;
6+
use rustpython_common::lock::{PyMutex, PyRwLock};
57
use std::ffi::c_void;
8+
use std::fmt::Debug;
9+
use std::sync::Arc;
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(
44+
library: &libloading::Library,
45+
function: &str,
46+
args: Vec<PyObjectRef>,
47+
return_type: PyTypeRef,
48+
) -> PyResult<Self> {
49+
let terminated = format!("{}\0", function);
50+
let pointer = library
51+
.get(terminated.as_bytes())
52+
.map_err(|err| err.to_string())
53+
.unwrap();
54+
Ok(Function {
55+
pointer: Arc::new(PyMutex::new(*pointer)),
56+
arguments: args
57+
.iter()
58+
.map(|arg| todo!("convert PyObjectRef to FunctionArgument"))
59+
.collect(),
60+
return_type,
61+
})
62+
}
63+
64+
pub unsafe fn call(&self, vm: &VirtualMachine) -> PyObjectRef {
65+
// assemble function type signature
66+
let pointer = self.pointer.lock();
67+
let f: extern "C" fn() = std::mem::transmute(*pointer);
68+
f();
69+
vm.ctx.none()
70+
}
1271
}
1372

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

23-
#[pyclass]
83+
impl Debug for PyCFuncPtr {
84+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85+
f.debug_struct("PyCFuncPtr")
86+
.field("_name_", &self._name_)
87+
.finish()
88+
}
89+
}
90+
91+
impl Constructor for PyCFuncPtr {
92+
type Args = ();
93+
94+
fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult {
95+
todo!("PyCFuncPtr::py_new")
96+
}
97+
}
98+
99+
impl Callable for PyCFuncPtr {
100+
type Args = Vec<PyObjectRef>;
101+
102+
fn call(zelf: &Py<Self>, _args: Self::Args, vm: &VirtualMachine) -> PyResult {
103+
todo!()
104+
}
105+
}
106+
107+
#[pyclass(flags(BASETYPE), with(Callable, Constructor))]
24108
impl PyCFuncPtr {}

0 commit comments

Comments
 (0)
0