Исключение при использовании Shell32 для получения расширенных свойств файла
Я пытаюсь использовать 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);
}
Я получаю исключение следующим образом.
Сообщение - Невозможно привести объект COM типа "Shell32.ShellClass" к типу интерфейса "Shell32.IShellDispatch6". Эта операция завершилась неудачно, поскольку вызов QueryInterface для компонента COM для интерфейса с IID '{286E6F1B-7113-4355-9562-96B7E9D64C54}' завершился ошибкой из-за следующей ошибки: такой интерфейс не поддерживается (Исключение из HRESULT: 0x80004002 (E_NOINTERFACE)),
Трассировка стека - в System.StubHelpers.StubHelpers.GetCOMIPFromRCW(Объект objSrc, IntPtr pCPCMD, IntPtr& ppTarget, Boolean& pfNeedsRelease) в Shell32.ShellClass.NameSpace(Object vDir: объектный_каталог_узла): Объектный объектный объект (в виде объекта). \Projects\PBSWebApplication\PBSWebApplication\PBSWebApplication\Test.aspx.cs: строка 33 в System.Web.UI.WebControls.Button.OnClick(EventArgs e) в System.Web.UI.WebControls.Button.RaisePostBackEvent(событие String) System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument) в System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArguancePP). (NameValueCollection postData) в System.Web.UI.Page.ProcessRequestMain(логическое значение includeStagesBeforeAsyncPoint, логическое значение includeStagesAfterAsyncPoint)
Как это исправить?
Благодарю.
3 ответа
Как вы определили, это потому, что Shell32 требует потока STA. Если вы не можете просто настроить приложение для работы с потоком STA, как в вашем решении, в качестве альтернативы вы можете создать отдельный поток STA, использовать его для запуска кода Shell32 и продолжить выполнение. например, это то, чем я закончил, когда писал задачу сценариев 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);
}
}
У меня была похожая проблема, и ответ от 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
Это оказалось простым решением добавить атрибут 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}\t{1}: {2}", i, arrHeaders[i], objFolder.GetDetailsOf(item, i));
}
}
}