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路径(例如网络路径,如“\\计算机名称\共享文件夹\”),而Icon.ExtractAssociatedIcon则不能。如果需要或可能需要使用UNC路径,则最好使用SHGetFileInfo而不是Icon.ExtractAssociatedIcon。
System.Drawing.Icon.ExtractAssociatedIcon
和shell32.dll ExtractAssociatedIcon
,第一种方法给了我错误的图标,第二种方法虽然可行但图标并不总是正确的。最终偶然发现了这个答案,它完全符合我的意图。 - killer_mech仅仅是 Stefan 回答的 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;
}
}
这对我的项目有效,希望能帮助到别人。
这是使用P/Invokes的C#代码,在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;
}
注册表方法的问题在于您没有明确获取图标索引ID。有时(如果不是所有时候),您会获得一个图标ResourceID,这是应用程序开发人员用来命名图标插槽的别名。
因此,注册表方法意味着所有开发人员都使用与隐式图标索引ID相同的ResourceIDs(基于零、绝对、确定性)。
扫描注册表位置,您将看到许多负数,甚至文本引用-即不是图标索引ID。隐式方法似乎更好,因为它让操作系统完成工作。
现在只测试这种新方法,但它很有道理,希望解决这个问题。
应用程序可以包含多个图标,仅提取其中一个可能不足以满足您的需求。我自己想要挑选图标以便稍后在编译中重复使用它来制作 shim。
官方方法是使用 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);
}
仅适用于一个图标,但某种程度上也已经损坏了。
当使用pinvoke方法SHGetFileInfo
时,我得到了类似的效果。
使用PeNet库,代码如下:
var peFile = new PeFile(cmdArgs.iconpath);
byte[] icon = peFile.Icons().First().AsSpan().ToArray();
File.WriteAllBytes(iconPath, icon);
PeNet 允许提取图标,但它们不是原始格式,而且有多个。在这个提交中,整个功能被开发出来了 - 但还没有线索如何使用该功能。也许需要等待功能成熟。(请参见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 编译的二进制文件中遇到了异常,可能这个库并不适用于所有情况。
"HKCR\.{扩展名}"
,读取默认值(我们称之为文件类型
)"HKCR\{文件类型}\DefaultIcon"
中,读取默认值:这是图标文件的路径(或嵌入图标资源的 .exe 等图标容器文件)如果图标位于容器文件中(这很常见),则路径后面会有一个计数器,如这样:"foo.exe,3"
。这意味着它是可用图标的第4个(索引从零开始)。",0" 的值是隐含的(也是可选的)。如果计数器为 0 或缺失,则 shell 将使用第一个可用的图标。