6

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

Dragoon
  • 723
  • 6
  • 13

1 Answers1

7

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.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • Two additional questions I guess; I've seen other FFI examples where they just use a single pointer for the output object, though in those cases you pass a pointer to an initialized struct that the foreign function overwrites. Because this is pointing to an uninitialized and indeed abstract interface that presumably can't actually be initialized, is that why they use a double pointer? And why wouldn't a single pointer suffice? – Dragoon Feb 11 '21 at 16:24
  • Also, I'm guessing that the output for pADs is intended to be a struct that is based on a class that inherits IADs, but if that can't be done at compile time, how does rust "marshall" that into a known struct? (apologies for using bad terms here, I'm new to rust and have almost no experience with c++) – Dragoon Feb 11 '21 at 16:29
  • Actually come to think of it, I don't even know how you would tell rust what type pADs actually is, it seems you can't just do let foo = pads as IADsTypeBar. How do you actually do that? – Dragoon Feb 11 '21 at 17:47
  • @Dragoon Single pointer output parameters can be used if the result object is a simple value or structure, however in this case the `IADs` result represents an interface, a polymorphic object, which can only be used via indirection. It *can't* be a simple structure since it can represent more than one type, so it must be a `*void`, so the parameter must be a `**void`. – kmdreko Feb 11 '21 at 23:27
  • C++ polymorphism uses a pointer to a vtable of functions as the first field of an object. This is a stable mechanism so the Rust side can implement the polymorphic calls manually (I'm not 100% sure that's actually what it does, but its certainly feasible). If you know the concrete type of the `*IADs`, you can cast it to `*IADsTypeBar`. – kmdreko Feb 11 '21 at 23:28