尝试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的地址,简单。

总结

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

只可意会,不可言传了。

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

相关推荐
9***P3341 小时前
Rust在网络中的Rocket
开发语言·后端·rust
小光学长1 小时前
基于微信小程序的家具商城系统g80l9675(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·微信小程序·小程序
j***82702 小时前
Mybatis控制台打印SQL执行信息(执行方法、执行SQL、执行时间)
数据库·sql·mybatis
g***26792 小时前
5、使用 pgAdmin4 图形化创建和管理 PostgreSQL 数据库
数据库·postgresql
P***84392 小时前
【MySQL】C# 连接MySQL
数据库·mysql·c#
8***f3952 小时前
SQL中的REGEXP正则表达式使用指南
数据库·sql·正则表达式
o***74172 小时前
MySQL root用户密码忘记怎么办(Reset root account password)
数据库·mysql·adb
M***Z2102 小时前
【SQL技术】不同数据库引擎 SQL 优化方案剖析
数据库·sql
子午2 小时前
【蘑菇识别系统】Python+TensorFlow+Vue3+Django+人工智能+深度学习+卷积网络+resnet50算法
人工智能·python·深度学习