tags:

views:

329

answers:

3

I need a working example of the ExtractAssociatedIcon function in Shell32.dll. I cannot get it working and I am out of ideas. I need another set of eyes on the following code. When the form loads, its icon should be set to the Visual Studio icon, but all I get is the default system icon.

Imports System.Runtime.InteropServices

Public Class Form1

    Public Function ExtractIcon(ByVal path As String, ByVal handle As IntPtr) As Icon
        Dim oResult As Icon
        Dim hIcon As IntPtr
        Dim iIndex As Integer
        Dim oPath As New System.Text.StringBuilder(260, 260)

        oPath.Append(path)

        hIcon = ExtractAssociatedIcon(handle, oPath, iIndex)

        'hIcon = ExtractAssociatedIcon(handle, path, iIndex)

        Dim oIcon As Icon = Icon.FromHandle(hIcon)

        oResult = DirectCast(oIcon.Clone, Icon)

        DestroyIcon(hIcon)

        Return oResult
    End Function

    Public Declare Auto Function ExtractAssociatedIcon Lib "shell32" ( _
        ByVal hInst As IntPtr, _
        <MarshalAs(UnmanagedType.LPStr)> ByVal lpIconPath As System.Text.StringBuilder, _
        ByRef lpiIcon As Integer) As IntPtr

    'Public Declare Auto Function ExtractAssociatedIcon Lib "shell32" ( _
    '    ByVal hInst As IntPtr, _
    '    <MarshalAs(UnmanagedType.LPStr)> ByVal lpIconPath As String, _
    '    ByRef lpiIcon As Integer) As IntPtr

    Friend Declare Auto Function DestroyIcon Lib "user32" (<[In]()> ByVal hIcon As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        'ExtractAssociatedIcon uses ExtractAssociatedIcon that is in Shell32.dll.
        'This works, so why doesn't mine? What am I missing?
        'Me.Icon = System.Drawing.Icon.ExtractAssociatedIcon("C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe")

        Me.Icon = ExtractIcon("C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe", Me.Handle)

    End Sub

End Class

Corrected

<MarshalAs(UnmanagedType.LPStr)> was the problem. It should have been LPTStr, not LPStr.

A: 

Why not simply use the .NET native Icon.ExtractAssociatedIcon method, which seems to do the same thing?

Me.Icon = Icon.ExtractAssociatedIcon( _
    "C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe")

P.S.: I tried your code, with the same, wrong result that you described. The above code snippet seems to work though.

stakx
Because if Icon.ExtractAssociatedIcon is called on a thread that doesn't have a handle, the wrong icon may be extracted. Same with SHGetFileInfo.
AMissico
A: 

Try the native .NET method stakx mentions - I've had back luck with it saving the ICO file though (if you save as another format, say bitmap, it normally works though)

For a pinvoke somethign like this should work: (caveat, not tested) referenced: http://social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/ecb6137c-8bda-4468-b5e0-359caeb202b1

[DllImport("Shell32.dll")]
private static extern int SHGetFileInfo( string pszPath, uint dwFileAttributes, out SHFILEINFO psfi, uint cbfileInfo, SHGFI uFlags );

[StructLayout(LayoutKind.Sequential)]
private struct SHFILEINFO
{
    public SHFILEINFO(bool b)
    {
        hIcon = IntPtr.Zero; iIcon = 0; dwAttributes = 0; szDisplayName = ""; szTypeName = "";
    }
    public IntPtr hIcon;
    public int iIcon;
    public uint dwAttributes;

    [MarshalAs(UnmanagedType.LPStr, SizeConst = 260)]
    public string szDisplayName;

    [MarshalAs(UnmanagedType.LPStr, SizeConst = 80)]
    public string szTypeName;
};

private ExtractIcon()
{
}

private enum SHGFI
{
    SmallIcon = 0x00000001,
    LargeIcon = 0x00000000,
    Icon = 0x00000100,
    DisplayName = 0x00000200,
    Typename = 0x00000400,
    SysIconIndex = 0x00004000,
    UseFileAttributes = 0x00000010
}

public static Icon GetIcon(string strPath, bool bSmall)
{
    SHFILEINFO info = new SHFILEINFO(true);
    int cbFileInfo = Marshal.SizeOf(info);

    SHGFI flags;
    if (bSmall)
    {
        flags = SHGFI.Icon | SHGFI.SmallIcon | SHGFI.UseFileAttributes;
    }
    else
    {
        flags = SHGFI.Icon | SHGFI.LargeIcon | SHGFI.UseFileAttributes;
    }

    SHGetFileInfo(strPath, 256, out info, (uint)cbFileInfo, flags);

    return Icon.FromHandle(info.hIcon);
}
Chris B
Because if Icon.ExtractAssociatedIcon is called on a thread that doesn't have a handle, the wrong icon may be extracted. Same with SHGetFileInfo.
AMissico
A: 

Passing in the handle is definitely wrong, the 1st argument to EAI is a module handle, not an icon handle. This worked well:

Imports System.Runtime.InteropServices
Imports System.ComponentModel
...
  Public Shared Function ExtractIcon(ByVal path As String, Optional ByVal index As Integer = 0) As Icon
    Dim handle As IntPtr = ExtractAssociatedIcon(IntPtr.Zero, path, index)
    If handle = IntPtr.Zero Then Throw New Win32Exception(Marshal.GetLastWin32Error())
    Dim retval As Icon = Nothing
    using temp As Icon = Icon.FromHandle(handle)
      retval = CType(temp.Clone(), Icon)
      DestroyIcon(handle)
    end using
    Return retval      
  End Function

  Private Declare Auto Function ExtractAssociatedIcon Lib "shell32" ( _
      ByVal hInst As IntPtr, ByVal path As String, ByRef index As Integer) As IntPtr
  Private Declare Auto Function DestroyIcon Lib "user32" (ByVal hIcon As IntPtr) As Boolean

No need for StringBuilder if the module handle is null. Don't use this code if you don't the "index" argument. Icon.ExtractAssocationIcon will work just as well.

Hans Passant
<MarshalAs(UnmanagedType.LPStr)> was the problem. It should have been LP*T*Str. Thank you very much for answering the question.
AMissico
New Win32Exception(Marshal.GetLastWin32Error()) is the same as New Win32Exception. The constructor gets the last error by default unless you use the constructor where you specify the error code.
AMissico
I tried both a control handle and a module handle. The Icon.ExtractAssociatedIcon passes IntPtr.Zero, which I believed is the reason why ExtractAssociatedIcon fails to retrieve the proper icon when run on a background thread. This behavior can be seen when getting the icon for an XML file (.xml). That is why I wanted to try the API call in order to pass different handles. Regardless, of which function or the handles, the wrong icon is always returned. (I just tested.)
AMissico
Download SysInternals' AutoRuns utility and disable all non-MSFT shell extension handlers.
Hans Passant
No difference. I disabled even Microsoft's stuff. The affected extensions are .appref-ms, .ht, .htm, .html, .mht, .mhtml, .msc, .shtm, .shtml, .sln, .stm, .url, .vst, .vtx, .xml. Same results on another computer.
AMissico