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

huangapple go评论76阅读模式
英文:

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) // 为2个元素腾出空间
);
pLogPal->palNumEntries = 2;      // 通知有2个元素
pLogPal->palPalEntry[0] = entry; // 填充2个元素
pLogPal->palPalEntry[1] = entry;

// 使用 pLogPal...

free(pLogPal);

在Rust中,您可以这样实现,考虑 LOGPALETTEPALETTEENTRY 的声明:

use winapi::um::wingdi::{LOGPALETTE, PALETTEENTRY};
use std::ptr;
use std::mem;

fn main() {
    let entry: PALETTEENTRY = PALETTEENTRY { peRed: 0, peGreen: 0, peBlue: 0, peFlags: 0 };
    let num_entries: i16 = 2; // 指定元素数量

    let mut log_palette = LOGPALETTE {
        palVersion: 0x300,
        palNumEntries: num_entries,
        palPalEntry: [entry; 1], // 创建包含1个元素的数组,稍后可以重新分配大小
    };

    // 计算结构的实际大小
    let log_palette_size = mem::size_of::<LOGPALETTE>() + (num_entries as usize - 1) * mem::size_of::<PALETTEENTRY>();

    // 分配内存,如果需要更多的元素
    let p_log_pal = unsafe {
        let p = malloc(log_palette_size);
        if p.is_null() {
            // 处理内存分配失败的情况
            panic!("Memory allocation failed.");
        }
        ptr::copy_nonoverlapping(&log_palette, p as *mut LOGPALETTE, 1);
        p as *mut LOGPALETTE
    };

    // 使用 p_log_pal...

    // 最后记得释放内存
    unsafe {
        free(p_log_pal as *mut std::ffi::c_void);
    }
}

请注意,上述Rust代码使用了winapi库来访问Windows API,并使用mallocfree函数来手动管理内存。如果需要更多的元素,可以在分配内存时重新计算结构的大小,并复制数据。请根据您的实际需求进行适当的调整和错误处理。

英文:

I'm trying to create a dynamic LOGPALETTE struct in Rust. The last field of this struct is nominally declared as an array of 1 element, but in fact it can be an array of any number of elements. We specify the number of elements as we alloc the struct in the heap.

This is how I do it in C:

PALETTEENTRY entry = {0};

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

// use pLogPal...

free(pLogPal);

How can I write this in Rust, considering LOGPALETTE and PALETTEENTRY declarations?

EDIT:

I implemented the answer in WinSafe library here. If you want to discuss it, feel free to open an issue at the repo.

答案1

得分: 2

抱歉,Rust不直接支持可变长度数组(VLA)。因此,您需要手动进行操作(不比C差,但在C中,您可以使用大多数语言功能来处理指针,而在Rust中,您必须使用原始指针,不能使用引用)。您还需要非常小心,不要创建引用,因为引用只能用于读取它们指向的类型大小范围内的数据。您还不应创建对未初始化内存的引用。

以下是一个示例:

unsafe {
    // 为2个元素分配空间(已经有一个元素)。
    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);
    }
    // 不要使用 `*` 或 `= `,它会创建引用!
    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);

    // 在初始化后,您可以创建条目的切片:
    let entries = std::slice::from_raw_parts(std::ptr::addr_of!((*log_pal).palPalEntry[0]), 2);
    // 但您不能创建对整个类型的引用,即使在访问条目之前(只能访问其他字段),因为您没有初始化 `palVersion`!
}
英文:

Unfortunately, there is no direct support in Rust for VLAs. Therefore, you need to do that manually (not worse than C, but in C you can use most the language's facilities to work with pointers while in Rust you must use raw pointers and cannot use references). You also need to be very careful not to create references, because references can only be used to read the data within the size of the type they are pointing to. You may also not create references to uninitialized memory.

Here's an example of this being done:

unsafe {
    // Allocate space for 2 elements (one is already there).
    let layout = std::alloc::Layout::new::&lt;tagLOGPALETTE&gt;()
        .extend(std::alloc::Layout::array::&lt;tagPALETTEENTRY&gt;(1).unwrap())
        .unwrap()
        .0;
    let log_pal = std::alloc::alloc(layout).cast::&lt;tagLOGPALETTE&gt;();
    if log_pal.is_null() {
        std::alloc::handle_alloc_error(layout);
    }
    // Don&#39;t use `*` or `=`, it will create a reference!
    std::ptr::addr_of_mut!((*log_pal).palNumEntries).write(2);
    let entry = std::mem::zeroed::&lt;tagPALETTEENTRY&gt;();
    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&#39;t create a reference to the whole type, even without accessing the entires
    // (only other fields), because you didn&#39;t initialize `palVersion`!
}

答案2

得分: 1

以下是你要的代码部分的中文翻译:

添加到ChayimFriedman的回答中,这是我处理这个问题的方式:

```rust
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 {
            // 计算所需的分配大小和对齐方式
            let layout = Layout::new::<LOGPALETTE>()
                .extend(Layout::array::<PALETTEENTRY>(
                    num_entries.saturating_sub(1).into(),
                )?)?
                .0
                .pad_to_align();

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

            // 填充初始数据
            // 不要使用`*`或`=`,它会创建一个引用!
            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);
            }

            // 这是所有元素都初始化的时刻;现在我们允许创建引用。

            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!("将条目2的红色值设置为127 ...");
    println!();
    palette.get_entries_mut()[2].peRed = 127;

    println!("{:#?}", palette);
}

希望这有所帮助!

英文:

Adding to ChayimFriedman's answer, here is how I would tackle this problem:

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(&amp;self, f: &amp;mut std::fmt::Formatter&lt;&#39;_&gt;) -&gt; std::fmt::Result {
        f.debug_struct(&quot;LogPalette&quot;)
            .field(&quot;version&quot;, &amp;self.get_version())
            .field(&quot;entries&quot;, &amp;self.get_entries())
            .finish()
    }
}

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

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

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

            // Fill initial data
            // Don&#39;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::&lt;PALETTEENTRY&gt;();

            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(&amp;self) -&gt; *const LOGPALETTE {
        self.data
    }

    pub fn get_raw_mut(&amp;mut self) -&gt; *mut LOGPALETTE {
        self.data
    }

    pub fn get_entries(&amp;self) -&gt; &amp;[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(&amp;mut self) -&gt; &amp;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(&amp;self) -&gt; u16 {
        unsafe { (*self.data).palVersion }
    }

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

impl Drop for LogPalette {
    fn drop(&amp;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!(&quot;{:#?}&quot;, palette);

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

    println!(&quot;{:#?}&quot;, 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,
},
],
}

huangapple
  • 本文由 发表于 2023年2月23日 19:52:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/75544466.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定