0

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.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Jerome
  • 15
  • 4
  • 3
    [Please do not upload images of code/data/errors.](//meta.stackoverflow.com/q/285551) – cafce25 Mar 26 '23 at 19:30
  • 1
    Why do you try to use the C++ documentation for a Rust function, you should at least look at the [Rust documentation](https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/UI/Shell/struct.IShellFolder.html#method.BindToObject) as well. The error seems pretty clear, `BindToObject` takes 2 type parameters, you only provide one. – cafce25 Mar 26 '23 at 20:01
  • I use it because it's the only documentation available for these functions. I have tried to provide 2 type parameters but without success – Jerome Mar 26 '23 at 20:07
  • What are you trying to use `BindToObject` to do? Why are you ignoring its return value? – Solomon Ucko Mar 26 '23 at 20:37
  • 1
    The generic `T` parameter is usually inferred from the type of the variable the result gets bound to, e.g. `let foo: IShellFolder = desktop_folder.BindToObject(&itemID_list, &ibindctx)?;`. Explicitly spelling out generic type parameters is rarely required with the `windows` crate. – IInspectable Mar 27 '23 at 07:26
  • Rust issues apart, IShellFolder is difficult to work with and most of the time you don't need to use PIDLs directly. What do you need to do in the first place? – Simon Mourier Mar 27 '23 at 09:02
  • 1
    I'm trying to convert PIDLs that I get when parsing windows registry into a displayname. Until now, I didn't found an other way to do this conversion in rust. As far I understand the windows documentation, BindToObject is right way to do this. If someone have a better solution, I'll will take it. – Jerome Mar 27 '23 at 10:22

2 Answers2

3

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"
]  
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • Yeah, that looks much easier. Would [my answer](https://stackoverflow.com/a/75855653/5445670) work though? (In case someone still has to support XP or older, I guess?) – Solomon Ucko Mar 27 '23 at 14:57
  • @SolomonUcko - your answer should work fine (if one can understand the meaning of SHGDNF... !). In fact, IShellItem should work on XP SP1, but I've not tested *that* (too old :-) – Simon Mourier Mar 27 '23 at 15:01
  • Thanks you so much for your solution! may ask another question. In my case I find myself with just a pidl in the form of int u16. Do you know a clean solution to create it? otherwise my solution is to create SHITEMID to initialize the struct ITEMIDLIST. – Jerome Mar 27 '23 at 18:46
  • @Jerome - a PIDL cannot be an uint 16. A PIDL is a pointer to a buffer in memory. This buffer is a list of SHITEMID (each with a variable size) https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/cc144089(v=vs.85)#item-id-lists You never "create" a PIDL. The folder who owns a shell item creates the relative (one segment) PIDL for this item. – Simon Mourier Mar 27 '23 at 21:33
  • ok, I'have followed the wrong way since the beginning... at least I have learned several things. To summarise the basic problem, I want extract a path from the windows HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSavePidlMRU. I didn't find the documentation but it seems that the value contains some idlist that can be converted with this function GetPathFromIDList that accept ITEMIDLIST and flag. If i'm true, and trust me i'm not very confident, there is a way to convert bytes into ITEMIDLIST... but which one ? – Jerome Mar 27 '23 at 23:30
  • @Jerome I'd recommend posting that as a new question. You can link between the two questions in either or both directions if you want. – Solomon Ucko Mar 27 '23 at 23:51
  • 1
    What you find under keys in HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSavePidl are indeed serialized PIDL (undocumented). They all start with 0x14001F50E04FD020EA3A6910A2D808002B30309D as first SHITEMID segment wich is the root (means they are absolute PIDLs). So you can load these into memory, and pass the pointer of this memory to SHCreateItemFromIDList. – Simon Mourier Mar 28 '23 at 06:30
  • Oh! and I'm wright the root is the Desktop folder (seen somewhere in MS Doc) ? I totally understand but I must admit that I'm stuck when you tell to loads these PIDLS into memory. I have try something like that `let raw : *mut SHITEMID = 0x14001F50E04FD020EA3A6910A2D808002B30309D;` but the number is to big (What is obvious). – Jerome Mar 28 '23 at 19:17
  • @Jerome - PIDLs are array of bytes of arbitrary size, I've added another sample that reads a pidl from the registry. – Simon Mourier Mar 29 '23 at 14:52
1

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())?
}
Solomon Ucko
  • 5,724
  • 3
  • 24
  • 45