Estoy tratando de usar Shell32 para obtener propiedades de archivo extendidas en C#.
Mi código para esto es el siguiente.
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); } Recibo una excepción de la siguiente manera.
Mensaje: no se puede convertir el objeto COM del tipo 'Shell32.ShellClass' al tipo de interfaz 'Shell32.IShellDispatch6'. Esta operación falló porque la llamada QueryInterface en el componente COM para la interfaz con IID '{286E6F1B-7113-4355-9562-96B7E9D64C54}' falló debido al siguiente error: No se admite dicha interfaz (Excepción de HRESULT: 0x80004002 (E_NOINTERFACE)) .
Seguimiento de pila: en System.StubHelpers.StubHelpers.GetCOMIPFromRCW(Object objSrc, IntPtr pCPCMD, IntPtr& ppTarget, Boolean& pfNeedsRelease) en Shell32.ShellClass.NameSpace(Object vDir) en PBSWebApplication.Test.Button1_OnClick(Object sender, EventArgs e) en c: \Projects\PBSWebApplication\PBSWebApplication\PBSWebApplication\Test.aspx.cs:line 33 en System.Web.UI.WebControls.Button.OnClick(EventArgs e) en System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument) en System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument) en System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) en System.Web.UI.Page.RaisePostBackEvent (NameValueCollection postData) en System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
¿Cómo arreglar esto?
Gracias.
Como identificó, esto se debe a que Shell32 requiere un subproceso STA. Si no puede simplemente configurar su aplicación para que se ejecute con un subproceso STA como en su solución, entonces, como alternativa, puede crear un subproceso STA separado, usarlo para ejecutar el código Shell32 y luego continuar con la ejecución. por ejemplo, esto es con lo que terminé al escribir una tarea de secuencia de comandos SSIS que, según tengo entendido, siempre se ejecuta en el subproceso MTA. En mi caso, estoy llamando a un método diferente de Shell32 (CopyHere), pero la misma lógica se aplicaría al método que desee llamar:
/// <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); } }Resultó ser una solución simple agregar el atributo STAThread a mi clase y el problema desapareció mágicamente.
Aquí está mi código completo después de la actualización.
Nota: Es una aplicación de consola simple.
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)); } } } }Tuve un problema similar y la respuesta de jeronevw en este foro me lo solucionó: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b25e2b8f-141a-4a1c-a73c-1cb92f953b2b/instantiate-shell32shell -objeto-en-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 }); }Todos los creditos de jeronevw