0

Having known nothing about interfaces until now, I am having a remarkably hard time trying to actually implement the solution given to this question.

I think I am correctly getting the pointer to the IShellFolder interface, but I don't seem to be able to actually use that pointer. How do I get to a usable interface when having that pointer?

The code below shows what I am doing and where I suspect the problems.

First, my interface delarations:

' I have wrapped the interfaces in their own namespace, "BinarusShell".
' I have declared every interface's functions / subs in the same order as they are in the .idl files of the Windows Platform SDK 7.1.
' Every function / sub has the <PreserveSig()> attribute so that I can see the native return values when debugging.
' I'd like to keep the question as lean as possible, so I am showing only a few functions per interface.
' Of course, I have implemented all of them (as they are in the respective .idl file).

Namespace BinarusShell

  <ComImport()> _
  <InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
  <Guid("000214FA-0000-0000-C000-000000000046")> _
  Public Interface IExtractIcon

    ' The above GUID is the GUID for the UNICODE version of the interface, i.e. IExtractIconW.
    ' Therefore, the UNICODE variant of strings is used in the parameters of the following functions.
    ' See also the include files of the Windows SDK where this is done exactly that way as well.

    <PreserveSig()> Function GetIconLocation(ByVal uFlags As UInteger,
                                             <MarshalAs(UnmanagedType.LPWStr, SizeParamIndex:=2)> ByVal pszIconFile As String,
                                             ByVal cchmax As UInteger,
                                             ByRef piIndex As Integer,
                                             ByRef pwFlags As UInteger) As Integer

    ' ...
    ' Here comes the second function (IExtractIcon has only two) ...
    ' ...    

  End Interface

  <ComImport()> _
  <InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
  <Guid("000214E6-0000-0000-C000-000000000046")> _
  Public Interface IShellFolder

    <PreserveSig()> Function GetUIObjectOf(<[In]()> ByVal hwndOwner As IntPtr,
                                           <[In]()> ByVal cidl As UInteger,
                                           <[In]()> <MarshalAs(UnmanagedType.LPArray, SizeParamIndex:=1)> ByVal apidl() As IntPtr,
                                           <[In]()> <MarshalAs(UnmanagedType.LPStruct)> ByVal riid As Guid,
                                           <[In](), Out()> ByRef rgfReserved As UInteger,
                                           <Out()> ByRef ppv As IntPtr) As Integer

    ' ...
    ' Here come the other functions ...
    ' ...

  End Interface

End Namespace

Second, my Windows API declarations, and third, the function I want to implement (this is where I am stuck):

' The Windows API declarations are in class Win32Api.
' This class is not wrapped in a certain namespace.
' To keep this question as lean as possible, I am leaving out my declarations of constants or GUIDs.
' I have taken all of them literally from the Windows Platform SDK 7.1.

Public Class Win32Api

  <DllImport("shell32.dll", CharSet:=CharSet.Auto)> _
  Private Shared Function SHBindToParent(<[In]()> ByVal pidl As IntPtr,
                                         <[In]()> <MarshalAs(UnmanagedType.LPStruct)> ByVal riid As Guid,
                                         <Out()> ByRef ppv As IntPtr,
                                         <Out()> ByRef ppidlLast As IntPtr) As Integer
  End Function

  <DllImport("shell32.dll", CharSet:=CharSet.Auto)> _
  Private Shared Function SHGetKnownFolderIDList(<[In]()> <MarshalAs(UnmanagedType.LPStruct)> ByVal rfid As Guid,
                                                 <[In]()> ByVal dwFlags As UInteger,
                                                 <[In]()> ByVal hToken As IntPtr,
                                                 <Out()> ByRef ppidl As IntPtr) As Integer
  End Function

' -----------------------------------------------------

