Should I pass a mutable reference or transfer ownership of a variable in the context of FFI?

被刻印的时光 ゝ 提交于 2019-12-08 18:52:30

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!