65.9K
CodeProject 正在变化。 阅读更多。
Home

确定文件类型:不同技术的演示

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (8投票s)

2014年12月2日

CPOL

6分钟阅读

viewsIcon

25936

downloadIcon

890

分析用于确定文件 MIME 类型和可执行状态的各种方法。

引言

有几种常用的方法可以确定文件是否可执行,以及其他用于确定其Mime类型的方法。本文基于我的C#类库,该类库封装了其中一些方法。在演示解决方案中,我提供了Windows Forms和WPF应用程序,演示了如何使用该库。虽然本文没有专门讨论,但该库也应该适用于基于Windows的Web服务器。

背景

在本文中,我将描述主要功能及其工作原理,并在适当的地方提供一些简短的代表性代码示例。请参阅上面链接中提供的代码以获取其余的源代码。

注意:类方法返回带有通常人类可读信息的字符串值。如果您希望在生产环境中使用该库,建议从GetExecutableTypeGetBinaryTypeGetPEInformation返回枚举值而不是字符串。

解决方案文件是在Visual Studio 2013中创建的,但应该可以在2010及更高版本中使用。如果不能,您可以更改解决方案文件顶部的版本信息以匹配您的特定版本,或者使用您的Visual Studio版本将这三个项目文件导入新解决方案。

下面列出了这三个项目文件及其最低要求。我尽量将平台需求保持在最低限度,并尽可能避免使用2.0版以上的C#功能。

  1. FileTypesLib 类库 (.NET 2.0)
  2. FileTypesDemoWinForms (.NET 2.0)
  3. FileTypesDemoWPF (.NET 3.5)

Win32 函数

名为NativeMethods的类包含几个Win32函数调用及其相关的结构和枚举。我已对所有方法采用最佳实践,包括数据类型、调用约定和函数激活。在适当的情况下,例如SHGetFileInfo函数,演示应用程序中提供了线程和非线程示例。

一、SHGetFileInfo

收集有关文件系统对象的信息。需要一个SHFILEINFO结构和标志来告诉函数所需的操作。请注意在DllImport属性中使用CharSet.Auto。这使得该函数可以在Ansi或Unicode环境中工作。

[DllImport("shell32.dll", CharSet = CharSet.Auto)]
internal static extern IntPtr SHGetFileInfo(
	[MarshalAs(UnmanagedType.LPWStr)]
	string pszPath,
	uint dwFileAttributes,
	ref SHFILEINFO psfi,
	uint cbSizeFileInfo,
	uint uFlags);

二、GetBinaryType

检测文件是否可执行。不幸的是,此函数不可靠。在64位计算机上调用时,它不能正确识别32位和64位应用程序。对于64位应用程序,该函数返回false并保留默认的二进制类型值(SCS_32BIT_BINARY),而对于32位应用程序,它返回true但将二进制类型设置为64位。请参阅演示图像以了解这种异常行为的示例。

三、FindMimeFromData

通过检查提供的数据的前256个字节来检测文件是否是26种不同Mime类型中的一种。此函数有时与其他Mime检测技术结合使用,如FileTypes类库的GetContentType方法所示,以允许检测更多类型。

FileTypes 类库方法

FileTypes类库包含几种方法,可用于确定文件的Mime类型或文件是否可执行。下面方法描述中讨论了每种方法的各种限制。

一、GetShellInfo

使用shell32.dll的SHGetFileInfo函数获取有关提交文件信息。SHGetFileInfo返回文件类型描述。在文件资源管理器(以前称为Windows资源管理器)中,这是在“类型”列下看到的字符串值,例如“应用程序”。

GetShellInfo库方法创建一个SHFILEINFO结构,设置必要的标志,然后从NativeMethods调用SHGetFileInfo。友好的类型名称作为字符串返回。

注意:由于设置了SHGIF_USEFILEATTRIBUTES标志,因此不读取实际文件。相反,根据文件扩展名和调用函数时提供的属性,检索如果文件存在则可用的值。这当然比读取实际文件更快。

public string GetShellInfo(string fileName)
{
	if (String.IsNullOrEmpty(fileName)) return "Invalid file name";

	// Create the SHFILEINFO structure and assign strings to the string pointers
	SHFILEINFO info = new SHFILEINFO();
	info.szDisplayName = String.Empty;
	info.szTypeName = String.Empty;

	// Set the file attribute flags
	uint dwFileAttributes = (uint)SHGetFileInfoAttributes.FILE_ATTRIBUTE_NORMAL;

	// Set the operation flags for retrieving the desired information
	uint uFlags = (uint)(SHGetFileInfoFlags.SHGFI_USEFILEATTRIBUTES |
				 SHGetFileInfoFlags.SHGFI_DISPLAYNAME |
				 SHGetFileInfoFlags.SHGFI_TYPENAME);

	// Call the Win32 function
	NativeMethods.SHGetFileInfo(fileName, dwFileAttributes, ref info, (uint)Marshal.SizeOf(info), uFlags);

	// Return the type name as a string
	return info.szTypeName;
}

