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

3

我正在尝试在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);

如何在Rust中编写此代码,考虑到LOGPALETTEPALETTEENTRY声明?

编辑:

我在WinSafe库这里实现了答案。如果您想讨论它,请随时在存储库中打开问题。


挑剔一点:请使用官方的 windowswindows-sys 包。 - Chayim Friedman
1
可能首先需要解决C代码中的bug,然后将其转换为Rust代码,使用mem::transmute - IInspectable
补充一下@ChayimFriedman的评论,您应该使用以下条目:LOGPALETTEPALETTEENTRY。当然这并没有回答问题,只是一句注释。 - Finomnis
1
我想这归结于你将在哪里使用它。如果 Rust 管理调色板的整个生命周期,那么你可以创建一个 Rust 对象,然后返回一个 LOGPALETTE 指针。但是,如果你想在 Rust 中创建它,将其传递给 C,然后在 C 中释放它,那么我认为你需要采用不太美观的指针方式。 - Finomnis
2个回答

2
很遗憾,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`!
}

引用自.extend文档:“为了匹配C表示布局repr(C),您应该在扩展所有字段的布局后调用pad_to_align。” - Finomnis
@Finomnis 是对的,但在这种情况下并不是必要的(尽管它可以工作),因为我们最终以一个数组结束。 - Chayim Friedman
我们不应该在 log_pal 上调用 std::alloc::dealloc 吗? - rodrigocfd
@rodrigocfd 当然可以,在最后,使用你分配的相同布局。 - Chayim Friedman
@ChayimFriedman 是否有可能生成一个 Box<tagLOGPALETTE>,以便可以安全地从函数中返回该对象? - rodrigocfd
1
很抱歉,不行。因为这样会有错误的分配大小。 - Chayim Friedman

1

在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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接