使用 Shell32 获取文件扩展属性时出现异常
Posted
技术标签:
【中文标题】使用 Shell32 获取文件扩展属性时出现异常【英文标题】:Exception when using Shell32 to get File extended properties 【发布时间】:2015-07-14 10:22:42 【问题描述】:我正在尝试使用 Shell32 在 c# 中获取扩展文件属性。
我的代码如下。
var file = FileUpload1.PostedFile;
List<string> arrHeaders = new List<string>();
Shell shell = new ShellClass();
//Exception is thrown at next line
Folder rFolder = shell.NameSpace(Path.GetDirectoryName(file.FileName));
FolderItem rFiles = rFolder.ParseName(Path.GetFileName(file.FileName));
for (int i = 0; i < short.MaxValue; i++)
string value = rFolder.GetDetailsOf(rFiles, i).Trim();
arrHeaders.Add(value);
我得到如下异常。
消息 - 无法将“Shell32.ShellClass”类型的 COM 对象转换为接口类型“Shell32.IShellDispatch6”。此操作失败,因为 IID 为“286E6F1B-7113-4355-9562-96B7E9D64C54”的接口的 COM 组件上的 QueryInterface 调用因以下错误而失败:不支持此类接口(来自 HRESULT 的异常:0x80004002 (E_NOINTERFACE)) .
堆栈跟踪 - 在 System.StubHelpers.StubHelpers.GetCOMIPFromRCW(Object objSrc, IntPtr pCPCMD, IntPtr& ppTarget, Boolean& pfNeedsRelease) 在 Shell32.ShellClass.NameSpace(Object vDir) 在 c:\Projects\PBSWebApplication\PBSWebApplication\PBSWebApplication\Test.aspx.cs:line 33 中的 PBSWebApplication.Test.Button1_OnClick(Object sender, EventArgs e) 在 System.Web.UI.WebControls.Button.OnClick(EventArgs e) 在 System.Web.UI.WebControls.Button.RaisePostBackEvent(字符串 eventArgument) 在 System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(字符串 eventArgument) 在 System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl,字符串 eventArgument) 在 System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData) 在 System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
如何解决这个问题?
谢谢。
【问题讨论】:
【参考方案1】:正如您所指出的,这是因为 Shell32 需要一个 STA 线程。如果您不能像在您的解决方案中那样简单地将您的应用程序配置为使用 STA 线程运行,那么作为替代方案,您可以创建一个单独的 STA 线程,使用它来运行 Shell32 代码,然后继续执行。例如this 是我在编写 SSIS 脚本任务时得到的结果,据我所知,它总是在 MTA 线程上运行。在我的情况下,我调用了 Shell32 (CopyHere) 的不同方法,但相同的逻辑将适用于您要调用的任何方法:
/// <summary>
/// Ugh! SSIS runs script tasks on MTA threads but Shell32 only wants to
/// run on STA thread. So start a new STA thread to call UnZip, block
/// till it's done, then return.
/// We use Shell32 since .net 2 doesn't have ZipFile and we prefer not to
/// ship other dlls as they normally need to be deployed to the GAC. So this
/// is easiest, although not very pretty.
/// </summary>
/// <param name="zipFile">File to unzip</param>
/// <param name="folderPath">Folder to put the unzipped files</param>
public static void UnZipFromMTAThread(string zipFile, string folderPath)
object[] args = new object[] zipFile, folderPath ;
if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
UnZip(args);
else
Thread staThread = new Thread(new ParameterizedThreadStart(UnZip));
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start(args);
staThread.Join();
/// <summary>
/// From http://www.fluxbytes.com/csharp/unzipping-files-using-shell32-in-c/ but with
/// args packed in object array so can be called from new STA Thread in UnZipFromMTAThread().
/// </summary>
/// <param name="param">object array containing: [string zipFile, string destinationFolderPath]</param>
private static void UnZip(object param)
object[] args = (object[]) param;
string zipFile = (string)args[0];
string folderPath = (string)args[1];
if (!File.Exists(zipFile))
throw new FileNotFoundException();
if (!Directory.Exists(folderPath))
Directory.CreateDirectory(folderPath);
Shell32.Shell objShell = new Shell32.Shell();
Shell32.Folder destinationFolder = objShell.NameSpace(folderPath);
Shell32.Folder sourceFile = objShell.NameSpace(zipFile);
foreach (var file in sourceFile.Items())
// Flags are: No progress displayed, Respond with 'Yes to All' for any dialog, no UI on error
// I added 1024 although not sure it's relevant with Zip files.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/bb787866%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
destinationFolder.CopyHere(file, 4 | 16 | 1024);
【讨论】:
太复杂了,看看下面 Maarten Kieft 的解决方案,效果不错 同样的错误也可能是由于多次使用相同的 shell 实例而导致的。在这种情况下,创建一个新实例将修复它。但是创建太多实例会产生另一个异常。这解决了我的问题,然后我遇到了我提到的另外两个。【参考方案2】:事实证明,将STAThread
属性添加到我的班级是一个简单的解决方案,问题就神奇地消失了。
这是我更新后的完整代码。
注意:这是简单的控制台应用程序。
class Program
[STAThread]
static void Main(string[] args)
Console.Title = "Extended file properties.";
List<string> arrHeaders = new List<string>();
Shell32.Shell shell = new Shell32.Shell();
Shell32.Folder objFolder;
objFolder = shell.NameSpace(@"C:\Users\Admin\Pictures\PBS Docs");
for (int i = 0; i < short.MaxValue; i++)
string header = objFolder.GetDetailsOf(null, i);
if (String.IsNullOrEmpty(header))
break;
arrHeaders.Add(header);
foreach (Shell32.FolderItem2 item in objFolder.Items())
for (int i = 0; i < arrHeaders.Count; i++)
Console.WriteLine("0\t1: 2", i, arrHeaders[i], objFolder.GetDetailsOf(item, i));
【讨论】:
【参考方案3】:我遇到了类似的问题,jeronevw 在这个论坛上的回答为我解决了这个问题: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b25e2b8f-141a-4a1c-a73c-1cb92f953b2b/instantiate-shell32shell-object-in-windows-8?forum=clr
public Shell32.Folder GetShell32NameSpaceFolder(Object folder)
Type shellAppType = Type.GetTypeFromProgID("Shell.Application");
Object shell = Activator.CreateInstance(shellAppType);
return (Shell32.Folder)shellAppType.InvokeMember("NameSpace",
System.Reflection.BindingFlags.InvokeMethod, null, shell, new object[] folder );
jeronevw 的所有学分
【讨论】:
我们需要将文件夹路径作为参数传递给这个函数吗?以上是关于使用 Shell32 获取文件扩展属性时出现异常的主要内容,如果未能解决你的问题,请参考以下文章
异常:获取对象 Jdbc 上的方法或属性 getConnection 时出现意外错误
在 iOS 8 Today Extension 中获取 parse.com 用户数据时出现异常
尝试签署 ANDROID 应用程序时出现异常 - “java.lang.SecurityException:Manifest 主要属性的签名文件摘要无效”