2016-09-16 3 views
0

У меня есть программа, которая использует Windows API через C FFI (через winapi-rs). Одна из функций ожидает указатель на указатель на строку в качестве выходного параметра. Функция сохранит результат в этой строке. Я использую переменную типа WideCString для этой строки.Должен ли я передавать изменяемую ссылку или передавать право собственности на переменную в контексте FFI?

Может ли я «просто» пройти в изменяемом иом к иому на строку в эту функцию (внутри небезопасный блок), или я должен скорее использовать функциональные возможности, как .into_raw() и .from_raw(), который также перемещает собственность переменного к C функция?

Обе версии компилируются и работают, но мне интересно, покупаю ли я какие-либо недостатки с помощью прямого способа.

Вот соответствующие строки из моего кода, используя .into_raw и .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)}; 

description of this parameter in MSDN говорит:

Указатель на переменную, которая получает указатель на -завершённый строку дескриптора безопасности в нуль. Описание формата строки см. В разделе Security Descriptor String Format. Чтобы освободить возвращенный буфер, вызовите функцию LocalFree.

Я ожидаю, что функция изменит значение переменной. Разве это не означает, что я принимаю участие?

+1

Что указатель к указателю на строку в C в этом случае? «WCHAR **»?Что функция говорит, что это будет с этим связано? Возможно, вы можете сказать нам, какую функцию вы хотите назвать, чтобы мы могли найти ее в MSDN. –

+0

@Shepmaster Не очень, так как я до сих пор не знаю, что функция C * хочет *. –

+1

Краткая версия, показать код. –

ответ

3

Я ожидаю, что функция изменит значение переменной. Разве это не означает, что я принимаю участие?

Нет. Один ключевой способ подумать о собственности: кто несет ответственность за уничтожение ценности, когда вы закончите с этим.

Компетентные C API (и Microsoft обычно относится к этой категории) document ожидаемые правила владения, хотя иногда слова наклонны или предполагают некоторый уровень внешних знаний. Эта конкретная функция говорит:

Чтобы освободить возвращаемый буфер, вызовите функциюLocalFree.

Это означает, что ConvertSecurityDescriptorToStringSecurityDescriptorW собирается выполнить какое-то распределение и вернуть его пользователю. Проверяя объявление функции, вы также можете увидеть, что они документируют этот параметр как «вне» параметр:

_Out_ LPTSTR    *StringSecurityDescriptor, 

Почему это сделано именно так? Поскольку вызывающий абонент не знает, сколько памяти выделяется для хранения строки !

Обычно вы передавали ссылку на неинициализированную память в функцию, которая должна затем инициализировать ее для вас.

компилируется, но вы не предоставили достаточно контекста на самом деле назвать это, так что кто знает, если он работает:

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 = "*" 

Отвечая на ваши вопросы прямо:

Могу ли я «просто» передать в mutable ref ссылку на строку в эту функцию (внутри небезопасного блока) или мне лучше использовать такие функции, как .into_raw() и .from_r aw(), который также переносит право собственности на переменную на функцию C?

Ни то, ни другое. Функция не ожидает, что вы передадите ей указатель на строку , она хочет, чтобы вы передали указатель на место, где это может помещать строку.

Я также понял после вашего объяснения, что (насколько я понял) в моем примере переменная widestr никогда не перезаписывается функцией C. Он перезаписывает ссылку на нее, а не данные.

Весьма вероятно, что память, выделенная WideCString::from_str("test"), полностью просочилась, поскольку ничто не ссылается на этот указатель после вызова функции.

Это общее правило, что функция C (WinAPI) всегда будет выделять буфер самостоятельно (если не следовать двухэтапному подходу, когда он сначала возвращает размер)?

Я не верю, что есть любые общие правила между API, C или даже внутри в виде C API. Особенно в такой крупной компании, как Microsoft с такой большой поверхностью API. Вам необходимо прочитать документацию для каждого метода. Это часть постоянного сопротивления, которое может сделать запись C похожей на slog.

это как-то странно для меня передать неинициализированную память для такой функции.

Да, потому что на самом деле нет гарантии, что функция инициализирует его. На самом деле было бы расточительно инициализировать его в случае сбоя, поэтому, вероятно, этого не произойдет. Другое дело, что Rust, похоже, имеет более приятные решения.


Обратите внимание, что вы не должны делать вызовы функций (например, println!) перед вызовом таких вещей, как last_os_error; эти вызовы функций могут изменить значение последней ошибки!


Другие API для Windows на самом деле требует многоступенчатого процесса - вы вызываете функцию с NULL, она возвращает количество байтов, нужно выделить, то вы вызываете его

+0

Большое спасибо за ответ! Ваш код компилируется и работает отлично. Я также понял после вашего объяснения, что (насколько я понял) в моем примере переменная widestr никогда не перезаписывается функцией C. Он перезаписывает ссылку на нее, но не сами данные :-( Является ли это общим правилом, что функция C (WinAPI) всегда будет выделять буфер самостоятельно (если не следовать двухэтапному подходу, когда он сначала возвращает размер)? Я не могу с этим поделать, но мне почему-то кажется странным передавать неинициализированную память такой функции. – Norbert

+0

Могу ли я задать вопрос, связанный с этим? (Пожалуйста, дайте мне знать, если я открою другую тему). что я должен дважды вызвать функцию Windows API (где он сначала возвращает требуемый размер буфера) .Какой был бы лучший тип данных для использования? Я экспериментировал с Vector (и он работает), но следуя философии вашего anwer, может быть лучше использовать такую ​​функцию, как alloc :: heap :: allocate, чтобы просто адресовать память для FFI. – Norbert

Смежные вопросы