1 | use crate::Python; |
2 | use crate::{ffi, PyAny}; |
3 | use crate::{PyErr, PyResult}; |
4 | use std::ffi::{CStr, CString}; |
5 | use std::os::raw::{c_char, c_int, c_void}; |
6 | |
7 | /// Represents a Python Capsule |
8 | /// as described in [Capsules](https://docs.python.org/3/c-api/capsule.html#capsules): |
9 | /// > This subtype of PyObject represents an opaque value, useful for C extension |
10 | /// > modules who need to pass an opaque value (as a void* pointer) through Python |
11 | /// > code to other C code. It is often used to make a C function pointer defined |
12 | /// > in one module available to other modules, so the regular import mechanism can |
13 | /// > be used to access C APIs defined in dynamically loaded modules. |
14 | /// |
15 | /// |
16 | /// # Example |
17 | /// ``` |
18 | /// use pyo3::{prelude::*, types::PyCapsule}; |
19 | /// use std::ffi::CString; |
20 | /// |
21 | /// #[repr(C)] |
22 | /// struct Foo { |
23 | /// pub val: u32, |
24 | /// } |
25 | /// |
26 | /// let r = Python::with_gil(|py| -> PyResult<()> { |
27 | /// let foo = Foo { val: 123 }; |
28 | /// let name = CString::new("builtins.capsule" ).unwrap(); |
29 | /// |
30 | /// let capsule = PyCapsule::new(py, foo, Some(name.clone()))?; |
31 | /// |
32 | /// let module = PyModule::import(py, "builtins" )?; |
33 | /// module.add("capsule" , capsule)?; |
34 | /// |
35 | /// let cap: &Foo = unsafe { PyCapsule::import(py, name.as_ref())? }; |
36 | /// assert_eq!(cap.val, 123); |
37 | /// Ok(()) |
38 | /// }); |
39 | /// assert!(r.is_ok()); |
40 | /// ``` |
41 | #[repr (transparent)] |
42 | pub struct PyCapsule(PyAny); |
43 | |
44 | pyobject_native_type_core!(PyCapsule, pyobject_native_static_type_object!(ffi::PyCapsule_Type), #checkfunction=ffi::PyCapsule_CheckExact); |
45 | |
46 | impl PyCapsule { |
47 | /// Constructs a new capsule whose contents are `value`, associated with `name`. |
48 | /// `name` is the identifier for the capsule; if it is stored as an attribute of a module, |
49 | /// the name should be in the format `"modulename.attribute"`. |
50 | /// |
51 | /// It is checked at compile time that the type T is not zero-sized. Rust function items |
52 | /// need to be cast to a function pointer (`fn(args) -> result`) to be put into a capsule. |
53 | /// |
54 | /// # Example |
55 | /// |
56 | /// ``` |
57 | /// use pyo3::{prelude::*, types::PyCapsule}; |
58 | /// use std::ffi::CString; |
59 | /// |
60 | /// Python::with_gil(|py| { |
61 | /// let name = CString::new("foo" ).unwrap(); |
62 | /// let capsule = PyCapsule::new(py, 123_u32, Some(name)).unwrap(); |
63 | /// let val = unsafe { capsule.reference::<u32>() }; |
64 | /// assert_eq!(*val, 123); |
65 | /// }); |
66 | /// ``` |
67 | /// |
68 | /// However, attempting to construct a `PyCapsule` with a zero-sized type will not compile: |
69 | /// |
70 | /// ```compile_fail |
71 | /// use pyo3::{prelude::*, types::PyCapsule}; |
72 | /// use std::ffi::CString; |
73 | /// |
74 | /// Python::with_gil(|py| { |
75 | /// let capsule = PyCapsule::new(py, (), None).unwrap(); // Oops! `()` is zero sized! |
76 | /// }); |
77 | /// ``` |
78 | pub fn new<T: 'static + Send + AssertNotZeroSized>( |
79 | py: Python<'_>, |
80 | value: T, |
81 | name: Option<CString>, |
82 | ) -> PyResult<&Self> { |
83 | Self::new_with_destructor(py, value, name, |_, _| {}) |
84 | } |
85 | |
86 | /// Constructs a new capsule whose contents are `value`, associated with `name`. |
87 | /// |
88 | /// Also provides a destructor: when the `PyCapsule` is destroyed, it will be passed the original object, |
89 | /// as well as a `*mut c_void` which will point to the capsule's context, if any. |
90 | /// |
91 | /// The `destructor` must be `Send`, because there is no guarantee which thread it will eventually |
92 | /// be called from. |
93 | pub fn new_with_destructor< |
94 | T: 'static + Send + AssertNotZeroSized, |
95 | F: FnOnce(T, *mut c_void) + Send, |
96 | >( |
97 | py: Python<'_>, |
98 | value: T, |
99 | name: Option<CString>, |
100 | destructor: F, |
101 | ) -> PyResult<&'_ Self> { |
102 | AssertNotZeroSized::assert_not_zero_sized(&value); |
103 | |
104 | // Sanity check for capsule layout |
105 | debug_assert_eq!(memoffset::offset_of!(CapsuleContents::<T, F>, value), 0); |
106 | |
107 | let name_ptr = name.as_ref().map_or(std::ptr::null(), |name| name.as_ptr()); |
108 | let val = Box::new(CapsuleContents { |
109 | value, |
110 | destructor, |
111 | name, |
112 | }); |
113 | |
114 | unsafe { |
115 | let cap_ptr = ffi::PyCapsule_New( |
116 | Box::into_raw(val) as *mut c_void, |
117 | name_ptr, |
118 | Some(capsule_destructor::<T, F>), |
119 | ); |
120 | py.from_owned_ptr_or_err(cap_ptr) |
121 | } |
122 | } |
123 | |
124 | /// Imports an existing capsule. |
125 | /// |
126 | /// The `name` should match the path to the module attribute exactly in the form |
127 | /// of `"module.attribute"`, which should be the same as the name within the capsule. |
128 | /// |
129 | /// # Safety |
130 | /// |
131 | /// It must be known that the capsule imported by `name` contains an item of type `T`. |
132 | pub unsafe fn import<'py, T>(py: Python<'py>, name: &CStr) -> PyResult<&'py T> { |
133 | let ptr = ffi::PyCapsule_Import(name.as_ptr(), false as c_int); |
134 | if ptr.is_null() { |
135 | Err(PyErr::fetch(py)) |
136 | } else { |
137 | Ok(&*(ptr as *const T)) |
138 | } |
139 | } |
140 | |
141 | /// Sets the context pointer in the capsule. |
142 | /// |
143 | /// Returns an error if this capsule is not valid. |
144 | /// |
145 | /// # Notes |
146 | /// |
147 | /// The context is treated much like the value of the capsule, but should likely act as |
148 | /// a place to store any state management when using the capsule. |
149 | /// |
150 | /// If you want to store a Rust value as the context, and drop it from the destructor, use |
151 | /// `Box::into_raw` to convert it into a pointer, see the example. |
152 | /// |
153 | /// # Example |
154 | /// |
155 | /// ``` |
156 | /// use std::sync::mpsc::{channel, Sender}; |
157 | /// use libc::c_void; |
158 | /// use pyo3::{prelude::*, types::PyCapsule}; |
159 | /// |
160 | /// let (tx, rx) = channel::<String>(); |
161 | /// |
162 | /// fn destructor(val: u32, context: *mut c_void) { |
163 | /// let ctx = unsafe { *Box::from_raw(context as *mut Sender<String>) }; |
164 | /// ctx.send("Destructor called!" .to_string()).unwrap(); |
165 | /// } |
166 | /// |
167 | /// Python::with_gil(|py| { |
168 | /// let capsule = |
169 | /// PyCapsule::new_with_destructor(py, 123, None, destructor as fn(u32, *mut c_void)) |
170 | /// .unwrap(); |
171 | /// let context = Box::new(tx); // `Sender<String>` is our context, box it up and ship it! |
172 | /// capsule.set_context(Box::into_raw(context) as *mut c_void).unwrap(); |
173 | /// // This scope will end, causing our destructor to be called... |
174 | /// }); |
175 | /// |
176 | /// assert_eq!(rx.recv(), Ok("Destructor called!" .to_string())); |
177 | /// ``` |
178 | #[allow (clippy::not_unsafe_ptr_arg_deref)] |
179 | pub fn set_context(&self, context: *mut c_void) -> PyResult<()> { |
180 | let result = unsafe { ffi::PyCapsule_SetContext(self.as_ptr(), context) }; |
181 | if result != 0 { |
182 | Err(PyErr::fetch(self.py())) |
183 | } else { |
184 | Ok(()) |
185 | } |
186 | } |
187 | |
188 | /// Gets the current context stored in the capsule. If there is no context, the pointer |
189 | /// will be null. |
190 | /// |
191 | /// Returns an error if this capsule is not valid. |
192 | pub fn context(&self) -> PyResult<*mut c_void> { |
193 | let ctx = unsafe { ffi::PyCapsule_GetContext(self.as_ptr()) }; |
194 | if ctx.is_null() { |
195 | ensure_no_error(self.py())? |
196 | } |
197 | Ok(ctx) |
198 | } |
199 | |
200 | /// Obtains a reference to the value of this capsule. |
201 | /// |
202 | /// # Safety |
203 | /// |
204 | /// It must be known that this capsule is valid and its pointer is to an item of type `T`. |
205 | pub unsafe fn reference<T>(&self) -> &T { |
206 | &*(self.pointer() as *const T) |
207 | } |
208 | |
209 | /// Gets the raw `c_void` pointer to the value in this capsule. |
210 | /// |
211 | /// Returns null if this capsule is not valid. |
212 | pub fn pointer(&self) -> *mut c_void { |
213 | unsafe { |
214 | let ptr = ffi::PyCapsule_GetPointer(self.0.as_ptr(), self.name_ptr_ignore_error()); |
215 | if ptr.is_null() { |
216 | ffi::PyErr_Clear(); |
217 | } |
218 | ptr |
219 | } |
220 | } |
221 | |
222 | /// Checks if this is a valid capsule. |
223 | /// |
224 | /// Returns true if the stored `pointer()` is non-null. |
225 | pub fn is_valid(&self) -> bool { |
226 | // As well as if the stored pointer is null, PyCapsule_IsValid also returns false if |
227 | // self.as_ptr() is null or not a ptr to a PyCapsule object. Both of these are guaranteed |
228 | // to not be the case thanks to invariants of this PyCapsule struct. |
229 | let r = unsafe { ffi::PyCapsule_IsValid(self.as_ptr(), self.name_ptr_ignore_error()) }; |
230 | r != 0 |
231 | } |
232 | |
233 | /// Retrieves the name of this capsule, if set. |
234 | /// |
235 | /// Returns an error if this capsule is not valid. |
236 | pub fn name(&self) -> PyResult<Option<&CStr>> { |
237 | unsafe { |
238 | let ptr = ffi::PyCapsule_GetName(self.as_ptr()); |
239 | if ptr.is_null() { |
240 | ensure_no_error(self.py())?; |
241 | Ok(None) |
242 | } else { |
243 | Ok(Some(CStr::from_ptr(ptr))) |
244 | } |
245 | } |
246 | } |
247 | |
248 | /// Attempts to retrieve the raw name pointer of this capsule. |
249 | /// |
250 | /// On error, clears the error indicator and returns NULL. This is a private function and next |
251 | /// use of this capsule will error anyway. |
252 | fn name_ptr_ignore_error(&self) -> *const c_char { |
253 | let ptr = unsafe { ffi::PyCapsule_GetName(self.as_ptr()) }; |
254 | if ptr.is_null() { |
255 | unsafe { ffi::PyErr_Clear() }; |
256 | } |
257 | ptr |
258 | } |
259 | } |
260 | |
261 | // C layout, as PyCapsule::get_reference depends on `T` being first. |
262 | #[repr (C)] |
263 | struct CapsuleContents<T: 'static + Send, D: FnOnce(T, *mut c_void) + Send> { |
264 | /// Value of the capsule |
265 | value: T, |
266 | /// Destructor to be used by the capsule |
267 | destructor: D, |
268 | /// Name used when creating the capsule |
269 | name: Option<CString>, |
270 | } |
271 | |
272 | // Wrapping ffi::PyCapsule_Destructor for a user supplied FnOnce(T) for capsule destructor |
273 | unsafe extern "C" fn capsule_destructor<T: 'static + Send, F: FnOnce(T, *mut c_void) + Send>( |
274 | capsule: *mut ffi::PyObject, |
275 | ) { |
276 | let ptr: *mut c_void = ffi::PyCapsule_GetPointer(capsule, name:ffi::PyCapsule_GetName(capsule)); |
277 | let ctx: *mut c_void = ffi::PyCapsule_GetContext(capsule); |
278 | let CapsuleContents { |
279 | value: T, destructor: F, .. |
280 | } = *Box::from_raw(ptr as *mut CapsuleContents<T, F>); |
281 | destructor(value, ctx) |
282 | } |
283 | |
284 | /// Guarantee `T` is not zero sized at compile time. |
285 | // credit: `<https://users.rust-lang.org/t/is-it-possible-to-assert-at-compile-time-that-foo-t-is-not-called-with-a-zst/67685>` |
286 | #[doc (hidden)] |
287 | pub trait AssertNotZeroSized: Sized { |
288 | const _CONDITION: usize = (std::mem::size_of::<Self>() == 0) as usize; |
289 | const _CHECK: &'static str = |
290 | ["PyCapsule value type T must not be zero-sized!" ][Self::_CONDITION]; |
291 | #[allow (path_statements, clippy::no_effect)] |
292 | fn assert_not_zero_sized(&self) { |
293 | <Self as AssertNotZeroSized>::_CHECK; |
294 | } |
295 | } |
296 | |
297 | impl<T> AssertNotZeroSized for T {} |
298 | |
299 | fn ensure_no_error(py: Python<'_>) -> PyResult<()> { |
300 | if let Some(err: PyErr) = PyErr::take(py) { |
301 | Err(err) |
302 | } else { |
303 | Ok(()) |
304 | } |
305 | } |
306 | |
307 | #[cfg (test)] |
308 | mod tests { |
309 | use libc::c_void; |
310 | |
311 | use crate::prelude::PyModule; |
312 | use crate::{types::PyCapsule, Py, PyResult, Python}; |
313 | use std::ffi::CString; |
314 | use std::sync::mpsc::{channel, Sender}; |
315 | |
316 | #[test ] |
317 | fn test_pycapsule_struct() -> PyResult<()> { |
318 | #[repr (C)] |
319 | struct Foo { |
320 | pub val: u32, |
321 | } |
322 | |
323 | impl Foo { |
324 | fn get_val(&self) -> u32 { |
325 | self.val |
326 | } |
327 | } |
328 | |
329 | Python::with_gil(|py| -> PyResult<()> { |
330 | let foo = Foo { val: 123 }; |
331 | let name = CString::new("foo" ).unwrap(); |
332 | |
333 | let cap = PyCapsule::new(py, foo, Some(name.clone()))?; |
334 | assert!(cap.is_valid()); |
335 | |
336 | let foo_capi = unsafe { cap.reference::<Foo>() }; |
337 | assert_eq!(foo_capi.val, 123); |
338 | assert_eq!(foo_capi.get_val(), 123); |
339 | assert_eq!(cap.name().unwrap(), Some(name.as_ref())); |
340 | Ok(()) |
341 | }) |
342 | } |
343 | |
344 | #[test ] |
345 | fn test_pycapsule_func() { |
346 | fn foo(x: u32) -> u32 { |
347 | x |
348 | } |
349 | |
350 | let cap: Py<PyCapsule> = Python::with_gil(|py| { |
351 | let name = CString::new("foo" ).unwrap(); |
352 | let cap = PyCapsule::new(py, foo as fn(u32) -> u32, Some(name)).unwrap(); |
353 | cap.into() |
354 | }); |
355 | |
356 | Python::with_gil(|py| { |
357 | let f = unsafe { cap.as_ref(py).reference::<fn(u32) -> u32>() }; |
358 | assert_eq!(f(123), 123); |
359 | }); |
360 | } |
361 | |
362 | #[test ] |
363 | fn test_pycapsule_context() -> PyResult<()> { |
364 | Python::with_gil(|py| { |
365 | let name = CString::new("foo" ).unwrap(); |
366 | let cap = PyCapsule::new(py, 0, Some(name))?; |
367 | |
368 | let c = cap.context()?; |
369 | assert!(c.is_null()); |
370 | |
371 | let ctx = Box::new(123_u32); |
372 | cap.set_context(Box::into_raw(ctx) as _)?; |
373 | |
374 | let ctx_ptr: *mut c_void = cap.context()?; |
375 | let ctx = unsafe { *Box::from_raw(ctx_ptr as *mut u32) }; |
376 | assert_eq!(ctx, 123); |
377 | Ok(()) |
378 | }) |
379 | } |
380 | |
381 | #[test ] |
382 | fn test_pycapsule_import() -> PyResult<()> { |
383 | #[repr (C)] |
384 | struct Foo { |
385 | pub val: u32, |
386 | } |
387 | |
388 | Python::with_gil(|py| -> PyResult<()> { |
389 | let foo = Foo { val: 123 }; |
390 | let name = CString::new("builtins.capsule" ).unwrap(); |
391 | |
392 | let capsule = PyCapsule::new(py, foo, Some(name.clone()))?; |
393 | |
394 | let module = PyModule::import(py, "builtins" )?; |
395 | module.add("capsule" , capsule)?; |
396 | |
397 | // check error when wrong named passed for capsule. |
398 | let wrong_name = CString::new("builtins.non_existant" ).unwrap(); |
399 | let result: PyResult<&Foo> = unsafe { PyCapsule::import(py, wrong_name.as_ref()) }; |
400 | assert!(result.is_err()); |
401 | |
402 | // corret name is okay. |
403 | let cap: &Foo = unsafe { PyCapsule::import(py, name.as_ref())? }; |
404 | assert_eq!(cap.val, 123); |
405 | Ok(()) |
406 | }) |
407 | } |
408 | |
409 | #[test ] |
410 | fn test_vec_storage() { |
411 | let cap: Py<PyCapsule> = Python::with_gil(|py| { |
412 | let name = CString::new("foo" ).unwrap(); |
413 | |
414 | let stuff: Vec<u8> = vec![1, 2, 3, 4]; |
415 | let cap = PyCapsule::new(py, stuff, Some(name)).unwrap(); |
416 | |
417 | cap.into() |
418 | }); |
419 | |
420 | Python::with_gil(|py| { |
421 | let ctx: &Vec<u8> = unsafe { cap.as_ref(py).reference() }; |
422 | assert_eq!(ctx, &[1, 2, 3, 4]); |
423 | }) |
424 | } |
425 | |
426 | #[test ] |
427 | fn test_vec_context() { |
428 | let context: Vec<u8> = vec![1, 2, 3, 4]; |
429 | |
430 | let cap: Py<PyCapsule> = Python::with_gil(|py| { |
431 | let name = CString::new("foo" ).unwrap(); |
432 | let cap = PyCapsule::new(py, 0, Some(name)).unwrap(); |
433 | cap.set_context(Box::into_raw(Box::new(&context)) as _) |
434 | .unwrap(); |
435 | |
436 | cap.into() |
437 | }); |
438 | |
439 | Python::with_gil(|py| { |
440 | let ctx_ptr: *mut c_void = cap.as_ref(py).context().unwrap(); |
441 | let ctx = unsafe { *Box::from_raw(ctx_ptr as *mut &Vec<u8>) }; |
442 | assert_eq!(ctx, &vec![1_u8, 2, 3, 4]); |
443 | }) |
444 | } |
445 | |
446 | #[test ] |
447 | fn test_pycapsule_destructor() { |
448 | let (tx, rx) = channel::<bool>(); |
449 | |
450 | fn destructor(_val: u32, ctx: *mut c_void) { |
451 | assert!(!ctx.is_null()); |
452 | let context = unsafe { *Box::from_raw(ctx as *mut Sender<bool>) }; |
453 | context.send(true).unwrap(); |
454 | } |
455 | |
456 | Python::with_gil(|py| { |
457 | let name = CString::new("foo" ).unwrap(); |
458 | let cap = PyCapsule::new_with_destructor(py, 0, Some(name), destructor).unwrap(); |
459 | cap.set_context(Box::into_raw(Box::new(tx)) as _).unwrap(); |
460 | }); |
461 | |
462 | // the destructor was called. |
463 | assert_eq!(rx.recv(), Ok(true)); |
464 | } |
465 | |
466 | #[test ] |
467 | fn test_pycapsule_no_name() { |
468 | Python::with_gil(|py| { |
469 | let cap = PyCapsule::new(py, 0usize, None).unwrap(); |
470 | |
471 | assert_eq!(unsafe { cap.reference::<usize>() }, &0usize); |
472 | assert_eq!(cap.name().unwrap(), None); |
473 | assert_eq!(cap.context().unwrap(), std::ptr::null_mut()); |
474 | }); |
475 | } |
476 | } |
477 | |