简体   繁体   English

可以安全地将没有repr(C)的struct传递给C API吗?

[英]Safe to pass struct without repr(C) to C API?

New question per Shepmaster's advice in comments 根据Shepmaster的评论建议提出新问题

Suppose a library defines a struct without #[repr(C)] . 假设一个库定义了一个没有#[repr(C)] If the struct needs to be passed to a C API, is there a safe way to do it? 如果需要将结构传递给C API,有没有一种安全的方法呢? Should I be worried that the Rust compiler will change the memory layout of the struct in a way the C API doesn't expect? 我是否应该担心Rust编译器会以C API不期望的方式更改结构的内存布局?

I've seen some libraries do this. 我见过一些图书馆这样做。 In my experience, they use mem::transmute when it's time to pass the struct to a C function. 根据我的经验, 他们在将结构传递给C函数时使用mem::transmute Does transmute somehow eliminate the need for #[repr(C)] ? transmute是否会以某种方式消除#[repr(C)]

If I were the one defining the structs, I wouldn't be asking. 如果我是定义结构的人,我不会问。 I'd just add #[repr(C)] . 我只想添加#[repr(C)] The problem is that I want to use structs from libraries I don't control. 问题是我想使用我无法控制的库中的结构。

Old question 老问题

I've been using cgmath-rs , and I might switch to nalgebra if cgmath is being abandoned . 我一直在使用cgmath-rs ,如果cgmath 被放弃 ,我可能会切换到nalgebra

In the source of these two libraries, I don't see #[repr(C)] on the vector and matrix structs. 在这两个库的源代码中,我没有在向量和矩阵结构上看到#[repr(C)] So how are these libraries compatible with the OpenGL API? 那么这些库如何与OpenGL API兼容? OpenGL expects a certain memory layout when you pass it pointers to vectors and matrices. 当您将指针传递给向量和矩阵时,OpenGL需要一定的内存布局。 It's my understanding that without #[repr(C)] , the memory layout of Rust structs is undefined. 我的理解是,没有#[repr(C)] ,Rust结构的内存布局是未定义的。

In at least one example , I've seen mem::transmute being applied to these structs just before handing them to OpenGL. 至少在一个例子中 ,我看到mem::transmute在将它们交给OpenGL之前应用于这些结构。 I could be misunderstanding transmute , but that function would seem to preserve the memory layout of the struct. 我可能会误解transmute ,但该函数似乎保留了struct的内存布局。 So if the layout is wrong to begin with, it's still wrong after transmute , correct? 所以,如果布局是错误的,首先,它仍然是错误的之后transmute ,是否正确?

I've also considered vecmath . 我也考虑过vecmath But it appears that, by design, vecmath lacks helper functions to generate rotation matrices and such. 但看起来,根据设计,vecmath缺乏辅助函数来生成旋转矩阵等。 True, I could implement those, but it would be nice not to have to. 没错,我可以实现这些,但不必这样做会很好。 In any case, does vecmath's design avoid the problem of memory layout by using arrays instead of structs? 无论如何,vecmath的设计是否通过使用数组而不是结构来避免内存布局的问题?

I've seen some libraries do this. 我见过一些图书馆这样做。 In my experience, they use mem::transmute when it's time to pass the struct to a C function. 根据我的经验,他们在将结构传递给C函数时使用mem :: transmute。 Does transmute somehow eliminate the need for #[repr(C)]? 转化是否会以某种方式消除#[repr(C)]的需要?

No. 没有。

...but it's complicated. ......但它很复杂。

If you have a &Foo, and you pass it to C and do not edit it at all , then it's perfectly valid to convert your &Foo to a *const c_void and pass it to ac call, something like: 如果你有一个&Foo,并且你将它传递给C并且根本不编辑它 ,那么将你的&Foo转换为* const c_void并将其传递给ac调用是完全有效的,例如:

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

You may see people doing this as one step, using transmute; 你可能会看到人们这样做是一步,使用transmute;

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

...but it's important to understand the transmute call does not modify the memory layout; ...但重要的是要理解转化调用不会修改内存布局; but it does consume values . 但它确实消耗了价值

So for example, this code may result in a segfault later: 例如,此代码可能会导致以后出现段错误:

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

This is because the pointer &foo points to foo; 这是因为指针&foo指向foo; but foo stops existing after the scope ends; 但是在范围结束后foo停止存在; so if it gets used later (eg. ffi call keeps a reference) a segfault will result. 因此,如果稍后使用(例如,ffi调用保持引用),将导致段错误。

You might think that boxing (ie. move to the heap) fixes this: 你可能会认为拳击(即移动到堆)修复了这个问题:

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

...but it does not; ......但它没有; because when the Box leaves scope it is dropped. 因为当Box离开范围时,它会被丢弃。 However, because transmute moves values; 但是,因为转化会改变价值; this code safely moves the foo instance into the ffi call for use at any later time; 此代码安全地将foo实例移动到ffi调用中以供以后使用; note though that this is a memory leak if you don't recover the value later: 请注意,如果以后不恢复该值,则会出现内存泄漏:

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

...but no use of transmute() will solve the absence of repr(C), and yes, you can expect rust to mess around with your struct layout; ...但是没有使用transmute()可以解决repr(C)的缺失,是的,你可以期待生锈与你的结构布局混乱; this is usually to do with drop flags, and may be resolved as part of https://github.com/rust-lang/rfcs/pull/320 , since drop flags are currently the only tangible different in memory layout to structs; 这通常与drop标志有关,并且可以作为https://github.com/rust-lang/rfcs/pull/320的一部分解决,因为drop flags目前是内存布局到结构的唯一有形的不同; but since it's not explicitly covered in that RFC, I wouldn't hold my breath. 但由于RFC没有明确涵盖,我不会屏住呼吸。

ie. 即。 tldr; tldr; If you need to pass a struct and modify it in C, it needs repr(C); 如果你需要传递一个结构并在C中修改它,它需要repr(C); if it doesn't, it won't work**. 如果没有,它将无法工作**。

Transmute is used for other reasons, totally unrelated to this. 转化被用于其他原因,完全与此无关。

** --> well, it may work, but what you're really hitting is undefined behaviour. ** - >好吧,它可能有效,但你真正打的是未定义的行为。 If the libraries you're using work, it's probably because it happens to work. 如果您正在使用的库工作,可能是因为它恰好起作用。 This is true of many things in rust; 生锈很多东西都是如此; eg. 例如。 mutable aliasing in certain circumstances... but it really means broken code. 在某些情况下可变别名...但它确实意味着破坏了代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM