前言
前面簡單地嘗試pyo3,繼續嘗試。
在正式搞事情之前,筆者發現一個問題,如果筆者使用Pycharm打開項目,項目沒什麼問題,但是筆者使用RustRover打開項目,居然會報錯
error: failed to run custom build command for `pyo3-build-config v0.27.1`
note: To improve backtraces for build dependencies, set the CARGO_PROFILE_DEV_BUILD_OVERRIDE_DEBUG=true environment variable to enable debug information generation.
Caused by:
process didn't exit successfully: `F:\code\Python\rye-rp\target\debug\build\pyo3-build-config-e55f551bc4a0cfcc\build-script-build` (exit code: 1)
--- stdout
cargo:rerun-if-env-changed=PYO3_CONFIG_FILE
cargo:rerun-if-env-changed=PYO3_NO_PYTHON
cargo:rerun-if-env-changed=PYO3_ENVIRONMENT_SIGNATURE
cargo:rerun-if-env-changed=PYO3_PYTHON
cargo:rerun-if-env-changed=VIRTUAL_ENV
cargo:rerun-if-env-changed=CONDA_PREFIX
cargo:rerun-if-env-changed=PATH
--- stderr
error: no Python 3.x interpreter found
意思是,沒有環境變量PYO3_PYTHON,
筆者在根目錄下新建一個.cargo目錄,其中新建一個config.toml文件
內容如下
[env]
PYO3_PYTHON = "F:/code/Python/rye-rp/.venv/Scripts/python.exe"
筆者設置為環境變量為虛擬環境裏面的環境變量。行
筆者決定使用lldb來看看內部的結構。
單個字段
測試代碼如下
use pyo3::prelude::*;
use std::any::type_name_of_val;
use pyo3::types::PyString;
#[pyclass]
struct Foo{
#[pyo3(get, set)]
name: String
}
#[pymethods]
impl Foo{
#[new]
fn new(name: String) -> Self {
Self{
name
}
}
}
#[cfg(test)]
mod tests{
use super::*;
use pyo3::Python;
#[test]
fn test_get_name(){
Python::initialize();
Python::attach(|py|{
let a=PyString::new(py,"ni hao");
let foo_bound=Bound::new(py,Foo::new(
"hello world".to_string(),
)).unwrap();
println!(" 類型: {}", type_name_of_val(&foo_bound));
})
}
}
那麼可以打個斷點,看看foo_bound
在LLDB裏面
首先,先看看foo_bound的結構
再看一下Bound的定義
#[repr(transparent)]
pub struct Bound<'py, T>(Python<'py>, ManuallyDrop<Py<T>>);
首先,Python是一個證明,裏面是PhantomData,而PhantomData在在運行時 完全不存在,裏面是空的,可以驗證一下里面的內容
如下
(lldb) expr foo_bound.0.__0
(core::marker::PhantomData<ref$<enum2$<pyo3::internal::state::AttachGuard> > >) __0 = {}
(lldb) expr foo_bound.0.__1
(core::marker::PhantomData<pyo3::marker::NotSend>) __1 = {}
筆者查看裏面的內容,可以發現兩個都是{},都是空的,沒有結構,沒有數據
可以獲取地址,然後查看一下內存視圖,加上符號&——取地址
如下
(lldb) expr &foo_bound.0.__0
(*mut core::marker::PhantomData<ref$<enum2$<pyo3::internal::state::AttachGuard> > >) &__0 = 0x000000a2432feca8
看一下內存視圖,如下
可以發現是00 00 00 00 00 00 00 00 ,足以證明內容是空的,什麼都沒有。
而前面e0 ae 34 b2 ab 01 00 00,這是什麼,可以輸出一下food_bound的第二個內容的指針,即
(lldb) expr foo_bound.1.value.0.pointer
(*mut pyo3_ffi::object::PyObject) pointer = 0x000001abb234aee0
Windows(x86/x64)是小端序,所以是0x000001abb234aee0
對於0x000001abb234aee0,可以發現這就是指針,指向PyObject。
可以嘗試讀取一下name,這其實也是比較麻煩的,筆者找了許久才找到
這個指針內部的結構是ob_refcnt和ob_type,
顯然是ob_type,而ob_type裏面也有非常多的東西,筆者沒找到
而這個tb_name裏面好像是這個Pyobject的名字,不是字段name的結果
筆者沒找不到,重新考慮一下
============過了不知道多久===========
,筆者,終於知道,怎麼搞了,中間過程不必細説。總之,先打個斷點
筆者修改一下name的值
let foo_bound=Bound::new(py,Foo::new("hello".to_string())).unwrap();
變成hello
獲取指針
(lldb) expr foo_bound.1.value.0.pointer
(*mut pyo3_ffi::object::PyObject) pointer = 0x0000021db068aee0
(lldb) x/4xg 0x0000021db068aee0
0x21db068aee0: 0x0000000000000001 0x0000021db05235a0
0x21db068aef0: 0x0000000000000005 0x0000021db01fcc10
注意到0x0000000000000005 ,這個顯示就是長度了,hello長度為5,那麼0x0000021db01fcc10應該就是hello了,結果如下
沒問題。
或者使用
(lldb) memory read -s 1 -c 5 -f c 0x0000021db01fcc10
0x21db01fcc10: hello(lldb) x/s 0x0000021db01fcc10
0x21db01fcc10:"hello....."
都行。
多個字段的數據1
筆者修改一下測試
use pyo3::prelude::*;
use std::any::type_name_of_val;
use pyo3::types::PyString;
#[pyclass]
struct Foo{
#[pyo3(get, set)]
name: String,
#[pyo3(get, set)]
age:u32,
#[pyo3(get, set)]
t:String,
}
#[pymethods]
impl Foo{
#[new]
fn new(name: String,age:u32,t:String) -> Self {
Self{
name,
age,
t
}
}
}
#[cfg(test)]
mod tests{
use super::*;
use pyo3::Python;
#[test]
fn test_get_name(){
Python::initialize();
Python::attach(|py|{
let a:u32=15;
let foo_bound=Bound::new(py,Foo::new(
"hello world".to_string(),
96,
"asdasdas".to_string()
)).unwrap();
println!(" 類型: {}", type_name_of_val(&foo_bound));
})
}
}
在println哪裏打個斷點,看看
首先確定地址
(lldb) x/4xg 0x257977a3780
0x257977a3780: 0x0000000000000001 0x00000257976935a0
0x257977a3790: 0x000000000000000b 0x000002579769bbb0
0x000000000000000b可以確定就是指長度11,0x000002579769bbb0應該就是內容
(lldb) x/s 0x000002579769bbb0
0x2579769bbb0: "hello world\xab\xab\x
確實如此,
(lldb) x/10xg 0x257977a3780
0x257977a3780: 0x0000000000000001 0x00000257976935a0
0x257977a3790: 0x000000000000000b 0x000002579769bbb0
0x257977a37a0: 0x000000000000000b 0x0000000000000008
0x257977a37b0: 0x000002579740ccf0 0x0000000000000008
0x257977a37c0: 0x0000025700000060 0x0000000000000000
結合代碼。仔細看看,
0x000002579769bbb0表示hello world ,
0x000000000000000b 這個不知道是什麼東西,
0x0000000000000008顯示是第二個字符串的長度,0x000002579740ccf0應該是內容
(lldb) x/s 0x000002579740ccf0
0x2579740ccf0: "asdasdas\xab\xab
確實如此,
後面0x0000000000000008,表示8,不知道什麼意思
後面 0x0000025700000060 ,也不知道是什麼意思,
後面就沒有了,筆者看了老半天,筆者好像看明白了
首先,96的16進制是60,而有一個00000060,是60,所以,筆者猜測這個就是96所在的位置
筆者換一個數字312,再來看看
(lldb) x/10xg 0x17242f6d020
0x17242f6d020: 0x0000000000000001 0x0000017242e331a0
0x17242f6d030: 0x000000000000000b 0x0000017242e66220
0x17242f6d040: 0x000000000000000b 0x0000000000000008
0x17242f6d050: 0x0000017242e3bbb0 0x0000000000000008
0x17242f6d060: 0x0000017200000138 0x0000000000000000
首先312的16進制是138,直接看關鍵東西,0x0000017200000138裏面的00000138,沒問題
u32表示32位,確實,只有32位,筆者明白了
如果筆者改成u64,就是64位了,如下
(lldb) x/10xg 0x1ef55f03780
0x1ef55f03780: 0x0000000000000001 0x000001ef55e2ee80
0x1ef55f03790: 0x000000000000000b 0x000001ef55b0ccf0
0x1ef55f037a0: 0x000000000000000b 0x0000000000000008
0x1ef55f037b0: 0x000001ef55e075e0 0x0000000000000008
0x1ef55f037c0: 0x0000000000000138 0x0000000000000000
沒問題
多個字段的數據2
再來測試
use pyo3::prelude::*;
use std::any::type_name_of_val;
use pyo3::types::PyString;
#[pyclass]
struct Foo{
#[pyo3(get, set)]
name: String,
#[pyo3(get, set)]
age:u32,
#[pyo3(get, set)]
g:Py<PyString>
}
#[pymethods]
impl Foo{
#[new]
fn new(name: String,age:u32,g:Bound<PyString>) -> Self {
Self{
name,
age,
g:g.unbind()
}
}
}
#[cfg(test)]
mod tests{
use super::*;
use pyo3::Python;
#[test]
fn test_get_name(){
Python::initialize();
Python::attach(|py|{
let a=PyString::new(py,"ni hao");
let foo_bound=Bound::new(py,Foo::new(
"hello world".to_string(),
312,
a
)).unwrap();
println!(" 類型: {}", type_name_of_val(&foo_bound));
})
}
}
斷點,運行 首先,先看看a的數據
直入主題,複製地址,看看
(lldb) x/6xg 0x180014da9a0
0x180014da9a0: 0x0000000000000001 0x00007fffda0170c0
0x180014da9b0: 0x0000000000000006 0xffffffffffffffff
0x180014da9c0: 0x0000018001277064 0x00006f616820696e
0x0000000000000006 顯然是長度,0xffffffffffffffff和0x000001f6c01e7064 不知道是幹什麼的
而 0x00006f616820696e是內容,
6e 是110,表示n
69 是105,表示i
其他同理。
看看foo_bound的內容
複製地址——0x1800127bbb0
這兩個地址之差是0x25edf0—— 2.37MB,有點大
這個差值反映的是兩個 Python 堆對象之間的內存距離。
然後,筆者重新運行一下,失誤了,重新來
新的a的地址——0x1af9539a9a0
新的foo_bound的地址——0x1af9513bbb0
二者之差還是2.37MB,可以的,查看0x1af9513bbb0
(lldb) x/8xg 0x1af9513bbb0
0x1af9513bbb0: 0x0000000000000001 0x000001af9526b620
0x1af9513bbc0: 0x000000000000000b 0x000001af95245a30
0x1af9513bbd0: 0x000000000000000b 0x000001af9539a9a0
0x1af9513bbe0: 0x0000000100000138 0x0000000000000000
看看地址,0x000000000000000b 是11,指的就是hello world的長度11
那麼0x000001af95245a30就是指向hello world
0x000001af9539a9a0和a的新的地址0x1af9539a9a0一樣,那麼顯然,這個就是指向a的指針
0x0000000100000138 取00000138就是312,312的類型是u32,沒問題。
有點意思。
PyList
筆者換一個類型來測試一下,PyList,代碼如下
use pyo3::prelude::*;
use std::any::type_name_of_val;
use pyo3::types::{PyList};
#[pyclass]
struct VecFoo{
#[pyo3(get, set)]
name: Py<PyList>,
}
#[pymethods]
impl VecFoo{
#[new]
fn new(name:Bound<PyList>) -> Self {
Self{
name:name.unbind()
}
}
}
#[cfg(test)]
mod tests{
use super::*;
use pyo3::Python;
#[test]
fn test_get_vec(){
Python::initialize();
Python::attach(|py|{
let a=PyList::new(py, vec![1,2,3]);
let foo_vec=Bound::new(py,VecFoo::new(
a.unwrap()
)).unwrap();
println!(" 類型: {}", type_name_of_val(&foo_vec));
})
}
}
打個斷點,先對a進行分析
地址是0x24c6b2eb1c0,看看
(lldb) x/4xg 0x24c6b2eb1c0
0x24c6b2eb1c0: 0x0000000000000001 0x00007fffde2323e0
0x24c6b2eb1d0: 0x0000000000000003 0x0000024c6b20b590
直言的説,0x0000000000000003 就是長度,而0x0000024c6b20b590就是指向vec
換一個長度試試
let a=PyList::new(py, vec![1,3,9,27,45]);
再次測試,長度應該是5,確定地址
(lldb) x/4xg 0x1a8cf11b1c0
0x1a8cf11b1c0: 0x0000000000000001 0x00007fffde7123e0
0x1a8cf11b1d0: 0x0000000000000005 0x000001a8cf3ea9a0
沒問題,看看0x000001a8cf3ea9a0
(lldb) x/6xg 0x000001a8cf3ea9a0
0x1a8cf3ea9a0: 0x00007fffde7f43a8 0x00007fffde7f43e8
0x1a8cf3ea9b0: 0x00007fffde7f44a8 0x00007fffde7f46e8
0x1a8cf3ea9c0: 0x00007fffde7f4928 0x000001a8cf0e71e0
可以發現前5個地址之間相差0x40,64個字節。
這就很顯然了,數字必然在裏面。看最後一個
(lldb) x/4xg 0x00007fffde7f4928
0x7fffde7f4928: 0x00000000ffffffff 0x00007fffde712580
0x7fffde7f4938: 0x0000000000000008 0x000000000000002d
0x000000000000002d是 45,沒問題。
看foo_vec
地址是0x1a8cf03af50,筆者現在已經可以斷言,裏面有0x1a8cf11b1c0這個a的地址,
結果如下
(lldb) x/4xg 0x1a8cf03af50
0x1a8cf03af50: 0x0000000000000001 0x000001a8cf2e41d0
0x1a8cf03af60: 0x000001a8cf11b1c0 0x0000000000000000
沒問題,哈哈哈哈哈哈哈哈哈哈
=========就這樣,有點意思,明天再説============
PyDict
筆者再換一個類型看看
測試代碼如下
use pyo3::prelude::*;
use std::any::type_name_of_val;
use pyo3::types::{PyDict};
#[pyclass]
struct VecFoo{
#[pyo3(get, set)]
name: Py<PyDict>,
}
#[pymethods]
impl VecFoo{
#[new]
fn new(name:Bound<PyDict>) -> Self {
Self{
name:name.unbind()
}
}
}
#[cfg(test)]
mod tests{
use super::*;
use pyo3::Python;
#[test]
fn test_get_vec(){
Python::initialize();
Python::attach(|py|{
let a=PyDict::new(py);
a.set_item("id",1).unwrap();
a.set_item("name","hello").unwrap();
let foo_vec=Bound::new(py,VecFoo::new(
a
)).unwrap();
println!(" 類型: {}", type_name_of_val(&foo_vec));
})
}
}
無論是PyDict還是PyList,調用new方法,都是返回Bound
可以看看函數簽名,如下。
impl PyDict {
/// Creates a new empty dictionary.
pub fn new(py: Python<'_>) -> Bound<'_, PyDict> {
unsafe { ffi::PyDict_New().assume_owned(py).cast_into_unchecked() }
}
....
}
裏面也是比較複雜的,總之,返回Bound。
PyDict的可以調用的方法在PyDictMethods裏面
#[doc(alias = "PyDict")]
pub trait PyDictMethods<'py>: crate::sealed::Sealed
有很多,比如set_item、get_item、keys、values之類的,python裏面也有。
要獲取字典裏面的值,這就值得考慮了,必然涉及hash表了
===========過了不知道多久=========
修改一下a的set_item
a.set_item("id",1387).unwrap();
a.set_item("names","hellosasafsdf").unwrap();
a.set_item("tt6","gsdfjjuihmjhg").unwrap();
筆者直接打個斷點。直接看a的地址,如下
0x1854bcb19c0
讀取這個地址
(lldb) x/6xg 0x1854bcb19c0
0x1854bcb19c0: 0x0000000000000001 0x00007fffec818e60
0x1854bcb19d0: 0x0000000000000003 0x0000000003323000
0x1854bcb19e0: 0x000001854ba33d30 0x0000000000000000
這6個東西,可能是地址,可能是數據,
可以斷言0x0000000000000003 是指的dict中key的數量。
筆者測試過,添加一個就變成了4
0x0000000003323000不可以使用,如下
那麼就只要0x000001854ba33d30 這個地址可以使用
讀取0x000001854ba33d30
(lldb) x/6xg 0x000001854ba33d30
0x1854ba33d30: 0x0000000000000001 0x0000000000010303
0x1854ba33d40: 0x0000000000000002 0x0000000000000003
0x1854ba33d50: 0x01ffffffffff0002 0x000001854bcfa9a0
這裏也有6個地址,説實話,也只要0x000001854bcfa9a0這個地址可以使用,其他0x0000000000000002 、顯然指的數據2,不知道是什麼含義,但可以確定不是地址
總之,就只要0x000001854bcfa9a0可以使用
0x01ffffffffff0002 也不行,如下
都在報錯
讀取0x000001854bcfa9a0
(lldb) x/6xg 0x000001854bcfa9a0
0x1854bcfa9a0: 0x0000000000000001 0x00007fffec8270c0
0x1854bcfa9b0: 0x0000000000000002 0x0a5d181c41bb99f9
0x1854bcfa9c0: 0x000001854ba37064 0x000001854b006469
這6個東西的,筆者不好説,直接看內存視圖,如下
筆者專門標註了一個id,就是最後一個0x000001854b006469,中的6469,即 69 64
是id,這是巧合嗎?
筆者不裝了,直接操作。
重新運行一下。
a的地址是0x1f31db819c0
第一次讀取
(lldb) x/6xg 0x1f31db819c0
0x1f31db819c0: 0x0000000000000001 0x00007fffec818e60
0x1f31db819d0: 0x0000000000000003 0x0000000003323000
0x1f31db819e0: 0x000001f31da13d30 0x0000000000000000
0x000001f31da13d30 +40=0x000001f31da13d58
第二次讀取
(lldb) x/6xg 0x000001f31da13d58
0x1f31da13d58: 0x000001f31dbca9a0 0x000001f31d96b590
0x1f31da13d68: 0x000001f31dbcab20 0x000001f31db81a30
0x1f31da13d78: 0x000001f31dbcaaf0 0x000001f31db81ab0
這六個地址,分別讀取
第一個地址
(lldb) x/6xg 0x000001f31dbca9a0
0x1f31dbca9a0: 0x0000000000000001 0x00007fffec8270c0
0x1f31dbca9b0: 0x0000000000000002 0x3a9079259de0afc1
0x1f31dbca9c0: 0x000001f31da17064 0x000001f31d006469
可以發現最後一個地址0x000001f31d006469,顯然是id
至於前面的000001f31d00,筆者也不知道。
總之,這是第一個key
第二個地址
(lldb) x/6xg 0x000001f31d96b590
0x1f31d96b590: 0x0000000000000001 0x00007fffec822580
0x1f31d96b5a0: 0x0000000000000008 0x000001f30000056b
0x1f31d96b5b0: 0x0000000000000001 0x00007fffec822580
注意到,0x000001f30000056b中的56b,前面1387,16進制是0x56b,那麼這個就是id對應的值
這是第一個value
第三個地址
(lldb) x/6xg 0x000001f31dbcab20
0x1f31dbcab20: 0x0000000000000001 0x00007fffec8270c0
0x1f31dbcab30: 0x0000000000000005 0x8dbc1a921a4da357
0x1f31dbcab40: 0xffffffffffffff64 0x00000073656d616e
注意到0x00000073656d616e,變成 6e 61 6d 65 73 變成ascii字符是n a m e s
這是第二個key
第四個地址
(lldb) x/7xg 0x000001f31db81a30
0x1f31db81a30: 0x0000000000000001 0x00007fffec8270c0
0x1f31db81a40: 0x000000000000000d 0xffffffffffffffff
0x1f31db81a50: 0x000001f31d9b2364 0x7361736f6c6c6568
0x1f31db81a60: 0x0000006664736661
注意到0x7361736f6c6c6568和0x0000006664736661
73 → s
61 → a
73 → s
6f → o
6c → l
6c → l
65 → e
68 → h
66 → f
64 → d
73 → s
66 → f
61 → a
第二個value
第五個地址
(lldb) x/6xg 0x000001f31dbcaaf0
0x1f31dbcaaf0: 0x0000000000000001 0x00007fffec8270c0
0x1f31dbcab00: 0x0000000000000003 0xa2f85981c063f6ea
0x1f31dbcab10: 0xffffffffffffff64 0x0000000000367474
0x36 → 6
0x74 → t
0x74 → t
第三個key
第六個地址
(lldb) x/7xg 0x000001f31db81ab0
0x1f31db81ab0: 0x0000000000000001 0x00007fffec8270c0
0x1f31db81ac0: 0x000000000000000d 0xffffffffffffffff
0x1f31db81ad0: 0x0000000000000064 0x69756a6a66647367
0x1f31db81ae0: 0x00000067686a6d68
69 → i
75 → u
6a → j
6a → j
66 → f
64 → d
73 → s
67 → g
67 → g
68 → h
6a → j
6d → m
68 → h
第三個value
行,感覺沒問題,沒有計算什麼hash值之類的,最關鍵的一步是+40
繼續,前面是獲取a裏面的數據,還有foo_vec,這感覺不是很需要,因為筆者可以確定foo_vec中有a的地址
首先,a的地址是
0x21df98919c0
看看foo_vec的地址
讀取地址,結果如下
(lldb) x/4xg 0x21df98d0e70
0x21df98d0e70: 0x0000000000000001 0x0000021df9776390
0x21df98d0e80: 0x0000021df98919c0 0x0000000000000000
0x0000021df98919c0 就是a的地址,簡單。
總結
筆者回看了一下,全都是地址,確實不好看,而且地址每次運行都不一樣
只可意會,不可言傳了。
哈哈哈哈哈哈哈哈哈哈哈哈哈哈