Получить значок файла, используемый Shell
В.Net (C# или VB: все равно), учитывая строку пути к файлу, структуру FileInfo или структуру FileSystemInfo для реально существующего файла, как я могу определить значки, используемые оболочкой (проводником) для этого? файл?
В настоящее время я не планирую использовать это для чего-либо, но мне стало интересно, как это сделать, когда я смотрю на этот вопрос, и я подумал, что было бы полезно заархивировать его здесь на SO.
10 ответов
Imports System.Drawing
Module Module1
Sub Main()
Dim filePath As String = "C:\myfile.exe"
Dim TheIcon As Icon = IconFromFilePath(filePath)
If TheIcon IsNot Nothing Then
''#Save it to disk, or do whatever you want with it.
Using stream As New System.IO.FileStream("c:\myfile.ico", IO.FileMode.CreateNew)
TheIcon.Save(stream)
End Using
End If
End Sub
Public Function IconFromFilePath(filePath As String) As Icon
Dim result As Icon = Nothing
Try
result = Icon.ExtractAssociatedIcon(filePath)
Catch ''# swallow and return nothing. You could supply a default Icon here as well
End Try
Return result
End Function
End Module
Вы должны использовать SHGetFileInfo.
Icon.ExtractAssociatedIcon в большинстве случаев работает так же хорошо, как и SHGetFileInfo, но SHGetFileInfo может работать с UNC-путями (например, сетевой путь типа "\\ComputerName\SharedFolder\"), а Icon.ExtractAssociatedIcon не может. Если вам нужно или может понадобиться использовать UNC-пути, лучше использовать SHGetFileInfo вместо Icon.ExtractAssociatedIcon.
Это хорошая статья CodeProject о том, как использовать SHGetFileInfo.
Пожалуйста, игнорируйте всех, кто говорит вам использовать реестр! Реестр НЕ является API. API, который вам нужен, это SHGetFileInfo с SHGFI_ICON. Вы можете получить подпись P/Invoke здесь:
Не более, чем версия ответа Стефана на C#.
using System.Drawing;
class Class1
{
public static void Main()
{
var filePath = @"C:\myfile.exe";
var theIcon = IconFromFilePath(filePath);
if (theIcon != null)
{
// Save it to disk, or do whatever you want with it.
using (var stream = new System.IO.FileStream(@"c:\myfile.ico", System.IO.FileMode.CreateNew))
{
theIcon.Save(stream);
}
}
}
public static Icon IconFromFilePath(string filePath)
{
var result = (Icon)null;
try
{
result = Icon.ExtractAssociatedIcon(filePath);
}
catch (System.Exception)
{
// swallow and return nothing. You could supply a default Icon here as well
}
return result;
}
}
Это работает для меня в моих проектах, надеюсь, это поможет кому-то.
Это C# с P/ Вызывает, что он будет работать до сих пор на системах x86/x64 начиная с WinXP.
(Shell.cs)
using System;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
namespace IconExtraction
{
internal sealed class Shell : NativeMethods
{
#region OfExtension
///<summary>
/// Get the icon of an extension
///</summary>
///<param name="filename">filename</param>
///<param name="overlay">bool symlink overlay</param>
///<returns>Icon</returns>
public static Icon OfExtension(string filename, bool overlay = false)
{
string filepath;
string[] extension = filename.Split('.');
string dirpath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "cache");
Directory.CreateDirectory(dirpath);
if (String.IsNullOrEmpty(filename) || extension.Length == 1)
{
filepath = Path.Combine(dirpath, "dummy_file");
}
else
{
filepath = Path.Combine(dirpath, String.Join(".", "dummy", extension[extension.Length - 1]));
}
if (File.Exists(filepath) == false)
{
File.Create(filepath);
}
Icon icon = OfPath(filepath, true, true, overlay);
return icon;
}
#endregion
#region OfFolder
///<summary>
/// Get the icon of an extension
///</summary>
///<returns>Icon</returns>
///<param name="overlay">bool symlink overlay</param>
public static Icon OfFolder(bool overlay = false)
{
string dirpath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "cache", "dummy");
Directory.CreateDirectory(dirpath);
Icon icon = OfPath(dirpath, true, true, overlay);
return icon;
}
#endregion
#region OfPath
///<summary>
/// Get the normal,small assigned icon of the given path
///</summary>
///<param name="filepath">physical path</param>
///<param name="small">bool small icon</param>
///<param name="checkdisk">bool fileicon</param>
///<param name="overlay">bool symlink overlay</param>
///<returns>Icon</returns>
public static Icon OfPath(string filepath, bool small = true, bool checkdisk = true, bool overlay = false)
{
Icon clone;
SHGFI_Flag flags;
SHFILEINFO shinfo = new SHFILEINFO();
if (small)
{
flags = SHGFI_Flag.SHGFI_ICON | SHGFI_Flag.SHGFI_SMALLICON;
}
else
{
flags = SHGFI_Flag.SHGFI_ICON | SHGFI_Flag.SHGFI_LARGEICON;
}
if (checkdisk == false)
{
flags |= SHGFI_Flag.SHGFI_USEFILEATTRIBUTES;
}
if (overlay)
{
flags |= SHGFI_Flag.SHGFI_LINKOVERLAY;
}
if (SHGetFileInfo(filepath, 0, ref shinfo, Marshal.SizeOf(shinfo), flags) == 0)
{
throw (new FileNotFoundException());
}
Icon tmp = Icon.FromHandle(shinfo.hIcon);
clone = (Icon)tmp.Clone();
tmp.Dispose();
if (DestroyIcon(shinfo.hIcon) != 0)
{
return clone;
}
return clone;
}
#endregion
}
}
(NativeMethods.cs)
using System;
using System.Drawing;
using System.Runtime.InteropServices;
namespace IconExtraction
{
internal class NativeMethods
{
public struct SHFILEINFO
{
public IntPtr hIcon;
public int iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
};
[DllImport("user32.dll")]
public static extern int DestroyIcon(IntPtr hIcon);
[DllImport("shell32.dll", CharSet = CharSet.Auto, BestFitMapping = false, ThrowOnUnmappableChar = true)]
public static extern IntPtr ExtractIcon(IntPtr hInst, string lpszExeFileName, int nIconIndex);
[DllImport("Shell32.dll", BestFitMapping = false, ThrowOnUnmappableChar = true)]
public static extern int SHGetFileInfo(string pszPath, int dwFileAttributes, ref SHFILEINFO psfi, int cbFileInfo, SHGFI_Flag uFlags);
[DllImport("Shell32.dll")]
public static extern int SHGetFileInfo(IntPtr pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, int cbFileInfo, SHGFI_Flag uFlags);
}
public enum SHGFI_Flag : uint
{
SHGFI_ATTR_SPECIFIED = 0x000020000,
SHGFI_OPENICON = 0x000000002,
SHGFI_USEFILEATTRIBUTES = 0x000000010,
SHGFI_ADDOVERLAYS = 0x000000020,
SHGFI_DISPLAYNAME = 0x000000200,
SHGFI_EXETYPE = 0x000002000,
SHGFI_ICON = 0x000000100,
SHGFI_ICONLOCATION = 0x000001000,
SHGFI_LARGEICON = 0x000000000,
SHGFI_SMALLICON = 0x000000001,
SHGFI_SHELLICONSIZE = 0x000000004,
SHGFI_LINKOVERLAY = 0x000008000,
SHGFI_SYSICONINDEX = 0x000004000,
SHGFI_TYPENAME = 0x000000400
}
}
Если вас интересует только значок для определенного расширения и вы не против создать временный файл, вы можете следовать приведенному здесь примеру.
Код C#:
public Icon LoadIconFromExtension(string extension)
{
string path = string.Format("dummy{0}", extension);
using (File.Create(path)) { }
Icon icon = Icon.ExtractAssociatedIcon(path);
File.Delete(path);
return icon;
}
Проблема с подходом реестра заключается в том, что вы явно не получаете идентификатор индекса иконки. Иногда (если не всегда) вы получаете значок ResourceID, который является псевдонимом, который разработчик приложения использовал для обозначения слота значка.
Следовательно, метод реестра подразумевает, что все разработчики используют ResourceID, которые совпадают с неявным идентификатором индекса иконки (который является нулевым, абсолютным, детерминированным).
Просканируйте местоположение реестра, и вы увидите много отрицательных чисел, иногда даже текстовые ссылки - т.е. не идентификатор индекса значка. Неявный метод кажется лучше, так как он позволяет ОС делать всю работу.
Только сейчас тестирую этот новый метод, но он имеет смысл и, надеюсь, решит эту проблему.
- определить расширение
- в реестре, перейдите к
"HKCR\.{extension}"
, прочитайте значение по умолчанию (давайте назовем егоfiletype
) - в
"HKCR\{filetype}\DefaultIcon"
, прочитайте значение по умолчанию: это путь к файлу значка (или файлу контейнера значка, например.exe со встроенным ресурсом значка) - при необходимости используйте предпочитаемый метод извлечения ресурса значка из упомянутого файла.
редактировать / сдвинуть вверх от комментариев:
Если значок находится в файле контейнера (это довольно часто), после пути будет счетчик, например: "foo.exe,3"
, Это означает, что это значок № 4 (индекс начинается с нуля) из доступных значков. Значение ",0" неявно (и необязательно). Если счетчик равен 0 или отсутствует, оболочкой будет использоваться значок "Доступен первый".
Эта ссылка, кажется, содержит некоторую информацию. Это включает в себя много обходов реестра, но это кажется выполнимым. Примеры в C++
Приложение может содержать несколько значков, и извлечения только одного из них может быть недостаточно для ваших нужд. Я сам хотел подобрать иконку, чтобы потом использовать ее в компиляции для создания шима.
Официальный метод, который работает - используйтеIconLib.Unofficial
0.73.0 или выше.
Добавьте такой код:
MultiIcon multiIcon = new MultiIcon();
multiIcon.Load(<in path>);
multiIcon.Save(<out path>, MultiIconFormat.ICO);
может извлекать значки, которые используются приложением.
Однако сама библиотека работает в .net framework 4.6.1 - v4.8, не работает в .net core.
Другие методы, которые я пробовал также:
Icon icon = Icon.ExtractAssociatedIcon(<in path>);
using (FileStream stream = new FileStream(<out path>, FileMode.CreateNew))
{
icon.Save(stream);
}
Работает только на одну иконку, но она тоже как-то испорчена. Аналогичный эффект я получал при использовании метода pinvokeSHGetFileInfo
.
При использовании библиотеки PeNet код выглядит так:
var peFile = new PeFile(cmdArgs.iconpath);
byte[] icon = peFile.Icons().First().AsSpan().ToArray();
File.WriteAllBytes(iconPath, icon);
PeNet позволяет извлекать иконки, но они не в оригинальном формате, а также их несколько. В этом коммите разрабатывается вся функция, но пока нет понятия, как эту функцию следует использовать. Возможно, нужно подождать, пока функция созреет. (См. Пенет, выпуск № 258)
ICSharpCode.Decompiler
можно использовать в частных методах для обеспечения аналогичной функциональности:
PEFile file = new PEFile(cmdArgs.iconpath);
var resources = file.Reader.ReadWin32Resources();
if (resources != null)
{
var createAppIcon = typeof(WholeProjectDecompiler).GetMethod("CreateApplicationIcon", BindingFlags.Static | BindingFlags.NonPublic);
byte[] icon = (byte[])createAppIcon.Invoke(null, new[] { file });
File.WriteAllBytes(iconPath, icon);
}
Но у меня было исключение для скомпилированных двоичных файлов .net core 3.1, возможно, эта библиотека не работает для всех случаев.