问题描述
我有一个程序,该程序通过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)}};
说:
我期望该函数更改变量的值。
否。思考所有权的一种关键方法是:谁负责在完成使用后破坏价值。
有能力的C API(并且Microsoft通常属于此类) document 预期的所有权规则,尽管有时这些词是倾斜的或假定某种程度的外部知识。这个特定的函数说:
这意味着 ConvertSecurityDescriptorToStringSecurityDescriptorW
将执行某种分配并将其返回给用户。检出函数声明,您还可以看到它们将该参数记录为 out参数:
_Out_ LPTSTR * StringSecurityDescriptor,
为什么这样做?因为调用者不知道要分配多少内存来存储字符串!
通常,您会将对未初始化内存的引用传递给该函数,然后该函数必须为您进行初始化。
这可以编译,但是您没有提供足够的上下文来实际调用它,所以谁知道它是否有效:
外部板条箱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 = *
直接回答您的问题:
都不是。该函数不希望您向其传递指向 string 的指针,而是希望向其传递指向 it 可以放置字符串的位置的指针。
很可能由 WideCString分配的内存会覆盖它的引用。 :: from_str( test)
完全泄漏,因为函数调用后没有对该指针的引用。
我不认为C API之间甚至内部的C API。特别是在像Microsoft这样大的公司,拥有如此多的API表面。您需要阅读每种方法的文档。这是不断增加的阻力的一部分,它可以使编写C感觉像是一个口号。
是的,因为实际上并不能保证该函数将其初始化。实际上,在失败的情况下初始化它会很浪费,因此可能不会。 Rust似乎为它提供了更好的解决方案。
请注意,您不应该执行函数调用(例如 println!
),然后再调用 last_os_error
之类的东西;这些函数调用可能会更改上一个错误的值!
其他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:
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:
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 !
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:
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.
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.
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.
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!
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中传递可变引用或转让变量的所有权吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!