' The function I would like to implement is in class Win32Api as well.
' The function at this stage does nothing which is useful for the outer world;
' It is just meant for code which I am stepping through with the debugger.
' The comments below show the result of every step and my understanding about what has happened.

  Public Shared Function IconTest() As Boolean

    Dim ui_ReturnFlags As UInteger
    Dim i_Result As Integer
    Dim intptr_CurrentPidlAbsolute As IntPtr,
        intptr_ParentIShellFolder As IntPtr,
        intptr_CurrentIExtractIcon As IntPtr
    Dim arintptr_RelPidList(0) As IntPtr
    Dim ishellfolder_Parent As BinarusShell.IShellFolder
    Dim iextracticon_Extractor As BinarusShell.IExtractIcon

    i_Result = SHGetKnownFolderIDList(FOLDERID_System, 0, 0, intptr_CurrentPidlAbsolute)
    ' The above seems to work. i_Result is 0 which means success, and
    ' intptr_CurrentPidlAbsolute has a reasonable value now.

    i_Result = SHBindToParent(intptr_CurrentPidlAbsolute, IID_IShellFolder, intptr_ParentIShellFolder, arintptr_RelPidList(0))
    ' The above seems to work. i_Result is 0 which means success, and
    ' intptr_ParentIShellFolder and arintptr_RelPidList(0) have reasonable values now.

    ' Now the PROBLEMS begin. I have no clue how to get an interface / object
    ' from intptr_ParentIShellFolder which I can use from within VB.net.
    ' Therefore, I just have tried the following (but believing that it is wrong):
    ishellfolder_Parent = DirectCast(Marshal.GetObjectForIUnknown(intptr_ParentIShellFolder), BinarusShell.IShellFolder)
    ' This does not throw an error, but I have no clue what actually is happening.
    ' Marshal.GetObjectForIUnknown, as the name implies, should return an object which
    ' encapsulates an IUnknown interface, but I am giving a pointer to an IShellFolder
    ' interface, so I really wonder why it doesn't freak out. The original idea
    ' to try it that way was that I believe that IShellFolder inherits IUnknown,
    ' so it *eventually* could work that way. Could somebody comment about that?

    i_Result = ishellfolder_Parent.GetUIObjectOf(0, 1, arintptr_RelPidList, IID_IExtractIcon, ui_ReturnFlags, intptr_CurrentIExtractIcon)
    ' Now this is the point where I am completely puzzled. i_Result is still 0,
    ' which means success, but intptr_CurrentIExtractIcon is 0 as well, meaning
    ' that something has gone horribly wrong. According to my understanding of
    ' the MSDN documentation, GetUIObjectOf MUST NOT return S_OK if something
    ' has gone wrong; in other words, if it returns S_OK, then intptr_CurrentIExtractIcon
    ' MUST be set to a reasonable value. Could somebody explain what's happening?

    ' I have left out the rest of the function because it wouldn't make any sense
    ' to use the NULL pointer for any further action.

  End Function

End Class

Any ideas what I am doing wrong? From the docs, I got the impression that every folder item (virtual or not) and file exposes IExtractIcon.

Binarus
  • 4,005
  • 3
  • 25
  • 41
  • Marshal.GetTypedObjectForIUnknown(). I doubt anybody is going to debug it for you when you don't post runnable code. – Hans Passant Nov 06 '16 at 12:23
  • @HansPassant Thanks for the reply. I already had tried that method (instead of what I have shown above), but couldn't get the IExtractIcon interface as well. In the meantime, I think I have found my error (see my answer below). Do you think this question and its answer are useful? If not, I'll just delete the whole thing ... – Binarus Nov 08 '16 at 11:59

1 Answers1

0

Answering my own question: When defining the IShellFolder interface in VB.net, I had forgotten to include the BindToFolder() function (you really shouldn't try to implement things at 03:00 a.m., notably if you had no clue of those things until 1 hour before).

Of course, as documented everywhere, this made the IShellFolder interface produce wrong results (again not really knowing what I am talking about, since BindToFolder() comes before GetUIObjectOf() in the interface definition, and the latter in turn comes before GetDisplayNameOf(), I could imagine that actually GetDisplayNameOf() has been called when my code called GetUIObjectOf(), which would explain the weird results).

Now I have corrected my IShellFolder definition (i.e. I have added the missing function) and have double-checked my other interface definitions (those were without errors), and everything works like expected.

Binarus
  • 4,005
  • 3
  • 25
  • 41