Binding a Pidl with function BindToObject?

192 Views Asked by At

Here is my Rust code:

use std::mem::ManuallyDrop;
use windows::core::ComInterface;
use windows::Win32::System::Com::*;
use windows::Win32::UI::Shell::Common::ITEMIDLIST;
use windows::{core::Result, Win32::UI::Shell::*};

struct Com;
impl Drop for Com {
    fn drop(&mut self) {
        unsafe { CoUninitialize() };
    }
}

struct Variant(VARIANT);
impl Drop for Variant {
    fn drop(&mut self) {
        unsafe {
            match self.0.Anonymous.Anonymous.vt {
                VT_BSTR => {
                    ManuallyDrop::drop(&mut ((*self.0.Anonymous.Anonymous).Anonymous.bstrVal))
                }
                VT_DISPATCH => {
                    ManuallyDrop::drop(&mut ((*self.0.Anonymous.Anonymous).Anonymous.pdispVal))
                }
                _ => (),
            }
            ManuallyDrop::drop(&mut self.0.Anonymous.Anonymous);
        }
    }
}

fn main() -> Result<()> {
    unsafe {
        CoInitialize(None)?;
        let _com = Com;
        //https://learn.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-createbindctx
        let ibindctx = CreateBindCtx(0u32).unwrap();
        let itemID_list = ITEMIDLIST::default();
        let desktop_folder = SHGetDesktopFolder()?;

        let pidl: [u16; 1] = [0x14]; // convert this into ITEMIDLIST

        desktop_folder.BindToObject::<&IBindCtx>(&itemID_list, &ibindctx)?;
    }
    Ok(())
}

When I try to compile, I have the following error:

image

with the following toml dependencies:

[dependencies.windows]  
version = "0.46"  
features = [  
"Win32_Foundation",  
"Win32_System_Com",  
"Win32_System_Ole",  
"Win32_UI_Shell", 
"Win32_UI_Shell_Common"
]  

I have tried to follow the following documentation from Microsoft:

https://learn.microsoft.com/en-us/windows/win32/shell/folder-info#using-the-ishellfolder-interface

The purpose of this code is to convert a know PIDL from a folder to a display name. Unfortunately, the documentation of the windows crate is not beginner friendly.

Can someone help me, please?

I have tried to follow the C++ documentation of Microsoft for this function, without success.

2

There are 2 best solutions below

9
Simon Mourier On BEST ANSWER

As I said in the comment, IShellFolder is an old and clunky interface that's not easy to work with. One of the newer interfaces that have been introduced since Windows Vista is IShellItem which offer a simple wrapper over IShellFolder and friends and ususally avoids "talking PIDL" directly which can be a real pain (absolute vs relative, etc.).

Here is some sample code that demonstrates how to use it if you already have an absolute PIDL:

use windows::{core::*, Win32::System::Com::*, Win32::UI::Shell::*};

fn main() -> Result<()> {
    unsafe {
        _ = CoInitialize(None)?;
        
        // get some pidl (here the pidl for c:\temp\rust for demonstration)
        let item: IShellItem = SHCreateItemFromParsingName(w!("c:\\temp\\rust"), None)?;
        let pidl = SHGetIDListFromObject(&item)?;
        
        // get a IShellItem from an absolute PIDL
        let other_item : IShellItem = SHCreateItemFromIDList(pidl)?;

        // get of IShellItem's display names
        // use SIGDN_NORMALDISPLAY for in-folder name
        let other_name = other_item.GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING)?.to_string()?;
        println!("{other_name}"); // prints c:\temp\rust obviously
        Ok(())
    }
}

And here is another code that reads a PIDL from the registry (note: a PIDL is a serialized array of bytes of arbitrary size composed of multiple segments, each segment being created and parsable only by the Shell folder which created it https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/cc144089(v=vs.85)#item-id-lists) and displays it's full name (it should corresponds to one of the file that was opened on your disk):

use ::core::ffi::*;
use windows::{
    core::*, Win32::System::Com::*, Win32::System::Registry::*, Win32::UI::Shell::Common::*,
    Win32::UI::Shell::*,
};

fn main() -> Result<()> {
    unsafe {
        _ = CoInitialize(None)?;

        let path = w!(
            "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU\\*"
        );

        let value = w!("0");

        // get some registry buffer.
        // step 1: get size
        let mut size = 0;
        SHRegGetValueW(
            HKEY_CURRENT_USER,
            path,
            value,
            SRRF_RT_REG_BINARY as i32,
            None,
            None,
            Some(&mut size),
        );

        // step 2: allocate & read buffer
        let mut buffer = vec![0u8; size as usize];
        SHRegGetValueW(
            HKEY_CURRENT_USER,
            path,
            value,
            SRRF_RT_REG_BINARY as i32,
            None,
            Some(buffer.as_mut_ptr() as *mut c_void),
            Some(&mut size),
        );

        // resolve this PIDL's absolute path
        let other_item: IShellItem =
            SHCreateItemFromIDList(buffer.as_mut_ptr() as *mut ITEMIDLIST)?;
        let other_name = other_item
            .GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING)?
            .to_string()?;
        println!("{other_name}");
        Ok(())
    }
}

Needs this in cargo.toml:

[dependencies.windows]  
features = [  
"Win32_Foundation",
"Win32_System_Com",  
"Win32_UI_Shell", 
"Win32_UI_Shell_Common",
"Win32_System_Registry"
]  
0
Solomon Ucko On

To get the display name, try using SHBindToParent (C, Rust), IShellFolder::GetDisplayNameOf (C, Rust), and StrRetToBSTR (C, Rust, more information about STRRET); something like this:

fn get_display_name_from_pidl(pidl: *const ITEMIDLIST, flags: SHGDNF) -> Result<String> {
    let pidl_last: *mut ITEMIDLIST = std::ptr::null_mut();
    let parent: IShellFolder = SHBindToParent(pidl, &mut pidl_last)?;

    let strret: STRRET = STRRET::default();
    parent.GetDisplayNameOf(pidl_last, flags, &mut strret)?;

    let bstr: BSTR = BSTR::new();
    StrRetToBSTR(&mut strret, Some(&pidl_last), &mut bstr)?;

    String::from_utf16(bstr.as_wide())?
}