如何在 Rust 中创建一个结构体,其中最后一个元素是可变长度的数组?

How to create a struct in Rust where the last element is an array of variable length?

提问人:rodrigocfd 提问时间:2/23/2023 最后编辑:rodrigocfd 更新时间:2/24/2023 访问量:249

问:

我正在尝试在 Rust 中创建一个动态 LOGPALETTE 结构。此结构的最后一个字段名义上声明为 1 个元素的数组,但实际上它可以是任意数量的元素的数组。当我们在堆中分配结构时,我们指定元素的数量。

这就是我在 C 中的做法:

PALETTEENTRY entry = {0};

LOGPALETTE* pLogPal = (LOGPALETTE*)malloc(
    sizeof(LOGPALETTE) + 2 * sizeof(PALETTEENTRY) // room for 2 elements
);
pLogPal->palNumEntries = 2;      // inform we have 2 elements
pLogPal->palPalEntry[0] = entry; // fill the 2 elements
pLogPal->palPalEntry[1] = entry;

// use pLogPal...

free(pLogPal);

考虑到 LOGPALETTEPALETTEENTRY 声明,我如何在 Rust 中编写它?

编辑:

在这里的 WinSafe 库中实现了答案。如果您想讨论它,请随时在 repo 中提出问题。

数组 winapi rust malloc

评论

0赞 Chayim Friedman 2/23/2023
吹毛求疵:使用官方和板条箱。windowswindows-sys
1赞 IInspectable 2/23/2023
可能首先需要解决 C 代码中的错误,然后将其转化为 Rust 代码。mem::transmute
0赞 Finomnis 2/23/2023
除了 @ChayimFriedman 的评论之外,以下是您应该使用的条目:LOGPALETTEPALETTEENTRY。当然,没有回答这个问题,只是作为评论。
1赞 Finomnis 2/23/2023
我想这归结为您将使用它。如果 Rust 管理调色板的整个生命周期,你可以创建一个 Rust 对象,然后可以返回一个指针。但是如果你想在 Rust 中创建它,把它传递给 C,然后在 C 中释放它,那么我认为你需要走丑陋的指针方式。LOGPALETTE

答:

2赞 Chayim Friedman 2/23/2023 #1

不幸的是,Rust 中没有对 VLA 的直接支持。因此,你需要手动执行此操作(不比 C 差,但在 C 中,您可以使用大多数语言的工具来处理指针,而在 Rust 中,您必须使用原始指针并且不能使用引用)。您还需要非常小心,不要创建引用,因为引用只能用于读取它们所指向的类型大小内的数据。也不能创建对未初始化内存的引用。

下面是一个这样做的示例:

unsafe {
    // Allocate space for 2 elements (one is already there).
    let layout = std::alloc::Layout::new::<tagLOGPALETTE>()
        .extend(std::alloc::Layout::array::<tagPALETTEENTRY>(1).unwrap())
        .unwrap()
        .0;
    let log_pal = std::alloc::alloc(layout).cast::<tagLOGPALETTE>();
    if log_pal.is_null() {
        std::alloc::handle_alloc_error(layout);
    }
    // Don't use `*` or `=`, it will create a reference!
    std::ptr::addr_of_mut!((*log_pal).palNumEntries).write(2);
    let entry = std::mem::zeroed::<tagPALETTEENTRY>();
    std::ptr::addr_of_mut!((*log_pal).palPalEntry[0])
        .add(0)
        .write(entry);
    std::ptr::addr_of_mut!((*log_pal).palPalEntry[0])
        .add(1)
        .write(entry);

    // Here, after you initialized them, you can create a slice of the entries:
    let entries = std::slice::from_raw_parts(std::ptr::addr_of!((*log_pal).palPalEntry[0]), 2);
    // But you can't create a reference to the whole type, even without accessing the entires
    // (only other fields), because you didn't initialize `palVersion`!
}

评论

0赞 Finomnis 2/23/2023
引用文档中的一句话:“为了匹配 C 表示布局 repr(C),您应该在使用所有字段扩展布局后调用 pad_to_align.extend
0赞 Chayim Friedman 2/23/2023
@Finomnis True 但在这种情况下不是必需的(尽管它会起作用),因为我们以数组结尾。
0赞 rodrigocfd 2/23/2023
我们不应该调用 std::alloc::d ealloc 吗?log_pal
0赞 Chayim Friedman 2/23/2023
@rodrigocfd 当然,在最后,使用与分配它的布局相同的布局。
1赞 Chayim Friedman 2/23/2023
@rodrigocfd 很遗憾,不可以,因为它的分配大小不正确。
1赞 Finomnis 2/23/2023 #2

除了ChayimFriedman的回答之外,以下是我解决这个问题的方法:

use std::{alloc::Layout, error::Error, fmt::Debug};

