I have inherited the following extension method which creates an ImageSource object based on a file path
public static class ImageSourceExtensions
{
[StructLayout(LayoutKind.Sequential)]
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;
};
public const uint SHGFI_ICON = 0x100;
public const uint SHGFI_LARGEICON = 0x0;
[DllImport("shell32.dll")]
public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);
public static ImageSource GetIconFromFolder(this string filePath)
{
SHFILEINFO shinfo = new SHFILEINFO();
SHGetFileInfo(filePath, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo),
SHGFI_ICON | SHGFI_LARGEICON);
using (Icon i = Icon.FromHandle(shinfo.hIcon))
{
//Convert icon to a Bitmap source
ImageSource img = Imaging.CreateBitmapSourceFromHIcon(
i.Handle,
new Int32Rect(0, 0, i.Width, i.Height),
BitmapSizeOptions.FromEmptyOptions());
return img;
}
}
}
This extension method works perfectly for both folders and files 3310 times. On the 3311th method call, the following exception is thrown:
'Win32 handle that was passed to Icon is not valid or is the wrong
type'
This error is thrown when using the shinfo.hIcon property.
The error was initially thrown in my main application and was on a different file each time. I have since extracted this class into a new project and can get the same error to be thrown (after the same number of times) by doing this on a button click:
String path = #"C:\Generic Folder\Generic Document.pdf";
for (int i = 0; i <4000; i++)
{
ImageSource img = path.GetIconFromFolder();
}
Does anybody know of an obvious cause for this?
You are running out of HANDLEs. Windows has a finite number of handles and when you get one (in this case a handle to an icon), you are expected to release it after you no longer use it. If you don't, Windows will run out of free handles to throw around.
If SHGetFileInfo returns an icon handle in the hIcon member of the SHFILEINFO structure pointed to by psfi, you are responsible for freeing it with DestroyIcon when you no longer need it.
So you will need to PInvoke DestroyIcon and pass your shinfo.hIcon to it at the end of your function.
The disposal of the Icon created with FromHandle will not destroy the original handle:
When using this method, you must dispose of the original icon by using the DestroyIcon method in the Win32 API to ensure that the resources are released.
Related
I have some code which retrieves the 128bit NTFS Ids from files at specific paths. Then I attempted to retrieve the file path using this ID. The code works as long as when retrieving the paths I run as admin. This is not going to be possible in production. Unfortunately I am unable to call Marshal.GetLastWin32Error() because the System.AccessViolationException causes the application to completely crash. Below is the code to retrieve the paths.
public const int NO_PERMISSION = 0;
[DllImportAttribute("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeFileHandle CreateFile(
string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
[InAttribute()] System.IntPtr lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
[InAttribute()] System.IntPtr hTemplateFile
);
[DllImportAttribute("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeFileHandle OpenFileById(
IntPtr hVolumeHint,
FILE_ID_DESCRIPTOR lpFileId,
uint dwDesiredAccess,
uint dwShareMode,
[InAttribute()] System.IntPtr lpSecurityAttributes,
uint dwFlagsAndAttributes
);
public enum _FILE_ID_TYPE
{
FileIdType = 0,
ObjectIdType,
ExtendedFileIdType,
MaximumFileIdType
}
[StructLayout(LayoutKind.Explicit)]
public struct FILE_ID_128
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
[FieldOffset(0)]
public byte[] Identifier;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct FILE_ID_DESCRIPTOR
{
public uint dwSize;
public _FILE_ID_TYPE Type;
public FILE_ID_128 ExtendedFileId;
}
public static string GetObjectPathFromId(string pathToSection, string hexId)
{
// We need a file handle to the drive we are looking in
using (SafeFileHandle handle = Methods.CreateFile(
pathToSection,
Constants.NO_PERMISSION,
Constants.NO_PERMISSION,
IntPtr.Zero,
Constants.OPEN_EXISTING,
0x02000000 | 0x00000080,
IntPtr.Zero))
{
// Build descriptor
FILE_ID_DESCRIPTOR descriptor = new FILE_ID_DESCRIPTOR();
descriptor.dwSize = (uint)Marshal.SizeOf(descriptor);
descriptor.Type = _FILE_ID_TYPE.ExtendedFileIdType;
descriptor.ExtendedFileId.Identifier = StringToByteArrayFastest(hexId);
using (SafeFileHandle actualFile = OpenFileById(handle.DangerousGetHandle(), descriptor,
Constants.NO_PERMISSION, Constants.NO_PERMISSION,
IntPtr.Zero, 0))
{
if (actualFile.IsInvalid)
return "";
// Buffer for the path, this should be way big enough
int sizeOfBuffer = 1024;
// Allocate a buffer
IntPtr pointer = Marshal.AllocHGlobal(sizeOfBuffer);
uint size = (uint)sizeOfBuffer;
uint returnValue = GetFinalPathNameByHandleW(actualFile.DangerousGetHandle(), pointer, size, 0);
// Copy it into a managed array
byte[] outPut = new byte[sizeOfBuffer];
Marshal.Copy(pointer, outPut, 0, (int)returnValue);
// Decode it
var str = Encoding.Unicode.GetString(outPut);
// Will be an empty string if the call fails
return str;
}
}
}
Again I want to specify - this code works perfectly when running as admin. The files are owned by the user, the user is able to delete, rename and move the files without any additional permissions.
Any help would be greatly appreciated thanks!
Edit1:
I implemented the answer found here How to handle AccessViolationException to successfully catch the exception. However even after doing this Marshal.GetLastWin32Error() returns 0. If anyone has any idea of how I can debug this type of issue please let me know.
Also it's still functioning when I run as admin, just not as a user.
Edit2:
Not sure if it's relevant - library with this code is building for .NET Standard 2.0 - Application using this library code is building for .NET Framework 4.6.2
I'm currently trying to accomplish the following:
For an SDK, which we provide to our customers, we want the SDK-developers to be able to provide external application calls, so that they can insert additional buttons. These buttons than will start an external application or open a file with the default application for it (Word for *.docx for example).
There should be some visual distinction between the different buttons, so our approach is to show the icon of the application to be called.
Now, there are three different kind of calls:
(The strings below would always be the value of ProcessStartInfo.FileName)
Calling an application providing the full application path, possibly with environement vars (e.g. "C:\Program Files\Internet Explorer\iexplore.exe" / "%ProgramFiles%\Internet Explorer\iexplore.exe")
Calling an application providing only the executable name, given the application can be found in the PATH Variable (e.g. "iexplore")
Opening a document, without providing an application to open it (e.g. "D:\test.html")
We are looking for a way, to find the appropriate Icon for any given call. For this we have to find the full application path of the application, which will be executed in any of the three ways above, but before we actually have started the Process
Is there a way to find the full path or the icon of a System.Diagnostics.Process or System.Diagnostics.ProcessStartInfo object, before the process has been started?
Important: We must not start the process before (could have side effects)
Example Code:
var process = new Process
{
StartInfo =
{
//applicationPath could be any of the stated above calls
FileName = Environment.ExpandEnvironmentVariables(applicationPath)
}
};
//we have to find the full path here, but MainModule is null as long as the process object has not yet started
var icon = Icon.ExtractAssociatedIcon(process.MainModule.FullPath)
Solution
Thanks to you guys I found my solution. The project linked here at CodeProject provides a solution for my exact problem, which works equally with programs and files and can provide the icon before starting the process. Thanks for the link #wgraham
If you want your UI to be visually-consistent with the rest of the user's machine, you may want to extract the icon from the file using Icon.ExtractAssociatedIcon(string path). This works under the WinForms/GDI world. Alternatively, this question addresses how to complete it with P/Invoke.
Your first two examples shouldn't be too hard to figure out using Environment.ExpandEnvironmentVariables. Your last one is the tougher one - the best bet seems to be using PInvoke to call AssocCreate. Adapted from the pinvoke page (http://www.pinvoke.net/default.aspx/shlwapi/AssocCreate.html):
public static class GetDefaultProgramHelper
{
public unsafe static string GetDefaultProgram(string ext)
{
try
{
object obj;
AssocCreate(
ref CLSID_QueryAssociations,
ref IID_IQueryAssociations,
out obj);
IQueryAssociations qa = (IQueryAssociations)obj;
qa.Init(
ASSOCF.INIT_DEFAULTTOSTAR,
ext, //".doc",
UIntPtr.Zero, IntPtr.Zero);
int size = 0;
qa.GetString(ASSOCF.NOTRUNCATE, ASSOCSTR.COMMAND,
"open", null, ref size);
StringBuilder sb = new StringBuilder(size);
qa.GetString(ASSOCF.NOTRUNCATE, ASSOCSTR.COMMAND,
"open", sb, ref size);
//Console.WriteLine(".doc is opened by : {0}", sb.ToString());
return sb.ToString();
}
catch(Exception e)
{
if((uint)Marshal.GetHRForException(e) == 0x80070483)
//Console.WriteLine("No command line is associated to .doc open verb.");
return null;
else
throw;
}
}
[DllImport("shlwapi.dll")]
extern static int AssocCreate(
ref Guid clsid,
ref Guid riid,
[MarshalAs(UnmanagedType.Interface)] out object ppv);
[Flags]
enum ASSOCF
{
INIT_NOREMAPCLSID = 0x00000001,
INIT_BYEXENAME = 0x00000002,
OPEN_BYEXENAME = 0x00000002,
INIT_DEFAULTTOSTAR = 0x00000004,
INIT_DEFAULTTOFOLDER = 0x00000008,
NOUSERSETTINGS = 0x00000010,
NOTRUNCATE = 0x00000020,
VERIFY = 0x00000040,
REMAPRUNDLL = 0x00000080,
NOFIXUPS = 0x00000100,
IGNOREBASECLASS = 0x00000200,
INIT_IGNOREUNKNOWN = 0x00000400
}
enum ASSOCSTR
{
COMMAND = 1,
EXECUTABLE,
FRIENDLYDOCNAME,
FRIENDLYAPPNAME,
NOOPEN,
SHELLNEWVALUE,
DDECOMMAND,
DDEIFEXEC,
DDEAPPLICATION,
DDETOPIC,
INFOTIP,
QUICKTIP,
TILEINFO,
CONTENTTYPE,
DEFAULTICON,
SHELLEXTENSION
}
enum ASSOCKEY
{
SHELLEXECCLASS = 1,
APP,
CLASS,
BASECLASS
}
enum ASSOCDATA
{
MSIDESCRIPTOR = 1,
NOACTIVATEHANDLER,
QUERYCLASSSTORE,
HASPERUSERASSOC,
EDITFLAGS,
VALUE
}
[Guid("c46ca590-3c3f-11d2-bee6-0000f805ca57"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IQueryAssociations
{
void Init(
[In] ASSOCF flags,
[In, MarshalAs(UnmanagedType.LPWStr)] string pszAssoc,
[In] UIntPtr hkProgid,
[In] IntPtr hwnd);
void GetString(
[In] ASSOCF flags,
[In] ASSOCSTR str,
[In, MarshalAs(UnmanagedType.LPWStr)] string pwszExtra,
[Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwszOut,
[In, Out] ref int pcchOut);
void GetKey(
[In] ASSOCF flags,
[In] ASSOCKEY str,
[In, MarshalAs(UnmanagedType.LPWStr)] string pwszExtra,
[Out] out UIntPtr phkeyOut);
void GetData(
[In] ASSOCF flags,
[In] ASSOCDATA data,
[In, MarshalAs(UnmanagedType.LPWStr)] string pwszExtra,
[Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)] out byte[] pvOut,
[In, Out] ref int pcbOut);
void GetEnum(); // not used actually
}
static Guid CLSID_QueryAssociations = new Guid("a07034fd-6caa-4954-ac3f-97a27216f98a");
static Guid IID_IQueryAssociations = new Guid("c46ca590-3c3f-11d2-bee6-0000f805ca57");
}
You could call this using string filePathToProgram = GetDefaultProgramHelper.GetDefaultProgram(".docx");
The Process class can do exactly what you want.
Environment Variables like %ProgramFiles% need to be expanded first with Environment.ExpandEnvironmentVariables(string).
1.
using System.IO;
using System.Diagnostics;
string iexplore = #"C:\Program Files\Internet Explorer\iexplore.exe");
string iexploreWithEnvVars = Environment.ExpandEnvironmentVariables(#"%ProgramFiles%\Internet Explorer\iexplore.exe");
2.
public static string FindFileInPath(string name)
{
foreach (string path in Environment.ExpandEnvironmentVariables("%path%").Split(';'))
{
string filename;
if (File.Exists(filename = Path.Combine(path, name)))
{
return filename; // returns the absolute path if the file exists
}
}
return null; // will return null if it didn't find anything
}
3.
Process.Start("D:\\test.html");
You want to put your code into try-catch blocks as well since Process.Start will throw an Exception if the file doesn't exist.
Edit: Updated the code, thanks to Dan Field for pointing out that I missed the meaning of the question. :/
I'm using SHGetFileInfo to retrieve certain information about a file or directory, i.e. the icon or the description the file extension.
When retrieving the description of the file extension, the string returned from SHGetFileInfo is missing the first four characters.
For example, the description of a .pdf file is Adobe Acrobat Document but I only get e Acrobat Document or the description of a .exe file is Anwendung (as I'm German, in English it's Application i suppose), but I only get ndung.
I'm using
public static string GetFileTypeDescription(this FileInfo file)
{
SHFILEINFO shFileInfo;
if (SHGetFileInfo(
file.Extension,
SHGFI_FILE_ATTRIBUTE_NORMAL,
out shFileInfo,
(uint)Marshal.SizeOf(typeof(SHFILEINFO)),
SHGFI_USEFILEATTRIBUTES | SHGFI_TYPENAME)
!= IntPtr.Zero)
{
return shFileInfo.szTypeName;
}
return null;
}
with the usual implementation of SHGetFileInfo:
[DllImport("shell32.dll")]
internal static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, out SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);
[StructLayout(LayoutKind.Sequential)]
internal struct SHFILEINFO
{
public IntPtr hIcon;
public IntPtr iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
};
//I omitted all flags which are not used above
private const uint SHGFI_FILE_ATTRIBUTE_NORMAL = 0x80,
SHGFI_USEFILEATTRIBUTES = 0x10,
SHGFI_TYPENAME = 0x400;
What is wrong? Did I miss something? Or how can I retrieve the full description?
The iIcon field in the C++ struct has type int. On Windows that is a 4 byte signed integer. It corresponds to int in C#.
You have declared the field as IntPtr in your C# code. That is a signed integer, the same size as a pointer. So it is 4 bytes in 32 bit code, and 8 bytes in 64 bit code. It seems likely that you are running 64 bit code.
So, the error is the declaration of this field which simply has the wrong type. The solution is to change the type of iIcon to int.
Change IntPtr on the iIcon to int. That should work. Or use x86 as the platform target. Either of the two.
I tried many times to load a Bitmap from native resources but I failed big time.
The image I'm trying to load is like this into the native resources:
http://i.stack.imgur.com/x7Jr8.jpg
The code I wrote the image with is this:
[DllImport("kernel32.dll")]
public static extern int UpdateResource(uint hUpdate, SqlDbType lpStructure, uint lpName, ushort wLanguage, int lpData, UIntPtr cbData);
[DllImport("kernel32.dll")]
public static extern UIntPtr BeginUpdateResource(string pFileName, bool bDeleteExistingResources);
[DllImport("kernel32.dll")]
public static extern bool EndUpdateResource(UIntPtr hUpdate, bool fDiscard);
public static bool WriteResource(string FileName, byte[] data, uint Name)
{
UIntPtr ptr = BeginUpdateResource(FileName, false);
GCHandle Handle = GCHandle.Alloc(data, GCHandleType.Pinned);
uint size = Convert.ToUInt32(Convert.ToUInt64(data.Length));
UIntPtr pt = new UIntPtr(size);
UpdateResource(ptr.ToUInt32(), SqlDbType.Bit, Name, 0, Handle.AddrOfPinnedObject().ToInt32(), (UIntPtr)Convert.ToUInt64(data.Length));
EndUpdateResource(ptr, false);
return true;
}
I tried to use LoadImage and LoadBitmap API calls but I failed big time.
Can you provide me with a snippet on how I can do this conversion? I want to fetch it from the native resources and store it into a Bitmap class (the common System.Drawing.Bitmap class of .NET framework). I'd appreciate any help.
I'm not sure if I've understood you correctly. But I could imagine you are searching for something like this:
System.Reflection.Assembly thisExe;
thisExe = System.Reflection.Assembly.GetExecutingAssembly();
System.IO.Stream file =
thisExe.GetManifestResourceStream("AssemblyName.ImageFile.jpg");
this.pictureBox1.Image = Image.FromStream(file);
first question here. I'm developing a program in C# (.NET 3.5) that displays files in a listview. I'd like to have the "large icon" view display the icon that Windows Explorer uses for that filetype, otherwise I'll have to use some existing code like this:
private int getFileTypeIconIndex(string fileName)
{
string fileLocation = Application.StartupPath + "\\Quarantine\\" + fileName;
FileInfo fi = new FileInfo(fileLocation);
switch (fi.Extension)
{
case ".pdf":
return 1;
case ".doc": case ".docx": case ".docm": case ".dotx":case ".dotm": case ".dot":case ".wpd": case ".wps":
return 2;
default:
return 0;
}
}
The above code returns an integer that is used to select an icon from an imagelist that I populated with some common icons. It works fine but I'd need to add every extension under the sun! Is there a better way? Thanks!
You might find the use of Icon.ExtractAssociatedIcon a much simpler (an managed) approach than using SHGetFileInfo. But watch out: two files with the same extension may have different icons.
The file icons are held in the registry. It's a little convoluted but it works something like
Take the file extension and lookup
the registry entry for it, for
example .DOC Get the default value
for that registry setting,
"Word.Document.8"
Now lookup that
value in the registry.
Look at the
default value for the "Default Icon"
registry key, in this case,
C:\Windows\Installer{91120000-002E-0000-0000-0000000FF1CE}\wordicon.exe,1
Open the file and get the icon,
using any number after the comma as
the indexer.
There is some sample code at on CodeProject
I used the following solution from codeproject in one of recent my projects
Obtaining (and managing) file and folder icons using SHGetFileInfo in C#
The demo project is pretty self explanatory but basically you just have to do:
private System.Windows.Forms.ListView FileView;
private ImageList _SmallImageList = new ImageList();
private ImageList _LargeImageList = new ImageList();
private IconListManager _IconListManager;
in the constructor:
_SmallImageList.ColorDepth = ColorDepth.Depth32Bit;
_LargeImageList.ColorDepth = ColorDepth.Depth32Bit;
_SmallImageList.ImageSize = new System.Drawing.Size(16, 16);
_LargeImageList.ImageSize = new System.Drawing.Size(32, 32);
_IconListManager = new IconListManager(_SmallImageList, _LargeImageList);
FileView.SmallImageList = _SmallImageList;
FileView.LargeImageList = _LargeImageList;
and then finally when you create the ListViewItem:
ListViewItem item = new ListViewItem(file.Name, _IconListManager.AddFileIcon(file.FullName));
Worked great for me.
Edit: Here is a version without PInvoke.
[StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
{
public IntPtr hIcon;
public IntPtr iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
};
public const uint SHGFI_ICON = 0x100;
public const uint SHGFI_LARGEICON = 0x0; // 'Large icon
public const uint SHGFI_SMALLICON = 0x1; // 'Small icon
[DllImport("shell32.dll")]
public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);
[DllImport("User32.dll")]
public static extern int DestroyIcon(IntPtr hIcon);
public static System.Drawing.Icon GetSystemIcon(string sFilename)
{
//Use this to get the small Icon
IntPtr hImgSmall; //the handle to the system image list
//IntPtr hImgLarge; //the handle to the system image list
APIFuncs.SHFILEINFO shinfo = new APIFuncs.SHFILEINFO();
hImgSmall = APIFuncs.SHGetFileInfo(sFilename, 0, ref shinfo,
(uint)Marshal.SizeOf(shinfo), APIFuncs.SHGFI_ICON | APIFuncs.SHGFI_SMALLICON);
//Use this to get the large Icon
//hImgLarge = SHGetFileInfo(fName, 0,
// ref shinfo, (uint)Marshal.SizeOf(shinfo),
// Win32.SHGFI_ICON | Win32.SHGFI_LARGEICON);
//The icon is returned in the hIcon member of the shinfo struct
System.Drawing.Icon myIcon = (System.Drawing.Icon)System.Drawing.Icon.FromHandle(shinfo.hIcon).Clone();
DestroyIcon(shinfo.hIcon); // Cleanup
return myIcon;
}