views:

19

answers:

0

Solution Found, See Below

Hi,

I have a situation where I want to perform special processing on some Windows shell special folders (those corresponding to values in the CSIDL enum.) (My solution must be WinXP compatible.) The problem I'm having is that when I encounter IShellFolders as I work my way down the heirarchy, I cannot find a way to match up the IShellFolders to their CSIDL.

This is my current approach:

Initialize a static one-to-one data structure (csidlToFromFullPIdl) of all CSIDLs to their pIDLs returned by SHGetSpecialFolderLocation.

foreach (CSIDL csidl in Enum.GetValues(typeof(CSIDL))
{
    IntPtr fullPIdl = IntPtr.Zero;
    int hResult = ShellApi.SHGetSpecialFolderLocation(IntPtr.Zero, csidl, ref fullPIdl);
    if (hResult != 0)
        Marshal.ThrowExceptionForHR(hResult);
    csidlToFromFullPIdl.Add(csidl, fullPIdl);
}

Start the heirarchy with the Desktop IShellFolder:

int hResult = ShellApi.SHGetDesktopFolder(ref _shellFolder);
hResult = ShellApi.SHGetSpecialFolderLocation(IntPtr.Zero, CSIDL.CSIDL_DESKTOP, ref _fullPIdl);

Retrieve children like so:

hResult = _shellFolder.EnumObjects(IntPtr.Zero, SHCONTF.SHCONTF_FOLDERS, out pEnum);

// Then repeatedly call:
pEnum.Next(1, out childRelativePIdl, out numberGotten);

Construct new fully-qualified pIDLs for the children like so:

_fullPIdl = ShellApi.ILCombine(parentFullPIdl, childRelativePIdl);

(Finally, retrieve the IShellFolder for the child using:)

hResultUint = parentShellItem.ShellFolder.BindToObject(childRelativePIdl, IntPtr.Zero, ShellApi.IID_IShellFolder, out _shellFolder);

The problem I'm having is that neither the childRelativePIdl nor the _fullPIdl correspond to any pIDLs in csidlToFromFullPIdl.

TIA.

FYI on Vista machines the GUID corresponding to KNOWNFOLDERIDs may be a solution (but not for me as I must be WinXP compatible.)

I should also say that I think using the paths of the special folders (via SHGetSpecialFolderPath) is insufficient because several of the special folders in which I'm interested do not have paths. (E.g., CSIDL_DRIVES and CSIDL_NETWORK.)


I'm trying two approaches to this. The first is to use SHGetDataFromIDList to retrieve the Clsid, which I can then compare to known Clsids:

public static Guid GetClsidFromFullPIdl(IntPtr fullPIdl)
{
    // Get both parent's IShellFolder and pIDL relative to parent from full pIDL
    IntPtr pParentShellFolder;
    IntPtr relativePIdl = IntPtr.Zero;
    int hResultInt = ShellApi.SHBindToParent(fullPIdl, ShellGuids.IID_IShellFolder, out pParentShellFolder, ref relativePIdl);
    if (hResultInt != (int)HRESULT.S_OK)
        Marshal.ThrowExceptionForHR(hResultInt);
    object parentShellFolderObj = System.Runtime.InteropServices.Marshal.GetTypedObjectForIUnknown(
        pParentShellFolder, typeof(IShellFolder));
    IShellFolder parentShellFolder = (IShellFolder)parentShellFolderObj;

    SHDESCRIPTIONID descriptionId = new SHDESCRIPTIONID();
    IntPtr pDescriptionId = MarshalToPointer(descriptionId);
    // Next line returns hResult corresponding to NotImplementedException
    hResultInt = ShellApi.SHGetDataFromIDList(parentShellFolder, ref relativePIdl, SHGDFIL.SHGDFIL_DESCRIPTIONID, pDescriptionId,
        Marshal.SizeOf(typeof(SHDESCRIPTIONID)));
    if (hResultInt != (int)HRESULT.S_OK)
        Marshal.ThrowExceptionForHR(hResultInt);

    if (parentShellFolder != null)
        Marshal.ReleaseComObject(parentShellFolder);

    return descriptionId.Clsid;
}

static IntPtr MarshalToPointer(object data)
{
    IntPtr pData = Marshal.AllocHGlobal(Marshal.SizeOf(data));
    Marshal.StructureToPtr(data, pData, false);
    return pData;
}

The problem with this approach is that the call to SHGetDataFromIDList returns an hResult that corresponds to throwing a NotImplementedException. Does this mean that SHGetDataFromIDList is unavailable on my system? (WinXP SP3.)

My second approach is to compare the item identifier lists referenced by two pointers to item identifier lists and see if they are equal. I'm implementing a technique coded here in C. This is what I have so far:


Solution

static bool pIdlsAreEquivalent(IntPtr pIdl1, IntPtr pIdl2)
{
    if (pIdl1 == pIdl2) return true;
    if (pIdl1 == IntPtr.Zero || pIdl2 == IntPtr.Zero) return false;
    int pIdl1Size = GetItemIDSize(pIdl1);
    if (pIdl1Size != GetItemIDSize(pIdl2)) return false;
    byte[] byteArray1 = new byte[pIdl1Size];
    byte[] byteArray2 = new byte[pIdl1Size];
    Marshal.Copy(pIdl1, byteArray1, 0, pIdl1Size);
    Marshal.Copy(pIdl2, byteArray2, 0, pIdl1Size);
    for (int i = 0; i < pIdl1Size; i++)
    {
        if (byteArray1[i] != byteArray2[i])
            return false;
    }
    return true;
}

static int GetItemIdSize(IntPtr pIdl)
{
    if (pIdl == IntPtr.Zero) return 0;
    int size = 0;
    // Next line throws AccessViolationException
    ITEMIDLIST idl = (ITEMIDLIST)Marshal.PtrToStructure(pIdl, typeof(ITEMIDLIST));
    while (idl.mkid.cb > 0)
    {
        size += idl.mkid.cb;
        pIdl = (IntPtr)((int)pIdl + idl.mkid.cb);
        idl = (ITEMIDLIST)Marshal.PtrToStructure(pIdl, typeof(ITEMIDLIST));
    }
    return size;
}

public struct ITEMIDLIST 
{
    public SHITEMID mkid;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct SHITEMID
{
    public ushort cb; // The size of identifier, in bytes, including cb itself.

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
    public byte[] abID; // A variable-length item identifier.
}