C++中的双重指针转换为void指针的含义是什么?

6

我正在尝试使用Windows-RS库和ADSI API,遵循微软发布的C++示例,从Rust中与Active Directory一起工作。但是,我不太理解这里正在发生什么:

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

他们创建了一个未初始化的IADs指针(从我的C#知识来看,它看起来像是一个接口),然后,在使用它时,他们有一个被转换为void的双重指针。我尝试在Rust中复制这个行为,但我认为我并没有完全理解发生了什么。到目前为止,我尝试了以下方法:
// 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(())
}

首先,我可能错误地创建了一个空指针,因为这不是示例中所做的,但问题在于Rust不允许使用未初始化的变量,所以我不确定等价物是什么。

其次,假设pADs变量是输出应该去的地方,但我不理解拥有者为空的对象有一个指针,然后是双重指针的交互。即使在Rust中可能存在这种情况,我觉得这不是我应该做的事情。

第三,一旦我通过FFI调用更新了指针,如何告诉Rust结果输出类型,以便我们可以进一步处理它?使用_无法工作,因为它是一个结构体,并且我有一种使用transmute是不好的感觉。

1个回答

7
指针参数常被用于外部函数接口(FFI),作为将数据返回给返回值的一种方法。这种想法是指针应该指向某些现有对象,调用将使用结果填充它。由于Windows API函数常常返回HRESULT来表示成功和失败,它们使用指针返回其他东西。
在这种情况下,ADsOpenObject要返回一个*void(所请求的ADs接口对象),因此你需要提供一个指向现有*void对象的指针以供其填充:
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 _,
);

我将 pads 更改为 *mut c_void 以简化演示并匹配 ADsOpenObject 参数。成功调用后,您可以将 pads 转换为您需要的任何内容。

关键区别在于将 pads 强制转换 vs &mut pads。之前的操作使得 ppadspads 的值相同,从而告诉函数结果应写入到 null,这是不好的。它将参数 指向 pads

未初始化和 null 的差异相当无足轻重,因为函数的目标是覆盖它们。


我猜还有两个问题;我看到其他 FFI 示例只使用单个指针作为输出对象,但在这些情况下,您需要传递一个指向初始化结构体的指针,该外部函数将其覆盖。因为这指向未初始化且实际上是抽象接口,可能无法实际初始化,所以他们为什么要使用双重指针?为什么不使用单个指针就足够了呢? - Dragoon
此外,我猜测 pADs 的输出意图是基于继承 IADs 的类的结构体,但如果在编译时无法完成,Rust 如何将其“马歇尔”成已知的结构体?(对于使用不恰当的术语表示歉意,我是 Rust 新手,几乎没有 C++ 经验) - Dragoon
其实现在想想,我甚至不知道你怎么告诉 Rust pADs 的类型是什么,似乎不能只是像 let foo = pads as IADsTypeBar 这样做。你真正该怎么做呢? - Dragoon
@Dragoon 如果结果对象是一个简单值或结构,则可以使用单指针输出参数,但在这种情况下,“IADs”结果表示一个接口,一个多态对象,只能通过间接方式使用。它不能是一个简单的结构,因为它可以表示多种类型,所以它必须是一个“*void”,因此参数必须是一个“**void”。 - kmdreko
C++ 多态性使用一个指向对象的 vtable 函数指针作为第一个字段。这是一种稳定的机制,因此 Rust 端可以手动实现多态调用(我不确定它实际上是否这样做,但肯定是可行的)。如果您知道 *IADs 的具体类型,则可以将其转换为 *IADsTypeBar - kmdreko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接