Распознать специальную папку Windows Shell (т.е. получить ее CSIDL) через ее pIDL (теперь определите, равны ли pIDL с C #) - PullRequest
6 голосов
/ 26 августа 2010

У меня есть ситуация, когда я хочу выполнить специальную обработку некоторых специальных папок оболочки Windows (соответствующих значениям в перечислении CSIDL.) (Мое решение должно быть совместимо с WinXP.) У меня проблема в том, что когда ясталкиваясь с IShellFolders, пока я прохожу путь вниз по иерархии, я не могу найти способ сопоставить IShellFolders с их CSIDL.

Это мой текущий подход:

Инициализация статического однозначногоодна структура данных (csidlToFromFullPIdl) из всех CSIDL в их pIDL, возвращаемая 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);
}

Начать иерархию с Desktop IShellFolder:

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

Получить дочерние элементы следующим образом:

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

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

Создайте новые полностью квалифицированные pIDL для детей следующим образом:

_fullPIdl = ShellApi.ILCombine(parentFullPIdl, childRelativePIdl);

(Наконец, получите IShellFolder для ребенка, используя:)

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

Проблема, с которой я сталкиваюсь, заключается в том, что ни childRelativePIdl, ни _fullPIdl не соответствуют никаким pIDL в csidlToFromFullPIdl.

TIA.

FYI на компьютерах Vista, GUID correspoОбнаружение KNOWNFOLDERID может быть решением (но не для меня, поскольку я должен быть совместимым с WinXP.)

Я также должен сказать, что я считаю, что использование путей к специальным папкам (через SHGetSpecialFolderPath) - этонедостаточно, потому что некоторые из специальных папок, в которых я заинтересован, не имеют путей.(Например, CSIDL_DRIVES и CSIDL_NETWORK.)


Я пробую два подхода к этому.Во-первых, использовать SHGetDataFromIDList для получения Clsid, который затем можно сравнить с известными 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;
}

Проблема этого подхода заключается в том, что вызов SHGetDataFromIDList возвращает hResult, который соответствует выбрасыванию NotImplementedException.Означает ли это, что SHGetDataFromIDList недоступен в моей системе?(WinXP SP3.)

Мой второй подход - сравнить списки идентификаторов элементов, на которые ссылаются два указателя, со списками идентификаторов элементов и посмотреть, равны ли они.Я реализую технику, закодированную здесь в C. Это то, что я до сих пор:

Ответы [ 2 ]

2 голосов
/ 02 октября 2011

За Раймонд Чен : Списки элементов могут быть эквивалентными, не будучи идентичными побайтно. Используйте IShellFolder :: CompareIDs для проверки эквивалентности.

0 голосов
/ 02 октября 2011
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.
}
...