二、GetExecutableType

同样使用shell32.dll的SHGetFileInfo函数,但设置了SHGFI_EXETYPE标志,以便它将确定可执行文件的类型。此检测相当原始,仅限于DOS、NT和OS2(这也是早期的Windows)。我向枚举添加了两个值,用于指示文件何时是WinNT控制台应用程序以及文件何时根本不是可执行文件。

此外,当检测到的文件类型为WinNT可执行文件时,返回值包含操作系统版本。

三、GetBinaryType

请参阅上面Win32方法下的讨论。我向枚举添加了一个值,以便将不可执行文件报告为不可执行文件。

四、GetContentType

结合了对包含数百种Mime类型的嵌入式资源文本文件的字典查找,以及对urlmon.dll函数FindMimeFromData的调用,以确定文件的Mime类型。此代码是Tim Schmelter在stackoverflow上的代码的增强版本。我将Mime类型列表从源代码中的硬编码更改为以名称-值对(例如:afl,video/animaflex)的形式放入嵌入式资源文本文件中,以便可以轻松更新类型。我还在这里和那里稍微调整了代码。

五、GetMimeType

引用一组只读字节数组,其中包含各种文件类型文件头中包含的“魔术”标识字节,以确定Mime类型。此方法改编自ROFLwTIME在stackoverflow上的帖子。该例程检测18种不同的Mime类型,但可以通过添加额外的魔术字节和Mime类型进行扩展,尽管我建议从硬编码转向某种查找技术。

六、GetPEInformation

使用Mono中略微修改的PEReader类来收集提交检查的可执行文件的相关信息。该方法从文件头读取可移植可执行(PE)字节,并提供各种信息。如上面的演示图像所示,这些信息包括文件运行的机器类型、文件是可执行文件还是DLL、是否是32位应用程序以及文件创建日期时间等。

我的修改非常小,主要涉及防止在传入非可执行文件时崩溃。FileTypes中的GetPEInformation方法使用一个switch和一些if语句将PEReader提供的数据解析为人类可读的形式。

关注点

根据SHGetFileInfo文档,"您应该从后台线程调用此函数。否则可能导致UI停止响应。" 由于这个免责声明,我提供了线程和非线程版本。例如,GetShellInfoGetShellInfoThread。WPF演示中使用线程版本,而Windows Forms演示中使用非线程版本。对于易于访问的本地文件,非线程版本应该没问题,但对于网络文件或其他访问缓慢的文件,请使用线程版本(并且可能添加更强大的错误检查)。

演示表单上显示的图标是通过调用带有适当标志的SHGetFileInfo获得的。如前所述,提供了线程和非线程版本。

将Windows Forms应用程序转换为WPF应用程序并不特别困难。对于我的演示应用程序,主要变化在于图像的处理方式和控件调用方式。以下是简要示例,说明了将图标加载到WinForms PictureBox和WPF Image之间的区别,以及如何调用线程化的WinForms和WPF控件。

WinForms PictureBox(非线程)

IntPtr handle = fileUtilities.GetLargeIcon(file);
if (handle != IntPtr.Zero)
{
	// Convert the icon to a bitmap and load it to the control
	pictureBox1.Image = Bitmap.FromHicon(handle);
}

WPF Image(非线程)

IntPtr handle = fileUtilities.GetLargeIcon(file);
if (handle != IntPtr.Zero)
{
	// Convert the icon to a bitmap and load it to the control
	image1.Source = Imaging.CreateBitmapSourceFromHIcon(
		handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
}

WinForms TextBox 线程回调

if (textBox1.InvokeRequired)
{
	SetTextBoxCallback d = new SetTextBoxCallback(TextBoxCallback);
	this.Invoke(d, new object[] { sender, eventArgs });
}
else { textBox1.Text = eventArgs.SHFileInfo; }

WPF TextBox 线程回调

if (!textBox1.Dispatcher.CheckAccess())
{
	SetTextBoxCallback d = new SetTextBoxCallback(TextBoxCallback);
	Dispatcher.Invoke(d, new object[] { sender, eventArgs });
}
else { textBox1.Text = eventArgs.SHFileInfo; }

历史

1.0 版于 2014/12/1 发布。CPOL 许可证。

© . All rights reserved.