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
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 HRESULT
s 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.