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

1

1

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?

Contumelious answered 16/9, 2016 at 15:5 Comment(8)
What's a pointer to a pointer to a string in C in this case? A WCHAR**? What does the function say it's going to do with this? Maybe you can tell us which function you want to call so we can look it up in MSDN.Polyp
@Shepmaster Not really, since I still don't know what the C side function wants.Polyp
Short version, show some code.Polyp
I just added a snippet from my code (the one version). 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.Contumelious
@Shepmaster I am expecting the function to change the value of the variable. Doesn't that - per definition - mean that I'm moving ownership?Contumelious
Read up on providing an minimal reproducible example.Remediless
ConvertSecurityDescriptorToStringSecurityDescriptorW isn't even exposed by that library!Remediless
@Remediless True, the master branch of the crate does not contain this function yet. So, I compile the crate locally and add code from this PR github.com/retep998/winapi-rs/pull/315 (which also includes this function). This however, makes it a little bit tricky for me to provide a minimal example that really compiles. I can for sure add a main function to my snippet and the definition for the other buffer but the code wont compile with the "public" version of winapi-rsContumelious
R
4

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

Remediless answered 16/9, 2016 at 16:12 Comment(2)
Thanks a lot for the answer! Your code compiles and runs fine. 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 :-( 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 can't help but it somehow feels odd for me to hand over uninitialized memory to such a function.Contumelious
Can I ask a question that is related to this? (please let me know if I should open another topic). For the case that I have to call a Windows API function twice (where it returns the required size of the buffer first). What would be the best datatype to use? I experimented with a Vector (and it works) but following the philosophy of your anwer, it may be better to use a function like alloc::heap::allocate to simply address memory for the FFI.Contumelious

© 2022 - 2024 — McMap. All rights reserved.