c++rustadsi

(Rust question) C++ double pointer to void meaning


I'm trying to work with active directory from Rust by following the c++ examples Microsoft posts for the ADSI API and the Windows-RS crate. I'm not understanding quite what is going on here:

https://learn.microsoft.com/en-us/windows/win32/api/adshlp/nf-adshlp-adsopenobject

They create an uninitialized pointer to IADs (drawing from my c# knowledge, it looks like an interface) then, when it comes time to use it, they have a double pointer that is cast as void. I tried to replicate this behavior in Rust, but I'm thinking I'm just not understanding exactly what is happening. This is what I've tried so far:

// bindings omitted
use windows::Interface;
use libc::c_void;
 
fn main() -> windows::Result<()> {
        let mut pads: *mut IADs = ptr::null_mut();
        let ppads: *mut *mut c_void = pads as _;

        unsafe {
            let _ = CoInitialize(ptr::null_mut());
            let mut ldap_root: Vec<u16> = "LDAP://rootDSE\0".encode_utf16().collect();
            let hr = ADsOpenObject(
                ldap_root.as_mut_ptr() as _,
                ptr::null_mut(),
                ptr::null_mut(),
                ADS_AUTHENTICATION_ENUM::ADS_SECURE_AUTHENTICATION.0 as _,
                & IADs::IID,
                ppads,
            );

            if !hr.is_err() {
                ...
            }

        }
   
    Ok(())
}

First, I'm probably wrong to be creating a null pointer because that's not what they're doing in the example, but the problem is that rust doesn't permit the use of an uninitialized variable, so I'm not sure what the equivalent is.

Second, presumably the pADs variable is where the output is supposed to go, but I'm not understanding the interaction of having a pointer, then a double pointer, to an object that doesn't have an owner. Even if that were possible in rust, I get the feeling that it's not what I'm supposed to do.

Third, once I have the pointer updated by the FFI call, how do I tell Rust what the resulting output type is so that we can do more work with it? Doing as _ won't work because it's a struct, and I have a feeling that using transmute is bad


Solution

  • Pointer parameters are often used in FFIs as a way to return data alongside the return value itself. The idea is that the pointer should point to some existing object that the call will populate with the result. Since the Windows API functions often return HRESULTs to indicate success and failure, they use pointers to return other stuff.

    In this case, the ADsOpenObject wants to return a *void (the requested ADs interface object), so you need to give it a pointer to an existing *void object for it to fill:

    let mut pads: *mut c_void = std::ptr::null_mut();
    let ppads = &mut pads as *mut *mut c_void;
    
    // or inferred inline
    
    let hr = ADsOpenObject(
        ...
        &mut pads as _,
    );
    

    I changed pads to *mut c_void to simplify this demonstration and match the ADsOpenObject parameters. After a successful call, you can cast pads to whatever you need.

    The key difference is casting pads vs &mut pads. What you were doing before was making ppads the same value as pads and thus telling the function that the *void result should be written at null. No good. This makes the parameter point to pads instead.

    And the uninitialized vs null difference is fairly moot because the goal of the function is to overwrite it anyways.