use windows::Win32::Graphics::Gdi::{LOGPALETTE, PALETTEENTRY};

pub struct LogPalette {
    data: *mut LOGPALETTE,
    layout: Layout,
}

impl Debug for LogPalette {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("LogPalette")
            .field("version", &self.get_version())
            .field("entries", &self.get_entries())
            .finish()
    }
}

impl LogPalette {
    pub fn new(
        version: u16,
        data: impl ExactSizeIterator<Item = PALETTEENTRY>,
    ) -> Result<LogPalette, Box<dyn Error>> {
        let num_entries: u16 = data.len().try_into()?;

        unsafe {
            // Compute required allocation size and alignment
            let layout = Layout::new::<LOGPALETTE>()
                .extend(Layout::array::<PALETTEENTRY>(
                    num_entries.saturating_sub(1).into(),
                )?)?
                .0
                .pad_to_align();

            // Allocate
            let logpalette = std::alloc::alloc(layout).cast::<LOGPALETTE>();
            if logpalette.is_null() {
                std::alloc::handle_alloc_error(layout);
            }

            // Fill initial data
            // Don't use `*` or `=`, it will create a reference!
            std::ptr::addr_of_mut!((*logpalette).palVersion).write(version);
            std::ptr::addr_of_mut!((*logpalette).palNumEntries).write(num_entries);

            let entries = std::ptr::addr_of_mut!((*logpalette).palPalEntry).cast::<PALETTEENTRY>();

            for (index, entry) in data.enumerate().take(num_entries.into()) {
                entries.add(index).write(entry);
            }

            // This is the point where all elements are initialized; now we are allowed to create references.

            Ok(Self {
                data: logpalette,
                layout,
            })
        }
    }

    pub fn get_raw(&self) -> *const LOGPALETTE {
        self.data
    }

    pub fn get_raw_mut(&mut self) -> *mut LOGPALETTE {
        self.data
    }

    pub fn get_entries(&self) -> &[PALETTEENTRY] {
        unsafe {
            let size = self.get_num_entries();
            let pal_ptr = std::ptr::addr_of!((*self.data).palPalEntry);
            std::slice::from_raw_parts(pal_ptr.cast(), size.into())
        }
    }

    pub fn get_entries_mut(&mut self) -> &mut [PALETTEENTRY] {
        unsafe {
            let size = self.get_num_entries();
            let pal_ptr = std::ptr::addr_of_mut!((*self.data).palPalEntry);
            std::slice::from_raw_parts_mut(pal_ptr.cast(), size.into())
        }
    }

    pub fn get_version(&self) -> u16 {
        unsafe { (*self.data).palVersion }
    }

    pub fn get_num_entries(&self) -> u16 {
        unsafe { (*self.data).palNumEntries }
    }
}

impl Drop for LogPalette {
    fn drop(&mut self) {
        unsafe {
            std::alloc::dealloc(self.data.cast(), self.layout);
        }
    }
}

fn main() {
    let entries = [
        PALETTEENTRY {
            peRed: 255,
            peGreen: 128,
            peBlue: 0,
            peFlags: 0,
        },
        PALETTEENTRY {
            peRed: 255,
            peGreen: 128,
            peBlue: 255,
            peFlags: 0,
        },
        PALETTEENTRY {
            peRed: 0,
            peGreen: 0,
            peBlue: 255,
            peFlags: 0,
        },
    ];

    let mut palette = LogPalette::new(1, entries.into_iter()).unwrap();
    println!("{:#?}", palette);

    println!();
    println!("Setting red value of entry 2 to 127 ...");
    println!();
    palette.get_entries_mut()[2].peRed = 127;

    println!("{:#?}", palette);
}
LogPalette {
    version: 1,
    entries: [
        PALETTEENTRY {
            peRed: 255,
            peGreen: 128,
            peBlue: 0,
            peFlags: 0,
        },
        PALETTEENTRY {
            peRed: 255,
            peGreen: 128,
            peBlue: 255,
            peFlags: 0,
        },
        PALETTEENTRY {
            peRed: 0,
            peGreen: 0,
            peBlue: 255,
            peFlags: 0,
        },
    ],
}

Setting red value of entry 2 to 127 ...

LogPalette {
    version: 1,
    entries: [
        PALETTEENTRY {
            peRed: 255,
            peGreen: 128,
            peBlue: 0,
            peFlags: 0,
        },
        PALETTEENTRY {
            peRed: 255,
            peGreen: 128,
            peBlue: 255,
            peFlags: 0,
        },
        PALETTEENTRY {
            peRed: 127,
            peGreen: 0,
            peBlue: 255,
            peFlags: 0,
        },
    ],
}

评论

1赞 Chayim Friedman 2/24/2023
对我来说看起来不错。