尝试rust与python的混合编程(二)

前言

前面简单地尝试pyo3,继续尝试。

尝试rust与python的混合编程(一)-CSDN博客https://blog.csdn.net/qq_63401240/article/details/155039627?sharetype=blogdetail&sharerId=155039627&sharerefer=PC&sharesource=qq_63401240&spm=1011.2480.3001.8118

在正式搞事情之前,笔者发现一个问题,如果笔者使用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来看看内部的结构。

正文

单个字段

测试代码如下

rust 复制代码
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的定义

rust 复制代码
#[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\<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\<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的值

rust 复制代码
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

gdb分析coredump的几个常用命令介绍_info proc mappings-CSDN博客https://blog.csdn.net/jwybobo2007/article/details/110916914https://blog.csdn.net/jwybobo2007/article/details/110916914使用 命令 x/4xg

(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

笔者修改一下测试

rust 复制代码
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

再来测试

rust 复制代码
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,代码如下

rust 复制代码
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

换一个长度试试

rust 复制代码
 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

笔者再换一个类型看看

测试代码如下

rust 复制代码
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

可以看看函数签名,如下。

rust 复制代码
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里面

rust 复制代码
#[doc(alias = "PyDict")]
pub trait PyDictMethods<'py>: crate::sealed::Sealed

有很多,比如set_item、get_item、keys、values之类的,python里面也有。

要获取字典里面的值,这就值得考虑了,必然涉及hash表了


===========过了不知道多久=========


修改一下a的set_item

rust 复制代码
            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

rust 复制代码
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

rust 复制代码
0x36 → 6  
0x74 → t 
0x74 → t

第三个key

第六个地址

(lldb) x/7xg 0x000001f31db81ab0
0x1f31db81ab0: 0x0000000000000001 0x00007fffec8270c0
0x1f31db81ac0: 0x000000000000000d 0xffffffffffffffff
0x1f31db81ad0: 0x0000000000000064 0x69756a6a66647367
0x1f31db81ae0: 0x00000067686a6d68

rust 复制代码
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

至于为什么是+40,这可能涉及内部Cpython的定义了,笔者使用的python313。


===========明天再说,就这样==========


继续,前面是获取a里面的数据,还有foo_vec,这感觉不是很需要,因为笔者可以确定foo_vec中有a的地址

首先,a的地址是

0x21df98919c0

看看foo_vec的地址

读取地址,结果如下

(lldb) x/4xg 0x21df98d0e70
0x21df98d0e70: 0x0000000000000001 0x0000021df9776390
0x21df98d0e80: 0x0000021df98919c0 0x0000000000000000

0x0000021df98919c0就是a的地址,简单。

总结

笔者回看了一下,全都是地址,确实不好看,而且地址每次运行都不一样

只可意会,不可言传了。

哈哈哈哈哈哈哈哈哈哈哈哈哈哈

相关推荐
keep one's resolveY5 小时前
时区问题解决
数据库
Leinwin5 小时前
OpenClaw 多 Agent 协作框架的并发限制与企业化规避方案痛点直击
java·运维·数据库
qq_417695055 小时前
机器学习与人工智能
jvm·数据库·python
漫随流水5 小时前
旅游推荐系统(view.py)
前端·数据库·python·旅游
ego.iblacat5 小时前
MySQL 服务基础
数据库·mysql
yy我不解释6 小时前
关于comfyui的mmaudio音频生成插件时时间不一致问题(一)
python·ai作画·音视频·comfyui
Maverick067 小时前
Oracle Redo 日志操作手册
数据库·oracle
紫丁香7 小时前
AutoGen详解一
后端·python·flask
FreakStudio7 小时前
不用费劲编译ulab了!纯Mpy矩阵micronumpy库,单片机直接跑
python·嵌入式·边缘计算·电子diy
攒了一袋星辰7 小时前
高并发强一致性顺序号生成系统 -- SequenceGenerator
java·数据库·mysql