【问题标题】:Safe to pass struct without repr(C) to C API?将没有 repr(C) 的结构安全地传递给 C API?
【发布时间】:2015-03-31 02:12:38
【问题描述】:

根据 Shepmaster 在 cmets 中的建议提出新问题

假设一个库定义了一个没有#[repr(C)] 的结构。如果需要将结构传递给 C API,有没有安全的方法呢?我是否应该担心 Rust 编译器会以 C API 不期望的方式更改结构的内存布局?

我见过一些图书馆这样做。根据我的经验,they use mem::transmute 是时候将结构传递给 C 函数了。 transmute 是否以某种方式消除了对 #[repr(C)] 的需求?

如果我是定义结构的人,我就不会问了。我只想添加#[repr(C)]。问题是我想使用我无法控制的库中的结构。

老问题

我一直在使用cgmath-rs,如果cgmath 是being abandoned,我可能会切换到nalgebra

在这两个库的源代码中,我没有在向量和矩阵结构上看到#[repr(C)]。那么这些库如何与 OpenGL API 兼容呢?当您将指针传递给向量和矩阵时,OpenGL 需要特定的内存布局。据我了解,没有#[repr(C)],Rust 结构的内存布局是不确定的。

at least one example 中,我看到mem::transmute 在将它们交给OpenGL 之前被应用于这些结构。我可能会误解transmute,但该函数似乎保留了结构的内存布局。所以如果一开始布局错了,transmute之后还是错了,对吧?

我也考虑过vecmath。但看起来,通过设计,vecmath 缺乏生成旋转矩阵等的辅助函数。没错,我可以实现这些,但不必这样做会很好。无论如何,vecmath 的设计是否通过使用数组而不是结构体来避免内存布局的问题?

【问题讨论】:

  • “这些库如何与 OpenGL API 兼容?”您的 cgmath-rs 链接已失效,但我在 nalgebra 的网站上没有看到任何声称它声称与 OpenGL 兼容的内容。你能链接到表明它们应该是的东西吗?
  • 谢谢。我修复了链接。不,我没有看到任何明确说明它们与 OpenGL 兼容的内容。但是,两者都明确地将自己标榜为“图形”。此外,nalgebra cites its usage in kiss3d,一个基于 OpenGL 的引擎。所以至少代数非常强烈地暗示着兼容性。
  • 只需报告一个针对该存储库的错误,IMO。不用问我们了:)
  • 我不相信这是一个错误,实际上。我问是因为我是 Rust 的新手,而且我不确定我比这些库的作者更了解。因此,我在问我的分析是否正确。
  • 您可能希望重新表述您的问题,使其更笼统。您有使用 OpenGL API 的特定示例,但从概念上讲,您的问题更接近于“转换非 repr(c) 结构并将其传递给 c 函数是否安全”。这可能会吸引一些不熟悉 OpenGL,但通常使用 FFI 的人。

标签: opengl rust


【解决方案1】:

我见过一些图书馆这样做。根据我的经验,当需要将结构传递给 C 函数时,他们会使用 mem::transmute。 transmute 是否以某种方式消除了对 #[repr(C)] 的需求?

没有。

...但它很复杂。

如果你有一个 &Foo,并且你将它传递给 C 并且根本不编辑它,那么将你的 &Foo 转换为 *const c_void 并将它传递给 ac 调用是完全有效的,类似:

let fp = &foo as *const Foo as *const c_void

您可能会看到人们使用 transmute 将其作为一个步骤来完成;

unsafe { ffi_call(transmute(&foo), ...) }

...但重要的是要了解 transmute 调用不会修改内存布局;但它确实消耗价值

因此,例如,此代码以后可能会导致段错误:

{
  let foo = Foo { ... }
  unsafe { ffi_call(&foo as _ as *const c_void); }
}

这是因为指针 &foo 指向 foo;但是 foo 在范围结束后停止存在;因此,如果它稍后被使用(例如,ffi 调用保留引用)将导致段错误。

您可能认为装箱(即移动到堆)解决了这个问题:

let foo = Box::new(Foo { ... })
unsafe { ffi_call(&*foo as *const c_void); }

...但事实并非如此;因为当 Box 离开范围时,它会被丢弃。但是,因为 transmute 移动值;此代码安全地将 foo 实例移动到 ffi 调用中以供以后使用;请注意,如果您稍后不恢复该值,则这是内存泄漏:

let foo = Box::new(Foo { ... })
unsafe { ffi_call(transmute(foo)); }

...但是不使用 transmute() 将解决没有 repr(C) 的问题,是的,您可以期望 rust 会弄乱您的结构布局;这通常与丢弃标志有关,可能作为https://github.com/rust-lang/rfcs/pull/320 的一部分解决,因为丢弃标志目前是内存布局与结构的唯一明显不同;但由于该 RFC 没有明确涵盖,我不会屏住呼吸。

即。 tldr;如果你需要传递一个结构并在C中修改它,它需要repr(C);如果没有,它将无法工作**。

Transmute 用于其他原因,与此完全无关。

** --> 好吧,它可能会起作用,但你真正遇到的是未定义的行为。如果您正在使用的库可以正常工作,那可能是因为它恰好可以正常工作。生锈的许多东西都是如此。例如。在某些情况下可变别名......但这确实意味着代码损坏。

【讨论】:

  • 完美。那是我的预感。我会看看是否可以将 repr(C) 添加到这些库中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多