Ok so I've been beating my brains out over this for two days now pouring
through the SDK, MSDN Documentation, and the Internet at large.
I'm not new to programming or .NET, but definately new to Interop and shell
programming, and am getting tripped up by all the marshaling, importing, and
pointers.
I'm trying to get the contents of the Recycle Bin, and specifically the file
names and deleted dates. I found a good C++ example at
http://www.codeproject.com/shell/recyclebin.asp, and have been sucessfull in
getting a managed IShellFolder object for the Recycle Bin itself. I can get
it's display name etc. I have even managed to convert the IEnumIDList and
IShellFolder2 interfaces over successfully (I think).
The problem comes when I call IEnumIDList.Next, and then try and marshal the
resulting pointers to managed (IShellFolder2) objects. I'm sure that I'm
not
doing something right with rgelt (the "address of an array of ITEMIDLIST
pointers") returned by IEnumIDList.Next. I get a
System.ExecutionEngineException on
the following line:
Dim objRecycledItem As Object = Marshal.GetTypedObjectForIUnknown(pidl(0),
System.Type.GetType("IShellFolder2"))
I've included what I think are the relevant portions of code below. If more
information is required let me know and I'd be happy to post it.
Any help, advice, thoughts, or links to infomration would be greatly
appreciated.
Thanks in advance.
Here is the function that is throwing the exception:
Sub Test()
Dim pDesktop As IntPtr
Dim pidlRecyclebin As IntPtr
Dim pRecyclebin As IntPtr
'Get a PIDL for the desktop
Shell32.SHGetDesktopFolder(pDesktop)
Shell32.SHGetSpecialFolderLocation(Me.Handle,
Shell32.Enums.CSIDL.CSIDL_BITBUCKET, pidlRecyclebin)
'Marshal pDesktop to a managed IShellFolder object
Dim objDesktop As Object =
Marshal.GetTypedObjectForIUnknown(pDesktop,
System.Type.GetType("IShellFolder"))
Dim desktop As IShellFolder = DirectCast(objDesktop, IShellFolder)
'Bind the pRecyclebin PIDL
desktop.BindToObject(pidlRecyclebin, IntPtr.Zero,
Shell32.ShellGUIDs.IID_IShellFolder, pRecyclebin)
'Get a STRRET structure with the recycle bin name (for testing just
to know our PIDL really is working)
Dim strret As Shell32.Structures.STRRET
Dim name As String
desktop.GetDisplayNameOf(pidlRecyclebin,
Convert.ToUInt32(Shell32.Enums.SHGNO.SHGDN_NORMAL), strret)
Shell32.StrRetToBSTR(strret, pidlRecyclebin, name)
Debug.WriteLine("Recyle Bin Name: " & name)
'Marshal the pRecyclebin PIDL to a managed IShellFolder object
Dim objRB As Object = Marshal.GetTypedObjectForIUnknown(pRecyclebin,
System.Type.GetType("IShellFolder"))
Dim Recyclebin As IShellFolder = DirectCast(objRB, IShellFolder)
'Use the RecycleBin IShellFolder object to enumerate it's contents
Dim ppEnumIDList As IntPtr
Recyclebin.EnumObjects(Me.Handle,
Shell32.Enums.SHCONTF.SHCONTF_FOLDERS Or
Shell32.Enums.SHCONTF.SHCONTF_NONFOLDERS _
Or Shell32.Enums.SHCONTF.SHCONTF_INCLUDEHIDDEN, ppEnumIDList)
Dim objEnumIDList As Object =
Marshal.GetTypedObjectForIUnknown(ppEnumIDList,
System.Type.GetType("IEnumIDList"))
Dim EnumIDList As IEnumIDList = DirectCast(objEnumIDList,
IEnumIDList)
Dim pidl As IntPtr()
While Not EnumIDList.Next(Convert.ToUInt32(1), pidl,
IntPtr.Zero).Equals(Convert.ToUInt32(Shell32.Constants.S_FALSE))
ERR HERE-> Dim objRecycledItem As Object =
Marshal.GetTypedObjectForIUnknown(pidl(0),
System.Type.GetType("IShellFolder2"))
Dim RecycledItem As IShellFolder2 =
DirectCast(objRecycledItem, IShellFolder2)
'...
Marshal.ReleaseComObject(RecycledItem)
End While
'Release our com objects
Marshal.ReleaseComObject(desktop)
Marshal.ReleaseComObject(Recyclebin)
Marshal.ReleaseComObject(EnumIDList)
End Sub
Here is my Import of IEnumIDList:
<ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("000214F2-0000-0000-C000-000000000046")> _
Public Interface IEnumIDList
'Retrieves the specified number of item identifiers in the
'enumeration sequence and advances the current position by
'the number of items retrieved.
'[in] celt Number of elements in the array pointed to by the rgelt
parameter.
'[out] rgelt Address of an array of ITEMIDLIST pointers that receives
the item
' identifiers. The implementation must allocate these item
identifiers
' using the Shell's allocator (retrieved by the SHGetMalloc
function).
' The calling application is responsible for freeing the item
' identifiers using the Shell's allocator.
'[out] pceltFetched Address of a value that receives a count of the item
identifiers
' actually returned in rgelt. The count can be smaller than the
value
' specified in the celt parameter. This parameter can be NULL only
' if celt is one.
<PreserveSig()> _
Function [Next](ByVal celt As UInt32, <MarshalAs(UnmanagedType.LPArray)>
ByRef rgelt As IntPtr(), ByRef pceltFetched As IntPtr) As UInt32
'Skips over the specified number of elements in the enumeration
sequence.
'[in] Number of item identifiers to skipt.
<PreserveSig()> _
Function Skip(ByVal celt As UInt32) As UInt32
'Returns to the beginning of the enumeration sequence.
<PreserveSig()> _
Function Reset() As UInt32
'Creates a new item enumeration object with the same contents and state
as the current one.
'[out] ppenum Address of a pointer to the new enumeration object. The
calling
' application must eventually free the new object by calling its
Release member function.
<PreserveSig()> _
Function Clone(ByRef ppenum As IEnumIDList) As UInt32
End Interface
And my import of ISHellFolder2:
<ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("93F2F68C-1D1B-11D3-A30E-00C04F79ABD1")> _
Public Interface IShellFolder2
Inherits IShellFolder
'Requests a pointer to an interface that allows a client to enumerate
the available search objects.
'ppEnum [out] Address of a pointer to an enumerator object's
IEnumExtraSearch interface.
<PreserveSig()> _
Function EnumSearches(ByRef ppenum As IntPtr) As UInt32
'Retreves the default sorting and display columns.
'dwReserved [in] Reserved. Set to zero.
'pSort [out] Pointer to a value that receives the index of the default
sorted column.
'pDisplay [out] Pointer to a value that receives the index of the
default display column.
'Return Value: Returns NOERROR if successful, or a COM error value
otherwise.
<PreserveSig()> _
Function GetDefaultColumn(ByVal dwReserved As UInt32, ByRef pSort As
IntPtr, ByRef pDisplay As IntPtr) As UInt32
'Retrieves the default state for a specified column.
'iColumn [in] Integer that specifies the column number.
'pcsFlags [out] Pointer to a value that contains flags that indicate
' the default column state. This parameter can include
' a combination of the following flags.
' SHCOLSTATE_TYPE_STR A string.
' SHCOLSTATE_TYPE_INT An integer.
' SHCOLSTATE_TYPE_DATE A date.
' SHCOLSTATE_ONBYDEFAULT Should be shown by default in the
' Microsoft Windows Explorer
Details view.
' SHCOLSTATE_SLOW Extracting information about the column
can be
' time consuming. This flag recommends
that the
' folder view extract column information
' asynchronously, on a background thread.
' SHCOLSTATE_EXTENDED Provided by a handler, not the
folder object.
' SHCOLSTATE_SECONDARYUI Not displayed in the shortcut
menu,
' but listed in the More dialog
box.
' SHCOLSTATE_HIDDEN Not displayed in the user interface.
' SHCOLSTATE_PREFER_VARCMP Uses default sorting rather
than
' IShellFolder::CompareIDs to get
the
' sort order.
'Return VAlue: Returns S_OK if successful, or an error value otherwise.
<PreserveSig()> _
Function GetDefaultColumnState(ByVal iColumn As UInt32, ByRef pcsFlags
As IntPtr) As UInt32
'Returns the globally unique identifier (GUID) of the default search
object for the folder.
'pguid [out] GUID of the default search object.
'Return Value: Returns NOERROR if successful, or an OLE error value
otherwise.
<PreserveSig()> _
Function GetDefaultSearchGUID(ByRef GUID As IntPtr) As UInt32
'Retrieves detailed information, identified by a property set identifier
(FMTID) and a property
'identifier (PID), on an item in a Shell folder.
'pidl [in] PIDL of the item, relative to the parent folder. This method
accepts only single-level
' PIDLs. The structure must contain exactly one SHITEMID
structure followed by a
' terminating zero.
'pscid [in] Pointer to an SHCOLUMNID structure that identifies the
column.
'pv [out] Pointer to a VARIANT with the requested information. The value
will be fully typed.
'Return Value: Returns S_OK if successful, or an error value otherwise.
<PreserveSig()> _
Function GetDetailsEx(ByVal pidl As IntPtr, ByVal pscid As IntPtr, ByRef
pv As IntPtr) As UInt32
'Retrieves detailed information, identified by a column index, on an
item in a Shell folder.
'pidl [in] Pointer to an item identifier list (PIDL) of the item for
which you are requesting
' information. This method accepts only single-level PIDLs. The
structure must
' contain exactly one SHITEMID structure followed by a
terminating zero. If this
' parameter is set to NULL, the title of the information field
specified by iColumn
' is returned.
'iColumn [in] Zero-based index of the desired information field. It is
identical to the
' column number of the information as it is displayed in a
Microsoft Windows
' Explorer Details view.
'psd [out] Pointer to a SHELLDETAILS structure that contains the
information.
'Return Value: Returns S_OK if successful, or an error value otherwise.
<PreserveSig()> _
Function GetDetailsOf(ByVal pidl As IntPtr, ByVal iColumn As UInt32,
ByRef psd As IntPtr) As UInt32
'Converts a column to the appropriate property set ID (FMTID) and
property ID (PID).
'iColumn [in] Column ID
'pscid [out] Pointer to an SHCOLUMNID structure containing the FMTID and
PID.
'Return Value: Returns NOERROR if successful, or a COM error value
otherwise.
<PreserveSig()> _
Function MapColumnToSCID(ByVal icolumn As UInt32, ByVal pscid As IntPtr)
As UInt32
End Interface
Dmytro Lapshyn [MVP] - 17 Apr 2006 12:58 GMT
Hi Joshua,
Why do you do a manual import of IShellFolder2? I'd suggest that you found
the unmanaged .DLL implementing this interface and applied tlbimp.exe to
that DLL. This is much more reliable.
Joshua Taylor - 19 Apr 2006 20:54 GMT
Honestly, because the whole COM Interop thing is new to me, and in reading
the documentation, I got the impression that tlbimp.exe only worked with
.TLBs, and I was also under the impression that IShellFolder2 was
implemented in shell32.dll. But, under futher investigation, you are
correct (but you knew that <g>) in that tlbimp.exe works for .DLLs also.
However, after importing shell32.dll, I'm not seeing IShellFolder2. I must
be missing something. Can you point me in the right direction for finding
which library implements this interface?
Thanks Again,
Josh
Dmytro Lapshyn [MVP] - 20 Apr 2006 11:34 GMT
There's a fellow MVP's site where he hosts some old VB6 stuff:
http://www.mvps.org/emorcillo/en/code/vb6/index.shtml
One of the samples is dedicated to IShellFolder. Hopefully there's a TLB in
the sample you can reuse. Anyway, you can probably ask the guy for the
assistance - he's an MVP and should be willing to help.
> Honestly, because the whole COM Interop thing is new to me, and in reading
> the documentation, I got the impression that tlbimp.exe only worked with
[quoted text clipped - 7 lines]
> Thanks Again,
> Josh
Dmytro Lapshyn [MVP] - 20 Apr 2006 11:40 GMT
See also this discussion. It's about C++, but the basic principles still
apply.
http://groups.google.com/group/microsoft.public.vc.atl/browse_thread/thread/ce1b
45bcb76fa2d2/48aa1ef81cf8dabe?lnk=st&q=IShellFolder2+tlb&rnum=2&hl=en#48aa1ef81c
f8dabe
That is, if you can't find the ready-made TLB, compile your own one from the
supplied IDL file (shobjidl.idl) with the MIDL tool. Or, indeed, resort to
the manual interface declaration which might be more accurate that the TLB
due to the reasons described in the referenced post.
IMPORTANT!!! If you do your own .NET interface declaration, use the IDL as
the source, but *NOT MSDN*.
This is because it is not guaranteed the method and property order and
declarations are correct in the MSDN docs, but they are practically 100%
accurate in the IDL.
> Honestly, because the whole COM Interop thing is new to me, and in reading
> the documentation, I got the impression that tlbimp.exe only worked with
[quoted text clipped - 7 lines]
> Thanks Again,
> Josh
Joshua Taylor - 20 Apr 2006 23:42 GMT
Thanks for both the replys. I have actually used Eduardo Morcillo's OLELIB
type library for a rudimentary word add-in I had done quite a while back,
and had completely forgot that the IShellFolder interface was in there. I
had never checked on IShellFolder2, but just downloaded it again and checked
and it is.
Also, on the note about not using MSDN. I was not aware that the order in
which the methods are defined are important. I was under the impression
that unless told otherwise Interop was matching the imported functions based
on name. I will check out both the OLELIB type library and the IDL and
re-order accordingly.
Thanks for the pointers, and all the help. I've at least got a new
direction to try,
Josh