Skip to content

Commit 40e3f49

Browse files
authored
_ctypes pt. 3 (RustPython#5530)
* Initial CFuncPtr implementation * function calling via libffi * working no-arg function call Signed-off-by: Ashwin Naren <[email protected]>
1 parent 5619689 commit 40e3f49

File tree

8 files changed

+386
-61
lines changed

8 files changed

+386
-61
lines changed

Cargo.lock

Lines changed: 18 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: 134 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,117 @@ 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+
elif _os.name == "posix":
159+
from _ctypes import dlopen as _dlopen
160+
161+
class CDLL(object):
162+
"""An instance of this class represents a loaded dll/shared
163+
library, exporting functions using the standard C calling
164+
convention (named 'cdecl' on Windows).
165+
166+
The exported functions can be accessed as attributes, or by
167+
indexing with the function name. Examples:
168+
169+
<obj>.qsort -> callable object
170+
<obj>['qsort'] -> callable object
171+
172+
Calling the functions releases the Python GIL during the call and
173+
reacquires it afterwards.
174+
"""
175+
_func_flags_ = _FUNCFLAG_CDECL
176+
_func_restype_ = c_int
177+
# default values for repr
178+
_name = '<uninitialized>'
179+
_handle = 0
180+
_FuncPtr = None
181+
182+
def __init__(self, name, mode=DEFAULT_MODE, handle=None,
183+
use_errno=False,
184+
use_last_error=False,
185+
winmode=None):
186+
self._name = name
187+
flags = self._func_flags_
188+
if use_errno:
189+
flags |= _FUNCFLAG_USE_ERRNO
190+
if use_last_error:
191+
flags |= _FUNCFLAG_USE_LASTERROR
192+
if _sys.platform.startswith("aix"):
193+
"""When the name contains ".a(" and ends with ")",
194+
e.g., "libFOO.a(libFOO.so)" - this is taken to be an
195+
archive(member) syntax for dlopen(), and the mode is adjusted.
196+
Otherwise, name is presented to dlopen() as a file argument.
197+
"""
198+
if name and name.endswith(")") and ".a(" in name:
199+
mode |= ( _os.RTLD_MEMBER | _os.RTLD_NOW )
200+
if _os.name == "nt":
201+
if winmode is not None:
202+
mode = winmode
203+
else:
204+
import nt
205+
mode = 4096
206+
if '/' in name or '\\' in name:
207+
self._name = nt._getfullpathname(self._name)
208+
mode |= nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
209+
210+
class _FuncPtr(_CFuncPtr):
211+
_flags_ = flags
212+
_restype_ = self._func_restype_
213+
self._FuncPtr = _FuncPtr
214+
215+
if handle is None:
216+
self._handle = _dlopen(self._name, mode)
217+
else:
218+
self._handle = handle
219+
220+
def __repr__(self):
221+
return "<%s '%s', handle %x at %#x>" % \
222+
(self.__class__.__name__, self._name,
223+
(self._handle & (_sys.maxsize*2 + 1)),
224+
id(self) & (_sys.maxsize*2 + 1))
225+
226+
def __getattr__(self, name):
227+
if name.startswith('__') and name.endswith('__'):
228+
raise AttributeError(name)
229+
func = self.__getitem__(name)
230+
setattr(self, name, func)
231+
return func
232+
233+
def __getitem__(self, name_or_ordinal):
234+
func = self._FuncPtr((name_or_ordinal, self))
235+
if not isinstance(name_or_ordinal, int):
236+
func.__name__ = name_or_ordinal
237+
return func
238+
239+
class LibraryLoader(object):
240+
def __init__(self, dlltype):
241+
self._dlltype = dlltype
242+
243+
def __getattr__(self, name):
244+
if name[0] == '_':
245+
raise AttributeError(name)
246+
try:
247+
dll = self._dlltype(name)
248+
except OSError:
249+
raise AttributeError(name)
250+
setattr(self, name, dll)
251+
return dll
252+
253+
def __getitem__(self, name):
254+
return getattr(self, name)
255+
256+
def LoadLibrary(self, name):
257+
return self._dlltype(name)
258+
259+
__class_getitem__ = classmethod(_types.GenericAlias)
260+
261+
cdll = LibraryLoader(CDLL)
262+
263+
if _os.name == "posix" or _sys.platform == "darwin":
264+
pass
265+
else:
266+
libc = cdll.msvcrt
267+
print("rand", libc.rand())

vm/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,12 @@ uname = "0.1.1"
100100
rustyline = { workspace = true }
101101
which = "6"
102102
errno = "0.3"
103-
libloading = "0.8"
104103
widestring = { workspace = true }
105104

105+
[target.'cfg(all(any(target_os = "linux", target_os = "macos", target_os = "windows"), not(any(target_env = "musl", target_env = "sgx"))))'.dependencies]
106+
libffi = "3.2"
107+
libloading = "0.8"
108+
106109
[target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies]
107110
num_cpus = "1.13.1"
108111

vm/src/stdlib/ctypes.rs

Lines changed: 23 additions & 4 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,12 +180,31 @@ pub(crate) mod _ctypes {
180180
}
181181

182182
#[pyfunction(name = "LoadLibrary")]
183-
fn load_library(name: String, vm: &VirtualMachine) -> PyResult<usize> {
183+
fn load_library_windows(
184+
name: String,
185+
_load_flags: OptionalArg<i32>,
186+
vm: &VirtualMachine,
187+
) -> PyResult<usize> {
188+
// TODO: audit functions first
189+
// TODO: load_flags
190+
let cache = library::libcache();
191+
let mut cache_write = cache.write();
192+
let (id, _) = cache_write.get_or_insert_lib(&name, vm).unwrap();
193+
Ok(id)
194+
}
195+
196+
#[pyfunction(name = "dlopen")]
197+
fn load_library_unix(
198+
name: String,
199+
_load_flags: OptionalArg<i32>,
200+
vm: &VirtualMachine,
201+
) -> PyResult<usize> {
184202
// TODO: audit functions first
203+
// TODO: load_flags
185204
let cache = library::libcache();
186205
let mut cache_write = cache.write();
187-
let lib_ref = cache_write.get_or_insert_lib(&name, vm).unwrap();
188-
Ok(lib_ref.get_pointer())
206+
let (id, _) = cache_write.get_or_insert_lib(&name, vm).unwrap();
207+
Ok(id)
189208
}
190209

191210
#[pyfunction(name = "FreeLibrary")]

vm/src/stdlib/ctypes/base.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,31 @@ use num_traits::ToPrimitive;
1010
use rustpython_common::lock::PyRwLock;
1111
use std::fmt::Debug;
1212

13+
pub fn ffi_type_from_str(_type_: &str) -> Option<libffi::middle::Type> {
14+
match _type_ {
15+
"c" => Some(libffi::middle::Type::u8()),
16+
"u" => Some(libffi::middle::Type::u32()),
17+
"b" => Some(libffi::middle::Type::i8()),
18+
"B" => Some(libffi::middle::Type::u8()),
19+
"h" => Some(libffi::middle::Type::i16()),
20+
"H" => Some(libffi::middle::Type::u16()),
21+
"i" => Some(libffi::middle::Type::i32()),
22+
"I" => Some(libffi::middle::Type::u32()),
23+
"l" => Some(libffi::middle::Type::i32()),
24+
"L" => Some(libffi::middle::Type::u32()),
25+
"q" => Some(libffi::middle::Type::i64()),
26+
"Q" => Some(libffi::middle::Type::u64()),
27+
"f" => Some(libffi::middle::Type::f32()),
28+
"d" => Some(libffi::middle::Type::f64()),
29+
"g" => Some(libffi::middle::Type::f64()),
30+
"?" => Some(libffi::middle::Type::u8()),
31+
"z" => Some(libffi::middle::Type::u64()),
32+
"Z" => Some(libffi::middle::Type::u64()),
33+
"P" => Some(libffi::middle::Type::u64()),
34+
_ => None,
35+
}
36+
}
37+
1338
#[allow(dead_code)]
1439
fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyResult {
1540
match _type_ {

0 commit comments

Comments
 (0)