提问人:rodrigocfd 提问时间:2/23/2023 最后编辑:rodrigocfd 更新时间:2/24/2023 访问量:249
如何在 Rust 中创建一个结构体,其中最后一个元素是可变长度的数组?
How to create a struct in Rust where the last element is an array of variable length?
问:
我正在尝试在 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);
考虑到 LOGPALETTE
和 PALETTEENTRY
声明,我如何在 Rust 中编写它?
编辑:
答:
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赞
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
对我来说看起来不错。
评论
windows
windows-sys
mem::transmute
LOGPALETTE
和PALETTEENTRY
。当然,没有回答这个问题,只是作为评论。LOGPALETTE