我应该在FFI中传递可变引用或转让变量的所有权吗? [英] Should I pass a mutable reference or transfer ownership of a variable in the context of FFI?

查看:130
本文介绍了我应该在FFI中传递可变引用或转让变量的所有权吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个程序,该程序通过C FFI(通过winapi-rs)利用Windows API。函数之一期望将指向字符串的指针作为输出参数。该函数将其结果存储到此字符串中。我正在为该字符串使用类型为 WideCString 的变量。



我可以只是将可变引用传递给对字符串的引用到此函数中(在不安全的块内),还是应该使用<$这样的功能? c $ c> .into_raw()和 .from_raw()也会将变量的所有权移到C函数吗?



两个版本都可以编译和运行,但是我想知道是否要直接购买任何缺点。



这是我代码中使用 .into_raw .from_raw 的相关行。

  let mut widestr:WideCString = WideCString :: from_str( test)。unwrap(); //这是应该存储结果的字符串
let mut security_descriptor_ptr:winnt :: LPWSTR = widestr.into_raw();

让rtrn3 =不安全{
advapi32 :: ConvertSecurityDescriptorToStringSecurityDescriptorW(sd_buffer.as_mut_ptr()as * mut std :: os :: raw :: c_void,
1,
winnt :: DACL_SECURITY_INFORMATION,
& mut security_descriptor_ptr,
ptr :: null_mut())

};

如果rtrn3 == 0 {
匹配IOError :: last_os_error()。raw_os_error(){
Some(1008)=> println!(需要在get_acl_of_file中修复此错误。),//不执行任何操作。不知道为什么会发生此错误
Some(e)=>紧急!( get_acl_of_file {}中未知的操作系统错误,e),
None =>恐慌!(那不应该在get_acl_of_file中发生!),
}
}

let mut rtr:WideCString =不安全{WideCString :: from_raw(security_descriptor_ptr)}};

在MSDN中对该参数的说明说:


指向变量的指针,该变量接收指向以 null 结尾的安全描述符字符串的指针。有关字符串格式的说明,请参见安全描述符字符串格式。要释放返回的缓冲区,请调用 LocalFree 函数。


我期望该函数更改变量的值。

解决方案


我是不是,按照定义,这意味着我正在转移所有权?期望函数更改变量的值。并非每个定义都意味着我要转移所有权吗?


否。思考所有权的一种关键方法是:谁负责在完成使用后破坏价值。



有能力的C API(并且Microsoft通常属于此类) document 预期的所有权规则,尽管有时这些词是倾斜的或假定某种程度的外部知识。这个特定的函数说:


要释放返回的缓冲区,请调用 LocalFree 函数。


这意味着 ConvertSecurityDescriptorToStringSecurityDescriptorW 将执行某种分配并将其返回给用户。检出函数声明,您还可以看到它们将该参数记录为 out参数:

  _Out_ LPTSTR * StringSecurityDescriptor,

为什么这样做?因为调用者不知道要分配多少内存来存储字符串 1



通常,您会将对未初始化内存的引用传递给该函数,然后该函数必须为您进行初始化。



这可以编译,但是您没有提供足够的上下文来实际调用它,所以谁知道它是否有效:

 外部板条箱advapi32; 
extern crate winapi;
外部板条箱宽字符串;

使用std :: {mem,ptr,io};
使用winapi :: {winnt,PSECURITY_DESCRIPTOR};
使用widestring :: WideCString;

fn foo(sd_buffer:PSECURITY_DESCRIPTOR)-> WideCString {
let mut security_descriptor =不安全{mem :: uninitialized()};

let retval =不安全{
advapi32 :: ConvertSecurityDescriptorToStringSecurityDescriptorW(
sd_buffer,
1,
winnt :: DACL_SECURITY_INFORMATION,
& mut security_descriptor,
ptr :: null_mut()

};

if retval == 0 {
match io :: Error :: last_os_error()。raw_os_error(){
Some(1008)=> println!(需要在get_acl_of_file中修复此错误。),//不执行任何操作。不知道为什么会发生此错误
Some(e)=>紧急!( get_acl_of_file {}中出现未知的操作系统错误,e),
None =>恐慌!(那不应该在get_acl_of_file中发生!),
}
}

不安全{WideCString :: from_raw(security_descriptor)}
}

fn main(){
让x = foo(ptr :: null_mut());
println!( {:?},x);
}



  [依赖关系] 
winapi = {git = https://github.com/nils-tekampe/winapi-rs/,rev = 1bb62e2c22d0f5833cfa9eec1db2c9cfc2a4a303}
advapi32-sys = {git = https://github.com/ nils-tekampe / winapi-rs /,rev = 1bb62e2c22d0f5833cfa9eec1db2c9cfc2a4a303}
widestring = *






直接回答您的问题:


我可以只是通过可变的ref到字符串的ref到此函数中(在不安全的块内),还是我应该使用.into_raw()和.from_raw()之类的功能,也将变量的所有权移至C函数?


都不是。该函数不希望您向其传递指向 string 的指针,而是希望向其传递指向 it 可以放置字符串的位置的指针。


在您的解释之后,我也才意识到(据我所知)在我的示例中,widestr变量永远不会被C函数覆盖。


很可能由 WideCString分配的内存会覆盖它的引用。 :: from_str( test)完全泄漏,因为函数调用后没有对该指针的引用。


这是C(WinAPI)函数始终始终自行分配缓冲区的通用规则吗(如果不遵循首先返回大小的两步方法)?


我不认为C API之间甚至内部的C API。特别是在像Microsoft这样大的公司,拥有如此多的API表面。您需要阅读每种方法的文档。这是不断增加的阻力的一部分,它可以使编写C感觉像是一个口号。


是的,因为实际上并不能保证该函数将其初始化。实际上,在失败的情况下初始化它会很浪费,因此可能不会。 Rust似乎为它提供了更好的解决方案。






请注意,您不应该执行函数调用(例如 println!),然后再调用 last_os_error 之类的东西;这些函数调用可能会更改上一个错误的值!






1 其他Windows API实际上需要一个多步骤过程-您使用 NULL 调用该函数,它返回需要分配的字节数,然后再次调用


I have a program that utilizes a Windows API via a C FFI (via winapi-rs). One of the functions expects a pointer to a pointer to a string as an output parameter. The function will store its result into this string. I'm using a variable of type WideCString for this string.

Can I "just" pass in a mutable ref to a ref to a string into this function (inside an unsafe block) or should I rather use a functionality like .into_raw() and .from_raw() that also moves the ownership of the variable to the C function?

Both versions compile and work but I'm wondering whether I'm buying any disadvantages with the direct way.

Here are the relevant lines from my code utilizing .into_raw and .from_raw.

let mut widestr: WideCString = WideCString::from_str("test").unwrap(); //this is the string where the result should be stored
let mut security_descriptor_ptr: winnt::LPWSTR = widestr.into_raw();

let rtrn3 = unsafe {
    advapi32::ConvertSecurityDescriptorToStringSecurityDescriptorW(sd_buffer.as_mut_ptr() as *mut std::os::raw::c_void,
                                    1, 
                                    winnt::DACL_SECURITY_INFORMATION,
                                    &mut security_descriptor_ptr,
                                        ptr::null_mut())

};

if rtrn3 == 0 {
    match IOError::last_os_error().raw_os_error() {
        Some(1008) => println!("Need to fix this errror in get_acl_of_file."), // Do nothing. No idea, why this error occurs
        Some(e) => panic!("Unknown OS error in get_acl_of_file {}", e),
        None => panic!("That should not happen in get_acl_of_file!"),
    }
}

let mut rtr: WideCString = unsafe{WideCString::from_raw(security_descriptor_ptr)};

The description of this parameter in MSDN says:

A pointer to a variable that receives a pointer to a null-terminated security descriptor string. For a description of the string format, see Security Descriptor String Format. To free the returned buffer, call the LocalFree function.

I am expecting the function to change the value of the variable. Doesn't that - per definition - mean that I'm moving ownership?

解决方案

I am expecting the function to change the value of the variable. Doesn't that - per definition - mean that I'm moving ownership?

No. One key way to think about ownership is: who is responsible for destroying the value when you are done with it.

Competent C APIs (and Microsoft generally falls into this category) document expected ownership rules, although sometimes the words are oblique or assume some level of outside knowledge. This particular function says:

To free the returned buffer, call the LocalFree function.

That means that the ConvertSecurityDescriptorToStringSecurityDescriptorW is going to perform some kind of allocation and return that to the user. Checking out the function declaration, you can also see that they document that parameter as being an "out" parameter:

_Out_ LPTSTR               *StringSecurityDescriptor,

Why is it done this way? Because the caller doesn't know how much memory to allocate to store the string 1!

Normally, you'd pass a reference to uninitialized memory into the function which must then initialize it for you.

This compiles, but you didn't provide enough context to actually call it, so who knows if it works:

extern crate advapi32;
extern crate winapi;
extern crate widestring;

use std::{mem, ptr, io};
use winapi::{winnt, PSECURITY_DESCRIPTOR};
use widestring::WideCString;

fn foo(sd_buffer: PSECURITY_DESCRIPTOR) -> WideCString {
    let mut security_descriptor = unsafe { mem::uninitialized() };

    let retval = unsafe {
        advapi32::ConvertSecurityDescriptorToStringSecurityDescriptorW(
            sd_buffer,
            1,
            winnt::DACL_SECURITY_INFORMATION,
            &mut security_descriptor,
            ptr::null_mut()
        )
    };

    if retval == 0 {
        match io::Error::last_os_error().raw_os_error() {
            Some(1008) => println!("Need to fix this errror in get_acl_of_file."), // Do nothing. No idea, why this error occurs
            Some(e) => panic!("Unknown OS error in get_acl_of_file {}", e),
            None => panic!("That should not happen in get_acl_of_file!"),
        }
    }

    unsafe { WideCString::from_raw(security_descriptor) }
}

fn main() {
    let x = foo(ptr::null_mut());
    println!("{:?}", x);
}

[dependencies]
winapi = { git = "https://github.com/nils-tekampe/winapi-rs/", rev = "1bb62e2c22d0f5833cfa9eec1db2c9cfc2a4a303" }
advapi32-sys = { git = "https://github.com/nils-tekampe/winapi-rs/", rev = "1bb62e2c22d0f5833cfa9eec1db2c9cfc2a4a303" }
widestring = "*"


Answering your questions directly:

Can I "just" pass in a mutable ref to a ref to a string into this function (inside an unsafe block) or should I rather use a functionality like .into_raw() and .from_raw() that also moves the ownership of the variable to the C function?

Neither. The function doesn't expect you to pass it a pointer to a string, it wants you to pass a pointer to a place where it can put a string.

I also just realized after your explanation that (as far as I understood it) in my example, the widestr variable never gets overwritten by the C function. It overwrites the reference to it but not the data itself.

It's very likely that the memory allocated by WideCString::from_str("test") is completely leaked, as nothing has a reference to that pointer after the function call.

Is this a general rule that a C (WinAPI) function will always allocate the buffer by itself (if not following the two step approach where it first returns the size)?

I don't believe there are any general rules between C APIs or even inside of a C API. Especially at a company as big as Microsoft with so much API surface. You need to read the documentation for each method. This is part of the constant drag that can make writing C feel like a slog.

it somehow feels odd for me to hand over uninitialized memory to such a function.

Yep, because there's not really a guarantee that the function initializes it. In fact, it would be wasteful to initialize it in case of failure, so it probably doesn't. It's another thing that Rust seems to have nicer solutions for.


Note that you shouldn't do function calls (e.g. println!) before calling things like last_os_error; those function calls might change the value of the last error!


1 Other Windows APIs actually require a multistep process - you call the function with NULL, it returns the number of bytes you need to allocate, then you call it again

这篇关于我应该在FFI中传递可变引用或转让变量的所有权吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