I've been trying to read the current power state of an USB port (D0/D1/D2/D3). I haven't been able to find much information on how to access the actual state. Here's a description of the USB Device Power States on Microsoft docs. It has a whole section on changing, but really didn't get how to read it. I have very little experience with working on the Windows and hardware level, so excuse me if it is obvious.
I've also found this Microsoft debug application written in C called USBView. If you install it and open the USB tree, the first information displayed for individual ports is its power state.
e.g.
Device Power State: PowerDeviceD2
It has source available on GitHub, but the files are over 5000 lines long and I can't navigate C code good enough to tell how to actually read the power state.
I'm trying to implement this into a C# application, but help in any language will be appreciated!
After lots of and lots of digging in the USBView source code, I figured out you need to do the following:
Get the handle to the device info set using
IntPtr deviceInfoSet = SetupDiGetClassDevs(ref classGuid, null, IntPtr.Zero, 0x00000002 | 0x00000010)
where the classGuid for USB devices is "A5DCBF10-6530-11D2-901F-00C04FB951ED"
Get the individual device info using
SetupDiEnumDeviceInfo(deviceInfoSet, index, ref deviceInfoData)
where deviceInfoData is an instance of the SP_DEVINFO_DATA struct with the value of cbSize inicializace to 28. (The device info will be then stored in this struct.)
You start with index = 0 and then increment until the method returns false and Marshal.GetLastWin32Error() returns 259 (ERROR_NO_MORE_ITEMS) to get all the devices.
You then retrieve the power property using SetupDiGetDeviceRegistryProperty.
You can either:
A. pass in CM_POWER_DATA (then you should replace byte[] in the DllImport method signature with ref CM_POWER_DATA)
B. pass in a byte array and then parse the byte array to CM_POWER_DATA (code from this answer proved very good at it).
Here I'm showing option B - passing in byte array. The power data will be in the data variable. (Conversion skipped.)
//sizeof evaluates to 56, if you want to hardcode it
byte[] data = new byte[Marshal.SizeOf<CM_POWER_DATA>()];
SetupDiGetDeviceRegistryProperty(
deviceInfoSet,
ref deviceInfoData,
0x0000001E, //the property SPDRP_DEVICE_POWER_DATA
out uint type,
data,
data.Length,
out uint size)
);
You can then query additional information with the SetupDiGetDeviceRegistryProperty function, like the HardwareID and description.
Here is my code.
Don't forget
using System.Runtime.InteropServices;
Constants of various GUIDs and properities
public const uint SPDRP_DEVICEDESC = (0x00000000); // DeviceDesc (R/W)
public const uint SPDRP_HARDWAREID = (0x00000001); // HardwareID (R/W)
public const uint SPDRP_COMPATIBLEIDS = (0x00000002); // CompatibleIDs (R/W)
public const uint SPDRP_UNUSED0 = (0x00000003); // unused
public const uint SPDRP_SERVICE = (0x00000004); // Service (R/W)
public const uint SPDRP_UNUSED1 = (0x00000005); // unused
public const uint SPDRP_UNUSED2 = (0x00000006); // unused
public const uint SPDRP_CLASS = (0x00000007); // Class (R--tied to ClassGUID)
public const uint SPDRP_CLASSGUID = (0x00000008); // ClassGUID (R/W)
public const uint SPDRP_DRIVER = (0x00000009); // Driver (R/W)
public const uint SPDRP_CONFIGFLAGS = (0x0000000A); // ConfigFlags (R/W)
public const uint SPDRP_MFG = (0x0000000B); // Mfg (R/W)
public const uint SPDRP_FRIENDLYNAME = (0x0000000C); // FriendlyName (R/W)
public const uint SPDRP_LOCATION_INFORMATION = (0x0000000D); // LocationInformation (R/W)
public const uint SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = (0x0000000E); // PhysicalDeviceObjectName (R)
public const uint SPDRP_CAPABILITIES = (0x0000000F); // Capabilities (R)
public const uint SPDRP_UI_NUMBER = (0x00000010); // UiNumber (R)
public const uint SPDRP_UPPERFILTERS = (0x00000011); // UpperFilters (R/W)
public const uint SPDRP_LOWERFILTERS = (0x00000012); // LowerFilters (R/W)
public const uint SPDRP_BUSTYPEGUID = (0x00000013); // BusTypeGUID (R)
public const uint SPDRP_LEGACYBUSTYPE = (0x00000014); // LegacyBusType (R)
public const uint SPDRP_BUSNUMBER = (0x00000015); // BusNumber (R)
public const uint SPDRP_ENUMERATOR_NAME = (0x00000016); // Enumerator Name (R)
public const uint SPDRP_SECURITY = (0x00000017); // Security (R/W, binary form)
public const uint SPDRP_SECURITY_SDS = (0x00000018); // Security (W, SDS form)
public const uint SPDRP_DEVTYPE = (0x00000019); // Device Type (R/W)
public const uint SPDRP_EXCLUSIVE = (0x0000001A); // Device is exclusive-access (R/W)
public const uint SPDRP_CHARACTERISTICS = (0x0000001B); // Device Characteristics (R/W)
public const uint SPDRP_ADDRESS = (0x0000001C); // Device Address (R)
public const uint SPDRP_UI_NUMBER_DESC_FORMAT = (0X0000001D); // UiNumberDescFormat (R/W)
public const uint SPDRP_DEVICE_POWER_DATA = (0x0000001E); // Device Power Data (R)
public const uint SPDRP_REMOVAL_POLICY = (0x0000001F); // Removal Policy (R)
public const uint SPDRP_REMOVAL_POLICY_HW_DEFAULT = (0x00000020); // Hardware Removal Policy (R)
public const uint SPDRP_REMOVAL_POLICY_OVERRIDE = (0x00000021); // Removal Policy Override (RW)
public const uint SPDRP_INSTALL_STATE = (0x00000022); // Device Install State (R)
public const uint SPDRP_LOCATION_PATHS = (0x00000023); // Device Location Paths (R)
public const uint SPDRP_BASE_CONTAINERID = (0x00000024); // Base ContainerID (R)
public const uint SPDRP_MAXIMUM_PROPERTY = (0x00000025); // Upper bound on ordinals
public const string GUID_DEVINTERFACE_USB_HUB = "f18a0e88-c30c-11d0-8815-00a0c906bed8";
public const string GUID_DEVINTERFACE_USB_DEVICE = "A5DCBF10-6530-11D2-901F-00C04FB951ED";
public const string GUID_DEVINTERFACE_USB_HOST_CONTROLLER = "3ABF6F2D-71C4-462a-8A92-1E6861E6AF27";
public const string GUID_USB_WMI_STD_DATA = "4E623B20-CB14-11D1-B331-00A0C959BBD2";
public const string GUID_USB_WMI_STD_NOTIFICATION = "4E623B20-CB14-11D1-B331-00A0C959BBD2";
public const string GUID_USB_WMI_DEVICE_PERF_INFO = "66C1AA3C-499F-49a0-A9A5-61E2359F6407";
public const string GUID_USB_WMI_NODE_INFO = "{9C179357-DC7A-4f41-B66B-323B9DDCB5B1}";
public const string GUID_USB_WMI_TRACING = "3a61881b-b4e6-4bf9-ae0f-3cd8f394e52f";
public const string GUID_USB_TRANSFER_TRACING = "{681EB8AA-403D-452c-9F8A-F0616FAC9540}";
public const string GUID_USB_PERFORMANCE_TRACING = "{D5DE77A6-6AE9-425c-B1E2-F5615FD348A9}";
public const string GUID_USB_WMI_SURPRISE_REMOVAL_NOTIFICATION = "{9BBBF831-A2F2-43B4-96D1-86944B5914B3}";
Structs and enums:
public enum DEVICE_POWER_STATE {
PowerDeviceUnspecified,
PowerDeviceD0,
PowerDeviceD1,
PowerDeviceD2,
PowerDeviceD3,
PowerDeviceMaximum
}
public enum SYSTEM_POWER_STATE {
PowerSystemUnspecified,
PowerSystemWorking,
PowerSystemSleeping1,
PowerSystemSleeping2,
PowerSystemSleeping3,
PowerSystemHibernate,
PowerSystemShutdown,
PowerSystemMaximum
}
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct SP_DEVINFO_DATA {
public UInt32 cbSize;
public Guid ClassGuid;
public UInt32 DevInst;
public IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct CM_POWER_DATA {
public uint PD_Size;
public DEVICE_POWER_STATE PD_MostRecentPowerState;
public uint PD_Capabilities;
public uint PD_D1Latency;
public uint PD_D2Latency;
public uint PD_D3Latency;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)]
public DEVICE_POWER_STATE[] PD_PowerStateMapping;
public SYSTEM_POWER_STATE PD_DeepestSystemWake;
}
DllImports
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool SetupDiGetDeviceRegistryPropertyA(
IntPtr DeviceInfoSet,
ref SP_DEVINFO_DATA DeviceInfoData,
uint Property,
out RegistryDataType PropertyRegDataType,
byte[] PropertyBuffer,
//ref CM_POWER_DATA PropertyBuffer,
uint PropertyBufferSize,
out UInt32 RequiredSize
);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetupDiGetClassDevsA(
ref Guid ClassGuid,
[MarshalAs(UnmanagedType.LPTStr)] string Enumerator,
IntPtr hwndParent,
uint Flags
);
[DllImport("setupapi.dll", SetLastError=true)]
private static extern bool SetupDiEnumDeviceInfo(
IntPtr DeviceInfoSet,
uint MemberIndex,
ref SP_DEVINFO_DATA DeviceInfoData
);
Various function for making the retrieve a bit fancier and easier to use. The main one and the one I've described above is the first one - GetInfoWithSetupDi. (You need to pass the GUID_DEVINTERFACE_USB_DEVICE to it.)
/// <summary>
/// Gets information about devices in the device class including power state.
/// </summary>
/// <param name="classGuid">The GUID of the class in which to get information about devices from.</param>
/// <returns></returns>
public static List<DeviceInfo> GetInfoWithSetupDi(Guid classGuid) {
List<DeviceInfo> deviceInfos = new List<DeviceInfo>();
IntPtr deviceInfoSet = SetupDiGetClassDevsA(ref classGuid, null, IntPtr.Zero, 0x00000002 | 0x00000010);
uint index = 0;
int error = 0;
while (error == 0) {
DeviceInfo curDevice = new DeviceInfo {
//Initializing SP_DEVINFO_DATA to be passed to SetupDiEnumDeviceInfo.
deviceInfoData = new SP_DEVINFO_DATA {
cbSize = 28
}
};
//Retrieves the information about the specified device.
bool success = SetupDiEnumDeviceInfo(deviceInfoSet, index, ref curDevice.deviceInfoData);
index++;
error = Marshal.GetLastWin32Error();
if (error == 259)
{ break; }
if (!success)
{ throw new Exception("Native method call error: " + error.ToString()); }
//Only add device after it was at least successfully retrieved.
deviceInfos.Add(curDevice);
//Retrieving individual information.
RegistryData localGetData(uint property) => GetData(deviceInfoSet, curDevice.deviceInfoData, property);
curDevice.hardwareId = (string[]) localGetData(SPDRP_HARDWAREID).parsed;
try {
curDevice.description = (string)localGetData(SPDRP_DEVICEDESC).parsed;
} catch {
curDevice.description = "Description not set.";
}
curDevice.cmPowerData = MarshallingUtils.FromBytes<CM_POWER_DATA>(
localGetData(SPDRP_DEVICE_POWER_DATA).data
);
}
return deviceInfos;
}
/// <summary>
/// Gets the required size in bytes for the given property.
/// </summary>
/// <param name="deviceInfoSet">A handle to the set of devices.</param>
/// <param name="property">The property for which the get the required size.</param>
private static uint GetRequiredSize(IntPtr deviceInfoSet, uint Property) {
SP_DEVINFO_DATA deviceInfoData = new SP_DEVINFO_DATA {
cbSize = 28
};
ThrowErrorIfNotSuccessful(SetupDiEnumDeviceInfo(deviceInfoSet, 0, ref deviceInfoData));
return GetRequiredSize(deviceInfoSet, Property, deviceInfoData);
}
/// <summary>
/// Gets the required size in bytes for the given property.
/// </summary>
/// <param name="deviceInfoSet">A handle to the set of devices.</param>
/// <param name="property">The property for which the get the required size.</param>
/// <param name="deviceInfoData">Info of a device.</param>
/// <returns></returns>
private static uint GetRequiredSize(IntPtr deviceInfoSet, uint property, SP_DEVINFO_DATA deviceInfoData) {
ThrowErrorIfNotSuccessful(
SetupDiGetDeviceRegistryPropertyA(
deviceInfoSet,
ref deviceInfoData,
property,
out RegistryDataType type,
new byte[1000],
1000,
out uint size)
);
return size;
}
/// <summary>
/// Gets the property using SetupDi of the device on the given index in the given device class.
/// </summary>
/// <param name="classGuid">The GUID of the device class.</param>
/// <param name="index">The index of the device.</param>
/// <param name="property">The property to retrieve.</param>
/// <returns></returns>
public static RegistryData GetData(Guid classGuid, uint index, uint property) {
IntPtr deviceInfoSet = SetupDiGetClassDevsA(ref classGuid, null, IntPtr.Zero, 0x00000002 | 0x00000010);
SP_DEVINFO_DATA deviceInfoData = new SP_DEVINFO_DATA {
cbSize = 28
};
ThrowErrorIfNotSuccessful(SetupDiEnumDeviceInfo(deviceInfoSet, index, ref deviceInfoData));
return GetData(deviceInfoSet, deviceInfoData, property);
}
/// <summary>
/// Gets the specified property using SetupDi of the device described in deviceInfoData in the given device info set.
/// The size is retrieved automatically.
/// </summary>
/// <param name="deviceInfoSet">A handle to the device info set.</param>
/// <param name="deviceInfoData">Description of the device.</param>
/// <param name="property">The property to retrieve.</param>
/// <returns></returns>
public static RegistryData GetData(IntPtr deviceInfoSet, SP_DEVINFO_DATA deviceInfoData, uint property) {
uint size = GetRequiredSize(deviceInfoSet, property, deviceInfoData);
return GetData(deviceInfoSet, deviceInfoData, property, size);
}
/// <summary>
/// Gets the specified property using SetupDi of the device described in deviceInfoData in the given device info set.
/// </summary>
/// <param name="deviceInfoSet">A handle to the device info set.</param>
/// <param name="deviceInfoData">Description of the device.</param>
/// <param name="property">The property to retrieve.</param>
/// <returns></returns>
public static RegistryData GetData(IntPtr deviceInfoSet, SP_DEVINFO_DATA deviceInfoData, uint property, uint size) {
byte[] data = new byte[size];
ThrowErrorIfNotSuccessful(
SetupDiGetDeviceRegistryPropertyA(
deviceInfoSet,
ref deviceInfoData,
property,
out RegistryDataType type,
data,
size,
out uint dummysize)
);
return new RegistryData(type, data);
}
/// <summary>
/// Method used to wrap native DLL calls. Throws the last system error when the call is unsuccessful.
/// </summary>
/// <param name="success">Return value of the native method. ( Usually used as ThrowErrorIfNotSuccessful(MethodCall()); )</param>
private static void ThrowErrorIfNotSuccessful(bool success) {
if (!success) {
throw new Exception("Native method call error: " + Marshal.GetLastWin32Error().ToString());
}
}
Generic version of the byte array <-> struct parser mentioned above.
public static class MarshallingUtils {
public static byte[] GetBytes<T>(T str) where T : struct {
int size = Marshal.SizeOf(str);
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(str, ptr, true);
Marshal.Copy(ptr, arr, 0, size);
Marshal.FreeHGlobal(ptr);
return arr;
}
public static T FromBytes<T>(byte[] arr) where T : struct {
T str = new T();
int size = Marshal.SizeOf(str);
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(arr, 0, ptr, size);
str = (T)Marshal.PtrToStructure(ptr, str.GetType());
Marshal.FreeHGlobal(ptr);
return str;
}
}
Helper class for parsing the retrieved registry data.
public struct RegistryData {
public readonly RegistryDataType type;
public readonly byte[] data;
public readonly object parsed;
public RegistryData(RegistryDataType type, byte[] data) {
this.type = type;
this.data = data;
this.parsed = ParseData(type, data);
}
public static string ParseString(byte[] data) {
string s = "";
foreach (byte b in data) {
if (b == 0) {
break;
}
s += (char)b;
}
return s;
}
public static string[] ParseMultiString(byte[] data) {
List<string> list = new List<string>();
string current = "";
bool terminator = false;
foreach (byte b in data) {
if (b == 0) {
if (terminator) {
break;
}
else {
terminator = true;
//Start a new string.
list.Add(current);
current = "";
}
}
else {
terminator = false;
current += (char)b;
}
}
return list.ToArray();
}
private static object ParseData(RegistryDataType type, byte[] data) {
switch (type) {
case RegistryDataType.REG_SZ:
return (object)ParseString(data);
case RegistryDataType.REG_MULTI_SZ:
return (object)ParseMultiString(data);
default: return null;
}
}
public override string ToString() {
switch (type) {
case RegistryDataType.REG_SZ: return (string)parsed;
case RegistryDataType.REG_MULTI_SZ: return String.Join(";", (List<string>)parsed);
default: return null;
}
}
}
And one last enum for the retrieved registry data type.
public enum RegistryDataType {
REG_NONE = (0), // No value type
REG_SZ = (1), // Unicode nul terminated string
REG_EXPAND_SZ = (2), // Unicode nul terminated string
// = (with environment variable references)
REG_BINARY = (3), // Free form binary
//REG_DWORD = (4), // 32-bit number
REG_DWORD_LITTLE_ENDIAN = (4), // 32-bit number = (same as REG_DWORD)
REG_DWORD_BIG_ENDIAN = (5), // 32-bit number
REG_LINK = (6), // Symbolic Link = (unicode)
REG_MULTI_SZ = (7), // Multiple Unicode strings
REG_RESOURCE_LIST = (8), // Resource list in the resource map
REG_FULL_RESOURCE_DESCRIPTOR = (9), // Resource list in the hardware description
REG_RESOURCE_REQUIREMENTS_LIST = (10),
//REG_QWORD = (11), // 64-bit number
REG_QWORD_LITTLE_ENDIAN = (11), // 64-bit number = (same as REG_QWORD)
}
And also a class to hold the info.
public class DeviceInfo {
public SP_DEVINFO_DATA deviceInfoData;
public string[] hardwareId;
public string description;
public CM_POWER_DATA cmPowerData;
}
Related
Does anyone have any C# code to accept Drag and Drop from Outlook to a Winforms application that works under .Net Framework 4.5 or later please?
I have some code that's been in use for about 12 years now, which includes the ability to drag and drop items, including emails from Outlook.
The code has been running perfectly when compiled with the Target Framework set to .Net Framework 4, however some new features I've been working on require .Net Framework 4.5 or later. This however prevents the Drag and Drop from Outlook working.
The code is quite convoluted, it does a ton of stuff unrelated to the issue to determine where the file goes, and who it shows up for etc, so I won't include all that, but the point at which it breaks is nice and simple...
public string ImportEmail(DragEventArgs e)
{
string strResult = string.Empty;
var dataObject = new OutlookDataObject(e.Data);
var filenames = (string[]) dataObject.GetData("FileGroupDescriptor");
Under .NET 4, the last line above returns the filename(s) as expected.
Under .NET 4.5 or later, the last line above returns null.
I tested it under .NET 4.5, 4.5.1, 4.5.2, 4.6, 4.6.1, 4.6.2 and 4.7.2 (all the versions I have installed), it doesn't work on any of them.
I've been trawling around for a couple of days trying out all the Drag and Drop code I could find, and none of it seems to work under .Net 4.5 or later.
Below is the complete code for a test form I did, using code I dug up from here and elsewhere. This code works perfectly under .Net Framework 4, whether you drag and drop a file or an email from Outlook, but neither works under .Net Framework 4.5 or later (tested all the same versions again).
using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Reflection;
using System.Windows.Forms;
namespace OutlookDragNDropTest
{
public partial class OutlookDragNDropTest : Form
{
public OutlookDragNDropTest()
{
InitializeComponent();
}
private void Form1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Copy;
}
// or this tells us if it is an Outlook attachment drop
else if (e.Data.GetDataPresent("FileGroupDescriptor"))
{
e.Effect = DragDropEffects.Copy;
}
// or none of the above
else
{
e.Effect = DragDropEffects.None;
}
}
private void Form1_DragDrop(object sender, DragEventArgs e)
{
try
{
//wrap standard IDataObject in OutlookDataObject
OutlookDataObject dataObject = new OutlookDataObject(e.Data);
//get the names and data streams of the files dropped
string[] filenames = (string[])dataObject.GetData("FileGroupDescriptor");
MemoryStream[] filestreams = (MemoryStream[])dataObject.GetData("FileContents");
string tempPath = Path.GetTempPath();
for (int fileIndex = 0; fileIndex < filenames.Length; fileIndex++)
{
//use the fileindex to get the name and data stream
string filename = tempPath + filenames[fileIndex];
MemoryStream filestream = filestreams[fileIndex];
//save the file stream using its name to the application path
FileStream outputStream = File.Create(filename);
filestream.WriteTo(outputStream);
outputStream.Close();
MessageBox.Show("Output to " + filename);
}
}
catch (Exception ex)
{
MessageBox.Show("Error : " + ex.ToString());
}
}
}
}
(Ok, seems that code was too long for one post, the OutlookDataObject class will be in a reply below)...
The only other thing is the form itself, which is just a blank Winforms form, with the following Properties set:-
AllowDrop : True
DragEnter event : Form1_DragEnter
DragDrop event : Form1_DragDrop
If it's relevant, I'm using Visual Studio 2017 Professional, and running on Windows 10, although the Drag and Drop not working has also been reported by clients running Windows 7.
Solved it!
The issue turned out to be that .Net Framework 4 and lower use 32-bit pointers, and .Net Framework 4.5 and above use 64-bit pointers.
The ancient code I had, and the example code I provided, were both written on the assumption that pointers would only be 32-bit.
Changing the following two lines in the OutlookDataObject class (and making a similar change in my code) fixed it:-
From:-
IntPtr fileDescriptorPointer = (IntPtr)((int)fileGroupDescriptorAPointer + Marshal.SizeOf(fileGroupDescriptor.cItems));
fileDescriptorPointer = (IntPtr)((int)fileDescriptorPointer + Marshal.SizeOf(fileDescriptor));
To:-
IntPtr fileDescriptorPointer = (IntPtr)((long)fileGroupDescriptorAPointer + Marshal.SizeOf(fileGroupDescriptor.cItems));
fileDescriptorPointer = (IntPtr)((long)fileDescriptorPointer + Marshal.SizeOf(fileDescriptor));
Not sure why my Visual Studio didn't throw a wobbly when it hit those lines, but oh well.
With the above change, the example code I provided handles drag and drop of files and Outlook emails, both single items and multiple items at once
The OutlookDataObject class for the code above, as it was too long for one post
public class OutlookDataObject : System.Windows.Forms.IDataObject
{
#region NativeMethods
private class NativeMethods
{
[DllImport("kernel32.dll")]
static extern IntPtr GlobalLock(IntPtr hMem);
[DllImport("ole32.dll", PreserveSig = false)]
public static extern ILockBytes CreateILockBytesOnHGlobal(IntPtr hGlobal, bool fDeleteOnRelease);
[DllImport("OLE32.DLL", CharSet = CharSet.Auto, PreserveSig = false)]
public static extern IntPtr GetHGlobalFromILockBytes(ILockBytes pLockBytes);
[DllImport("OLE32.DLL", CharSet = CharSet.Unicode, PreserveSig = false)]
public static extern IStorage StgCreateDocfileOnILockBytes(ILockBytes plkbyt, uint grfMode, uint reserved);
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("0000000B-0000-0000-C000-000000000046")]
public interface IStorage
{
[return: MarshalAs(UnmanagedType.Interface)]
IStream CreateStream([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In, MarshalAs(UnmanagedType.U4)] int grfMode, [In, MarshalAs(UnmanagedType.U4)] int reserved1, [In, MarshalAs(UnmanagedType.U4)] int reserved2);
[return: MarshalAs(UnmanagedType.Interface)]
IStream OpenStream([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, IntPtr reserved1, [In, MarshalAs(UnmanagedType.U4)] int grfMode, [In, MarshalAs(UnmanagedType.U4)] int reserved2);
[return: MarshalAs(UnmanagedType.Interface)]
IStorage CreateStorage([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In, MarshalAs(UnmanagedType.U4)] int grfMode, [In, MarshalAs(UnmanagedType.U4)] int reserved1, [In, MarshalAs(UnmanagedType.U4)] int reserved2);
[return: MarshalAs(UnmanagedType.Interface)]
IStorage OpenStorage([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, IntPtr pstgPriority, [In, MarshalAs(UnmanagedType.U4)] int grfMode, IntPtr snbExclude, [In, MarshalAs(UnmanagedType.U4)] int reserved);
void CopyTo(int ciidExclude, [In, MarshalAs(UnmanagedType.LPArray)] Guid[] pIIDExclude, IntPtr snbExclude, [In, MarshalAs(UnmanagedType.Interface)] IStorage stgDest);
void MoveElementTo([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In, MarshalAs(UnmanagedType.Interface)] IStorage stgDest, [In, MarshalAs(UnmanagedType.BStr)] string pwcsNewName, [In, MarshalAs(UnmanagedType.U4)] int grfFlags);
void Commit(int grfCommitFlags);
void Revert();
void EnumElements([In, MarshalAs(UnmanagedType.U4)] int reserved1, IntPtr reserved2, [In, MarshalAs(UnmanagedType.U4)] int reserved3, [MarshalAs(UnmanagedType.Interface)] out object ppVal);
void DestroyElement([In, MarshalAs(UnmanagedType.BStr)] string pwcsName);
void RenameElement([In, MarshalAs(UnmanagedType.BStr)] string pwcsOldName, [In, MarshalAs(UnmanagedType.BStr)] string pwcsNewName);
void SetElementTimes([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In] System.Runtime.InteropServices.ComTypes.FILETIME pctime, [In] System.Runtime.InteropServices.ComTypes.FILETIME patime, [In] System.Runtime.InteropServices.ComTypes.FILETIME pmtime);
void SetClass([In] ref Guid clsid);
void SetStateBits(int grfStateBits, int grfMask);
void Stat([Out]out System.Runtime.InteropServices.ComTypes.STATSTG pStatStg, int grfStatFlag);
}
[ComImport, Guid("0000000A-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ILockBytes
{
void ReadAt([In, MarshalAs(UnmanagedType.U8)] long ulOffset, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, [In, MarshalAs(UnmanagedType.U4)] int cb, [Out, MarshalAs(UnmanagedType.LPArray)] int[] pcbRead);
void WriteAt([In, MarshalAs(UnmanagedType.U8)] long ulOffset, IntPtr pv, [In, MarshalAs(UnmanagedType.U4)] int cb, [Out, MarshalAs(UnmanagedType.LPArray)] int[] pcbWritten);
void Flush();
void SetSize([In, MarshalAs(UnmanagedType.U8)] long cb);
void LockRegion([In, MarshalAs(UnmanagedType.U8)] long libOffset, [In, MarshalAs(UnmanagedType.U8)] long cb, [In, MarshalAs(UnmanagedType.U4)] int dwLockType);
void UnlockRegion([In, MarshalAs(UnmanagedType.U8)] long libOffset, [In, MarshalAs(UnmanagedType.U8)] long cb, [In, MarshalAs(UnmanagedType.U4)] int dwLockType);
void Stat([Out]out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, [In, MarshalAs(UnmanagedType.U4)] int grfStatFlag);
}
[StructLayout(LayoutKind.Sequential)]
public sealed class POINTL
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
public sealed class SIZEL
{
public int cx;
public int cy;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public sealed class FILEGROUPDESCRIPTORA
{
public uint cItems;
public FILEDESCRIPTORA[] fgd;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public sealed class FILEDESCRIPTORA
{
public uint dwFlags;
public Guid clsid;
public SIZEL sizel;
public POINTL pointl;
public uint dwFileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
public uint nFileSizeHigh;
public uint nFileSizeLow;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string cFileName;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public sealed class FILEGROUPDESCRIPTORW
{
public uint cItems;
public FILEDESCRIPTORW[] fgd;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public sealed class FILEDESCRIPTORW
{
public uint dwFlags;
public Guid clsid;
public SIZEL sizel;
public POINTL pointl;
public uint dwFileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
public uint nFileSizeHigh;
public uint nFileSizeLow;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string cFileName;
}
}
#endregion
#region Property(s)
/// <summary>
/// Holds the <see cref="System.Windows.Forms.IDataObject"/> that this class is wrapping
/// </summary>
private System.Windows.Forms.IDataObject underlyingDataObject;
private System.Runtime.InteropServices.ComTypes.IDataObject comUnderlyingDataObject;
private System.Windows.Forms.IDataObject oleUnderlyingDataObject;
private MethodInfo getDataFromHGLOBLALMethod;
#endregion
#region Constructor(s)
public OutlookDataObject(System.Windows.Forms.IDataObject underlyingDataObject)
{
this.underlyingDataObject = underlyingDataObject;
this.comUnderlyingDataObject = (System.Runtime.InteropServices.ComTypes.IDataObject)this.underlyingDataObject;
FieldInfo innerDataField = this.underlyingDataObject.GetType().GetField("innerData", BindingFlags.NonPublic | BindingFlags.Instance);
this.oleUnderlyingDataObject = (System.Windows.Forms.IDataObject)innerDataField.GetValue(this.underlyingDataObject);
this.getDataFromHGLOBLALMethod = this.oleUnderlyingDataObject.GetType().GetMethod("GetDataFromHGLOBLAL", BindingFlags.NonPublic | BindingFlags.Instance);
}
#endregion
#region IDataObject Members
public object GetData(Type format)
{
return this.GetData(format.FullName);
}
public object GetData(string format)
{
return this.GetData(format, true);
}
public object GetData(string format, bool autoConvert)
{
switch (format)
{
case "FileGroupDescriptor":
IntPtr fileGroupDescriptorAPointer = IntPtr.Zero;
try
{
//use the underlying IDataObject to get the FileGroupDescriptor as a MemoryStream
MemoryStream fileGroupDescriptorStream = (MemoryStream)this.underlyingDataObject.GetData("FileGroupDescriptor", autoConvert);
byte[] fileGroupDescriptorBytes = new byte[fileGroupDescriptorStream.Length];
fileGroupDescriptorStream.Read(fileGroupDescriptorBytes, 0, fileGroupDescriptorBytes.Length);
fileGroupDescriptorStream.Close();
//copy the file group descriptor into unmanaged memory
fileGroupDescriptorAPointer = Marshal.AllocHGlobal(fileGroupDescriptorBytes.Length);
Marshal.Copy(fileGroupDescriptorBytes, 0, fileGroupDescriptorAPointer, fileGroupDescriptorBytes.Length);
//marshal the unmanaged memory to to FILEGROUPDESCRIPTORA struct
object fileGroupDescriptorObject = Marshal.PtrToStructure(fileGroupDescriptorAPointer, typeof(NativeMethods.FILEGROUPDESCRIPTORA));
NativeMethods.FILEGROUPDESCRIPTORA fileGroupDescriptor = (NativeMethods.FILEGROUPDESCRIPTORA)fileGroupDescriptorObject;
//create a new array to store file names in of the number of items in the file group descriptor
string[] fileNames = new string[fileGroupDescriptor.cItems];
//get the pointer to the first file descriptor
IntPtr fileDescriptorPointer = (IntPtr)((int)fileGroupDescriptorAPointer + Marshal.SizeOf(fileGroupDescriptor.cItems));
//loop for the number of files acording to the file group descriptor
for (int fileDescriptorIndex = 0; fileDescriptorIndex < fileGroupDescriptor.cItems; fileDescriptorIndex++)
{
//marshal the pointer top the file descriptor as a FILEDESCRIPTORA struct and get the file name
NativeMethods.FILEDESCRIPTORA fileDescriptor = (NativeMethods.FILEDESCRIPTORA)Marshal.PtrToStructure(fileDescriptorPointer, typeof(NativeMethods.FILEDESCRIPTORA));
fileNames[fileDescriptorIndex] = fileDescriptor.cFileName;
//move the file descriptor pointer to the next file descriptor
fileDescriptorPointer = (IntPtr)((int)fileDescriptorPointer + Marshal.SizeOf(fileDescriptor));
}
//return the array of filenames
return fileNames;
}
finally
{
//free unmanaged memory pointer
Marshal.FreeHGlobal(fileGroupDescriptorAPointer);
}
case "FileGroupDescriptorW":
//override the default handling of FileGroupDescriptorW which returns a
//MemoryStream and instead return a string array of file names
IntPtr fileGroupDescriptorWPointer = IntPtr.Zero;
try
{
//use the underlying IDataObject to get the FileGroupDescriptorW as a MemoryStream
MemoryStream fileGroupDescriptorStream = (MemoryStream)this.underlyingDataObject.GetData("FileGroupDescriptorW");
byte[] fileGroupDescriptorBytes = new byte[fileGroupDescriptorStream.Length];
fileGroupDescriptorStream.Read(fileGroupDescriptorBytes, 0, fileGroupDescriptorBytes.Length);
fileGroupDescriptorStream.Close();
//copy the file group descriptor into unmanaged memory
fileGroupDescriptorWPointer = Marshal.AllocHGlobal(fileGroupDescriptorBytes.Length);
Marshal.Copy(fileGroupDescriptorBytes, 0, fileGroupDescriptorWPointer, fileGroupDescriptorBytes.Length);
//marshal the unmanaged memory to to FILEGROUPDESCRIPTORW struct
object fileGroupDescriptorObject = Marshal.PtrToStructure(fileGroupDescriptorWPointer, typeof(NativeMethods.FILEGROUPDESCRIPTORW));
NativeMethods.FILEGROUPDESCRIPTORW fileGroupDescriptor = (NativeMethods.FILEGROUPDESCRIPTORW)fileGroupDescriptorObject;
//create a new array to store file names in of the number of items in the file group descriptor
string[] fileNames = new string[fileGroupDescriptor.cItems];
//get the pointer to the first file descriptor
//get the pointer to the first file descriptor
IntPtr fileDescriptorPointer = (IntPtr)((int)fileGroupDescriptorWPointer + Marshal.SizeOf(fileGroupDescriptor.cItems));
//loop for the number of files acording to the file group descriptor
for (int fileDescriptorIndex = 0; fileDescriptorIndex < fileGroupDescriptor.cItems; fileDescriptorIndex++)
{
//marshal the pointer top the file descriptor as a FILEDESCRIPTORW struct and get the file name
NativeMethods.FILEDESCRIPTORW fileDescriptor = (NativeMethods.FILEDESCRIPTORW)Marshal.PtrToStructure(fileDescriptorPointer, typeof(NativeMethods.FILEDESCRIPTORW));
fileNames[fileDescriptorIndex] = fileDescriptor.cFileName;
//move the file descriptor pointer to the next file descriptor
fileDescriptorPointer = (IntPtr)((int)fileDescriptorPointer + Marshal.SizeOf(fileDescriptor));
}
//return the array of filenames
return fileNames;
}
finally
{
//free unmanaged memory pointer
Marshal.FreeHGlobal(fileGroupDescriptorWPointer);
}
case "FileContents":
//override the default handling of FileContents which returns the
//contents of the first file as a memory stream and instead return
//a array of MemoryStreams containing the data to each file dropped
//get the array of filenames which lets us know how many file contents exist
string[] fileContentNames = (string[])this.GetData("FileGroupDescriptor");
//create a MemoryStream array to store the file contents
MemoryStream[] fileContents = new MemoryStream[fileContentNames.Length];
//loop for the number of files acording to the file names
for (int fileIndex = 0; fileIndex < fileContentNames.Length; fileIndex++)
{
//get the data at the file index and store in array
fileContents[fileIndex] = this.GetData(format, fileIndex);
}
//return array of MemoryStreams containing file contents
return fileContents;
}
//use underlying IDataObject to handle getting of data
return this.underlyingDataObject.GetData(format, autoConvert);
}
/// <summary>
/// Retrieves the data associated with the specified data format at the specified index.
/// </summary>
/// <param name="format">The format of the data to retrieve. See <see cref="T:System.Windows.Forms.DataFormats"></see> for predefined formats.</param>
/// <param name="index">The index of the data to retrieve.</param>
/// <returns>
/// A <see cref="MemoryStream"/> containing the raw data for the specified data format at the specified index.
/// </returns>
public MemoryStream GetData(string format, int index)
{
//create a FORMATETC struct to request the data with
FORMATETC formatetc = new FORMATETC();
formatetc.cfFormat = (short)DataFormats.GetFormat(format).Id;
formatetc.dwAspect = DVASPECT.DVASPECT_CONTENT;
formatetc.lindex = index;
formatetc.ptd = new IntPtr(0);
formatetc.tymed = TYMED.TYMED_ISTREAM | TYMED.TYMED_ISTORAGE | TYMED.TYMED_HGLOBAL;
//create STGMEDIUM to output request results into
STGMEDIUM medium = new STGMEDIUM();
//using the Com IDataObject interface get the data using the defined FORMATETC
this.comUnderlyingDataObject.GetData(ref formatetc, out medium);
//retrieve the data depending on the returned store type
switch (medium.tymed)
{
case TYMED.TYMED_ISTORAGE:
//to handle a IStorage it needs to be written into a second unmanaged
//memory mapped storage and then the data can be read from memory into
//a managed byte and returned as a MemoryStream
NativeMethods.IStorage iStorage = null;
NativeMethods.IStorage iStorage2 = null;
NativeMethods.ILockBytes iLockBytes = null;
System.Runtime.InteropServices.ComTypes.STATSTG iLockBytesStat;
try
{
//marshal the returned pointer to a IStorage object
iStorage = (NativeMethods.IStorage)Marshal.GetObjectForIUnknown(medium.unionmember);
Marshal.Release(medium.unionmember);
//create a ILockBytes (unmanaged byte array) and then create a IStorage using the byte array as a backing store
iLockBytes = NativeMethods.CreateILockBytesOnHGlobal(IntPtr.Zero, true);
iStorage2 = NativeMethods.StgCreateDocfileOnILockBytes(iLockBytes, 0x00001012, 0);
//copy the returned IStorage into the new IStorage
iStorage.CopyTo(0, null, IntPtr.Zero, iStorage2);
iLockBytes.Flush();
iStorage2.Commit(0);
//get the STATSTG of the ILockBytes to determine how many bytes were written to it
iLockBytesStat = new System.Runtime.InteropServices.ComTypes.STATSTG();
iLockBytes.Stat(out iLockBytesStat, 1);
int iLockBytesSize = (int)iLockBytesStat.cbSize;
//read the data from the ILockBytes (unmanaged byte array) into a managed byte array
byte[] iLockBytesContent = new byte[iLockBytesSize];
iLockBytes.ReadAt(0, iLockBytesContent, iLockBytesContent.Length, null);
//wrapped the managed byte array into a memory stream and return it
return new MemoryStream(iLockBytesContent);
}
finally
{
//release all unmanaged objects
Marshal.ReleaseComObject(iStorage2);
Marshal.ReleaseComObject(iLockBytes);
Marshal.ReleaseComObject(iStorage);
}
case TYMED.TYMED_ISTREAM:
//to handle a IStream it needs to be read into a managed byte and
//returned as a MemoryStream
IStream iStream = null;
System.Runtime.InteropServices.ComTypes.STATSTG iStreamStat;
try
{
//marshal the returned pointer to a IStream object
iStream = (IStream)Marshal.GetObjectForIUnknown(medium.unionmember);
Marshal.Release(medium.unionmember);
//get the STATSTG of the IStream to determine how many bytes are in it
iStreamStat = new System.Runtime.InteropServices.ComTypes.STATSTG();
iStream.Stat(out iStreamStat, 0);
int iStreamSize = (int)iStreamStat.cbSize;
//read the data from the IStream into a managed byte array
byte[] iStreamContent = new byte[iStreamSize];
iStream.Read(iStreamContent, iStreamContent.Length, IntPtr.Zero);
//wrapped the managed byte array into a memory stream and return it
return new MemoryStream(iStreamContent);
}
finally
{
//release all unmanaged objects
Marshal.ReleaseComObject(iStream);
}
case TYMED.TYMED_HGLOBAL:
//to handle a HGlobal the exisitng "GetDataFromHGLOBLAL" method is invoked via
//reflection
return (MemoryStream)this.getDataFromHGLOBLALMethod.Invoke(this.oleUnderlyingDataObject, new object[] { DataFormats.GetFormat((short)formatetc.cfFormat).Name, medium.unionmember });
}
return null;
}
/// <summary>
/// Determines whether data stored in this instance is associated with, or can be converted to, the specified format.
/// </summary>
/// <param name="format">A <see cref="T:System.Type"></see> representing the format for which to check. See <see cref="T:System.Windows.Forms.DataFormats"></see> for predefined formats.</param>
/// <returns>
/// true if data stored in this instance is associated with, or can be converted to, the specified format; otherwise, false.
/// </returns>
public bool GetDataPresent(Type format)
{
return this.underlyingDataObject.GetDataPresent(format);
}
/// <summary>
/// Determines whether data stored in this instance is associated with, or can be converted to, the specified format.
/// </summary>
/// <param name="format">The format for which to check. See <see cref="T:System.Windows.Forms.DataFormats"></see> for predefined formats.</param>
/// <returns>
/// true if data stored in this instance is associated with, or can be converted to, the specified format; otherwise false.
/// </returns>
public bool GetDataPresent(string format)
{
return this.underlyingDataObject.GetDataPresent(format);
}
/// <summary>
/// Determines whether data stored in this instance is associated with the specified format, using a Boolean value to determine whether to convert the data to the format.
/// </summary>
/// <param name="format">The format for which to check. See <see cref="T:System.Windows.Forms.DataFormats"></see> for predefined formats.</param>
/// <param name="autoConvert">true to determine whether data stored in this instance can be converted to the specified format; false to check whether the data is in the specified format.</param>
/// <returns>
/// true if the data is in, or can be converted to, the specified format; otherwise, false.
/// </returns>
public bool GetDataPresent(string format, bool autoConvert)
{
return this.underlyingDataObject.GetDataPresent(format, autoConvert);
}
/// <summary>
/// Returns a list of all formats that data stored in this instance is associated with or can be converted to.
/// </summary>
/// <returns>
/// An array of the names that represents a list of all formats that are supported by the data stored in this object.
/// </returns>
public string[] GetFormats()
{
return this.underlyingDataObject.GetFormats();
}
public string[] GetFormats(bool autoConvert)
{
return this.underlyingDataObject.GetFormats(autoConvert);
}
/// <summary>
/// Stores the specified data in this instance, using the class of the data for the format.
/// </summary>
/// <param name="data">The data to store.</param>
public void SetData(object data)
{
this.underlyingDataObject.SetData(data);
}
/// <summary>
/// Stores the specified data and its associated class type in this instance.
/// </summary>
/// <param name="format">A <see cref="T:System.Type"></see> representing the format associated with the data. See <see cref="T:System.Windows.Forms.DataFormats"></see> for predefined formats.</param>
/// <param name="data">The data to store.</param>
public void SetData(Type format, object data)
{
this.underlyingDataObject.SetData(format, data);
}
/// <summary>
/// Stores the specified data and its associated format in this instance.
/// </summary>
/// <param name="format">The format associated with the data. See <see cref="T:System.Windows.Forms.DataFormats"></see> for predefined formats.</param>
/// <param name="data">The data to store.</param>
public void SetData(string format, object data)
{
this.underlyingDataObject.SetData(format, data);
}
/// <summary>
/// Stores the specified data and its associated format in this instance, using a Boolean value to specify whether the data can be converted to another format.
/// </summary>
/// <param name="format">The format associated with the data. See <see cref="T:System.Windows.Forms.DataFormats"></see> for predefined formats.</param>
/// <param name="autoConvert">true to allow the data to be converted to another format; otherwise, false.</param>
/// <param name="data">The data to store.</param>
public void SetData(string format, bool autoConvert, object data)
{
this.underlyingDataObject.SetData(format, autoConvert, data);
}
#endregion
}
I'm making a file transfer app, and I need to use system icons so the icons will match different versions of Windows. They are remote files are distant so I can't just use SHFILEINFO.
So, I got it working by using this very good example from CodeProject:
public class IconReader
{
/// <summary>
/// Options to specify the size of icons to return.
/// </summary>
public enum IconSize
{
/// <summary>
/// Specify large icon - 32 pixels by 32 pixels.
/// </summary>
Large = 0,
/// <summary>
/// Specify small icon - 16 pixels by 16 pixels.
/// </summary>
Small = 1
}
/// <summary>
/// Options to specify whether folders should be in the open or closed state.
/// </summary>
public enum FolderType
{
/// <summary>
/// Specify open folder.
/// </summary>
Open = 0,
/// <summary>
/// Specify closed folder.
/// </summary>
Closed = 1
}
/// <summary>
/// Returns an icon for a given file - indicated by the name parameter.
/// </summary>
/// <param name="name">Pathname for file.</param>
/// <param name="size">Large or small</param>
/// <param name="linkOverlay">Whether to include the link icon</param>
/// <returns>System.Drawing.Icon</returns>
public static System.Drawing.Icon GetFileIcon(string name, IconSize size, bool linkOverlay)
{
Shell32.SHFILEINFO shfi = new Shell32.SHFILEINFO();
uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES;
if (true == linkOverlay) flags += Shell32.SHGFI_LINKOVERLAY;
/* Check the size specified for return. */
if (IconSize.Small == size)
{
flags += Shell32.SHGFI_SMALLICON;
}
else
{
flags += Shell32.SHGFI_LARGEICON;
}
Shell32.SHGetFileInfo(name,
Shell32.FILE_ATTRIBUTE_NORMAL,
ref shfi,
(uint)System.Runtime.InteropServices.Marshal.SizeOf(shfi),
flags);
// Copy (clone) the returned icon to a new object, thus allowing us to clean-up properly
System.Drawing.Icon icon = (System.Drawing.Icon)System.Drawing.Icon.FromHandle(shfi.hIcon).Clone();
User32.DestroyIcon(shfi.hIcon); // Cleanup
return icon;
}
/// <summary>
/// Used to access system folder icons.
/// </summary>
/// <param name="size">Specify large or small icons.</param>
/// <param name="folderType">Specify open or closed FolderType.</param>
/// <returns>System.Drawing.Icon</returns>
public static System.Drawing.Icon GetFolderIcon(IconSize size, FolderType folderType)
{
// Need to add size check, although errors generated at present!
uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES;
if (FolderType.Open == folderType)
{
flags += Shell32.SHGFI_OPENICON;
}
if (IconSize.Small == size)
{
flags += Shell32.SHGFI_SMALLICON;
}
else
{
flags += Shell32.SHGFI_LARGEICON;
}
// Get the folder icon
Shell32.SHFILEINFO shfi = new Shell32.SHFILEINFO();
Shell32.SHGetFileInfo(#"Folder",
Shell32.FILE_ATTRIBUTE_DIRECTORY,
ref shfi,
(uint)System.Runtime.InteropServices.Marshal.SizeOf(shfi),
flags);
System.Drawing.Icon.FromHandle(shfi.hIcon); // Load the icon from an HICON handle
// Now clone the icon, so that it can be successfully stored in an ImageList
System.Drawing.Icon icon = (System.Drawing.Icon)System.Drawing.Icon.FromHandle(shfi.hIcon).Clone();
User32.DestroyIcon(shfi.hIcon); // Cleanup
return icon;
}
public static System.Drawing.Icon GetDriveIcon(IconSize size, FolderType folderType)
{
// Need to add size check, although errors generated at present!
uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES;
if (FolderType.Open == folderType)
{
flags += Shell32.SHGFI_OPENICON;
}
if (IconSize.Small == size)
{
flags += Shell32.SHGFI_SMALLICON;
}
else
{
flags += Shell32.SHGFI_LARGEICON;
}
// Get the folder icon
Shell32.SHFILEINFO shfi = new Shell32.SHFILEINFO();
Shell32.SHGetFileInfo(null,
Shell32.FILE_ATTRIBUTE_DIRECTORY,
ref shfi,
(uint)System.Runtime.InteropServices.Marshal.SizeOf(shfi),
flags);
System.Drawing.Icon.FromHandle(shfi.hIcon); // Load the icon from an HICON handle
// Now clone the icon, so that it can be successfully stored in an ImageList
System.Drawing.Icon icon = (System.Drawing.Icon)System.Drawing.Icon.FromHandle(shfi.hIcon).Clone();
User32.DestroyIcon(shfi.hIcon); // Cleanup
return icon;
}
}
/// <summary>
/// Wraps necessary Shell32.dll structures and functions required to retrieve Icon Handles using SHGetFileInfo. Code
/// courtesy of MSDN Cold Rooster Consulting case study.
/// </summary>
///
// This code has been left largely untouched from that in the CRC example. The main changes have been moving
// the icon reading code over to the IconReader type.
public class Shell32
{
public const int MAX_PATH = 256;
[StructLayout(LayoutKind.Sequential)]
public struct SHITEMID
{
public ushort cb;
[MarshalAs(UnmanagedType.LPArray)]
public byte[] abID;
}
[StructLayout(LayoutKind.Sequential)]
public struct ITEMIDLIST
{
public SHITEMID mkid;
}
[StructLayout(LayoutKind.Sequential)]
public struct BROWSEINFO
{
public IntPtr hwndOwner;
public IntPtr pidlRoot;
public IntPtr pszDisplayName;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpszTitle;
public uint ulFlags;
public IntPtr lpfn;
public int lParam;
public IntPtr iImage;
}
// Browsing for directory.
public const uint BIF_RETURNONLYFSDIRS = 0x0001;
public const uint BIF_DONTGOBELOWDOMAIN = 0x0002;
public const uint BIF_STATUSTEXT = 0x0004;
public const uint BIF_RETURNFSANCESTORS = 0x0008;
public const uint BIF_EDITBOX = 0x0010;
public const uint BIF_VALIDATE = 0x0020;
public const uint BIF_NEWDIALOGSTYLE = 0x0040;
public const uint BIF_USENEWUI = (BIF_NEWDIALOGSTYLE | BIF_EDITBOX);
public const uint BIF_BROWSEINCLUDEURLS = 0x0080;
public const uint BIF_BROWSEFORCOMPUTER = 0x1000;
public const uint BIF_BROWSEFORPRINTER = 0x2000;
public const uint BIF_BROWSEINCLUDEFILES = 0x4000;
public const uint BIF_SHAREABLE = 0x8000;
[StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
{
public const int NAMESIZE = 80;
public IntPtr hIcon;
public int iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NAMESIZE)]
public string szTypeName;
};
public const uint SHGFI_ICON = 0x000000100; // get icon
public const uint SHGFI_DISPLAYNAME = 0x000000200; // get display name
public const uint SHGFI_TYPENAME = 0x000000400; // get type name
public const uint SHGFI_ATTRIBUTES = 0x000000800; // get attributes
public const uint SHGFI_ICONLOCATION = 0x000001000; // get icon location
public const uint SHGFI_EXETYPE = 0x000002000; // return exe type
public const uint SHGFI_SYSICONINDEX = 0x000004000; // get system icon index
public const uint SHGFI_LINKOVERLAY = 0x000008000; // put a link overlay on icon
public const uint SHGFI_SELECTED = 0x000010000; // show icon in selected state
public const uint SHGFI_ATTR_SPECIFIED = 0x000020000; // get only specified attributes
public const uint SHGFI_LARGEICON = 0x000000000; // get large icon
public const uint SHGFI_SMALLICON = 0x000000001; // get small icon
public const uint SHGFI_OPENICON = 0x000000002; // get open icon
public const uint SHGFI_SHELLICONSIZE = 0x000000004; // get shell size icon
public const uint SHGFI_PIDL = 0x000000008; // pszPath is a pidl
public const uint SHGFI_USEFILEATTRIBUTES = 0x000000010; // use passed dwFileAttribute
public const uint SHGFI_ADDOVERLAYS = 0x000000020; // apply the appropriate overlays
public const uint SHGFI_OVERLAYINDEX = 0x000000040; // Get the index of the overlay
public const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
public const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;
[DllImport("Shell32.dll")]
public static extern IntPtr SHGetFileInfo(
string pszPath,
uint dwFileAttributes,
ref SHFILEINFO psfi,
uint cbFileInfo,
uint uFlags
);
}
/// <summary>
/// Wraps necessary functions imported from User32.dll. Code courtesy of MSDN Cold Rooster Consulting example.
/// </summary>
public class User32
{
/// <summary>
/// Provides access to function required to delete handle. This method is used internally
/// and is not required to be called separately.
/// </summary>
/// <param name="hIcon">Pointer to icon handle.</param>
/// <returns>N/A</returns>
[DllImport("User32.dll")]
public static extern int DestroyIcon(IntPtr hIcon);
}
But, There is still one problem: The icons look HORRIBLE ! Here is a screenshot:
Do someone know how I can solve this ?
I have an issue I am having trouble with and hope somebody here can help.
We use somewhat modified code from this OutlookDataObject project to handle files dropped from various mail clients like outlook as well as the general copy pasting of files via clipboard to process them and save them to the file system.
It works fine in most cases, however if a user opens a ZIP file with the Windows Explorer, copys a file from there and tries to add it we get an AccessViolationException at the following part of the GetData Method of the FileContentDataObject:
//marshal the unmanaged memory to to FILEGROUPDESCRIPTORW struct
object fileGroupDescriptorObject = Marshal.PtrToStructure(fileGroupDescriptorWPointer, typeof(NativeMethods.FILEGROUPDESCRIPTORW));
Here is a reduced version of the whole code in question with which you should be able to reproduce this issue:
// Starting Method
public void InsertFromClipboard()
{
FileContentDataObjectBase dataObject = GetDataObject();
if(dataObject!= null)
{
dataObject.SaveToFileSystem("C:/temp");
}
}
private FileContentDataObjectBase GetDataObject()
{
var dataObject = System.Windows.Forms.Clipboard.GetDataObject();
return new FileContentDataObject(System.Windows.Forms.Clipboard.GetDataObject());
}
public class FileContentDataObject : FileContentDataObjectBase
{
/// <summary>
/// Initializes a new instance of the <see cref="OutlookDataObject"/> class.
/// </summary>
/// <param name="underlyingDataObject">The underlying data object to wrap.</param>
public FileContentDataObject(System.Windows.Forms.IDataObject underlyingDataObject)
: base(underlyingDataObject) { }
public override void SaveToFileSystem(string path)
{
string[] filenames;
//get the names and data streams of the files dropped
if (this.GetFormats().Contains("FileGroupDescriptor"))
filenames = (string[])this.GetData("FileGroupDescriptor", true);
else if (this.GetFormats().Contains("FileGroupDescriptorW"))
filenames = (string[])this.GetData("FileGroupDescriptorW", true);
else
return;
MemoryStream[] filestreams = (MemoryStream[])this.GetData("FileContents");
SaveToFileSystem(filenames, filestreams, path);
}
public new object GetData(string format, bool autoConvert)
{
switch (format)
{
case "FileGroupDescriptorW":
//override the default handling of FileGroupDescriptorW which returns a
//MemoryStream and instead return a string array of file names
IntPtr fileGroupDescriptorWPointer = IntPtr.Zero;
try
{
//use the underlying IDataObject to get the FileGroupDescriptorW as a MemoryStream
MemoryStream fileGroupDescriptorStream = (MemoryStream)this.underlyingDataObject.GetData("FileGroupDescriptorW");
byte[] fileGroupDescriptorBytes = new byte[fileGroupDescriptorStream.Length];
fileGroupDescriptorStream.Read(fileGroupDescriptorBytes, 0, fileGroupDescriptorBytes.Length);
fileGroupDescriptorStream.Close();
//copy the file group descriptor into unmanaged memory
fileGroupDescriptorWPointer = Marshal.AllocHGlobal(fileGroupDescriptorBytes.Length);
Marshal.Copy(fileGroupDescriptorBytes, 0, fileGroupDescriptorWPointer, fileGroupDescriptorBytes.Length);
//marshal the unmanaged memory to to FILEGROUPDESCRIPTORW struct
object fileGroupDescriptorObject = Marshal.PtrToStructure(fileGroupDescriptorWPointer, typeof(NativeMethods.FILEGROUPDESCRIPTORW));
NativeMethods.FILEGROUPDESCRIPTORW fileGroupDescriptor = (NativeMethods.FILEGROUPDESCRIPTORW)fileGroupDescriptorObject;
//create a new array to store file names in of the number of items in the file group descriptor
string[] fileNames = new string[fileGroupDescriptor.cItems];
//get the pointer to the first file descriptor
IntPtr fileDescriptorPointer = (IntPtr)((int)fileGroupDescriptorWPointer + Marshal.SizeOf(fileGroupDescriptor.cItems));
//loop for the number of files acording to the file group descriptor
for (int fileDescriptorIndex = 0; fileDescriptorIndex < fileGroupDescriptor.cItems; fileDescriptorIndex++)
{
//marshal the pointer top the file descriptor as a FILEDESCRIPTORW struct and get the file name
NativeMethods.FILEDESCRIPTORW fileDescriptor = (NativeMethods.FILEDESCRIPTORW)Marshal.PtrToStructure(fileDescriptorPointer, typeof(NativeMethods.FILEDESCRIPTORW));
fileNames[fileDescriptorIndex] = fileDescriptor.cFileName;
//move the file descriptor pointer to the next file descriptor
fileDescriptorPointer = (IntPtr)((int)fileDescriptorPointer + Marshal.SizeOf(fileDescriptor));
}
//return the array of filenames
return fileNames;
}
finally
{
//free unmanaged memory pointer
Marshal.FreeHGlobal(fileGroupDescriptorWPointer);
}
}
//use underlying IDataObject to handle getting of data
return this.underlyingDataObject.GetData(format, autoConvert);
}
}
public abstract class FileContentDataObjectBase : System.Windows.Forms.IDataObject
{
#region NativeMethods
protected class NativeMethods
{
[DllImport("kernel32.dll")]
static extern IntPtr GlobalLock(IntPtr hMem);
[DllImport("ole32.dll", PreserveSig = false)]
internal static extern ILockBytes CreateILockBytesOnHGlobal(IntPtr hGlobal, bool fDeleteOnRelease);
[DllImport("OLE32.DLL", CharSet = CharSet.Auto, PreserveSig = false)]
internal static extern IntPtr GetHGlobalFromILockBytes(ILockBytes pLockBytes);
[DllImport("OLE32.DLL", CharSet = CharSet.Unicode, PreserveSig = false)]
internal static extern IStorage StgCreateDocfileOnILockBytes(ILockBytes plkbyt, uint grfMode, uint reserved);
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("0000000B-0000-0000-C000-000000000046")]
internal interface IStorage
{
[return: MarshalAs(UnmanagedType.Interface)]
IStream CreateStream([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In, MarshalAs(UnmanagedType.U4)] int grfMode, [In, MarshalAs(UnmanagedType.U4)] int reserved1, [In, MarshalAs(UnmanagedType.U4)] int reserved2);
[return: MarshalAs(UnmanagedType.Interface)]
IStream OpenStream([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, IntPtr reserved1, [In, MarshalAs(UnmanagedType.U4)] int grfMode, [In, MarshalAs(UnmanagedType.U4)] int reserved2);
[return: MarshalAs(UnmanagedType.Interface)]
IStorage CreateStorage([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In, MarshalAs(UnmanagedType.U4)] int grfMode, [In, MarshalAs(UnmanagedType.U4)] int reserved1, [In, MarshalAs(UnmanagedType.U4)] int reserved2);
[return: MarshalAs(UnmanagedType.Interface)]
IStorage OpenStorage([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, IntPtr pstgPriority, [In, MarshalAs(UnmanagedType.U4)] int grfMode, IntPtr snbExclude, [In, MarshalAs(UnmanagedType.U4)] int reserved);
void CopyTo(int ciidExclude, [In, MarshalAs(UnmanagedType.LPArray)] Guid[] pIIDExclude, IntPtr snbExclude, [In, MarshalAs(UnmanagedType.Interface)] IStorage stgDest);
void MoveElementTo([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In, MarshalAs(UnmanagedType.Interface)] IStorage stgDest, [In, MarshalAs(UnmanagedType.BStr)] string pwcsNewName, [In, MarshalAs(UnmanagedType.U4)] int grfFlags);
void Commit(int grfCommitFlags);
void Revert();
void EnumElements([In, MarshalAs(UnmanagedType.U4)] int reserved1, IntPtr reserved2, [In, MarshalAs(UnmanagedType.U4)] int reserved3, [MarshalAs(UnmanagedType.Interface)] out object ppVal);
void DestroyElement([In, MarshalAs(UnmanagedType.BStr)] string pwcsName);
void RenameElement([In, MarshalAs(UnmanagedType.BStr)] string pwcsOldName, [In, MarshalAs(UnmanagedType.BStr)] string pwcsNewName);
void SetElementTimes([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In] System.Runtime.InteropServices.ComTypes.FILETIME pctime, [In] System.Runtime.InteropServices.ComTypes.FILETIME patime, [In] System.Runtime.InteropServices.ComTypes.FILETIME pmtime);
void SetClass([In] ref Guid clsid);
void SetStateBits(int grfStateBits, int grfMask);
void Stat([Out]out System.Runtime.InteropServices.ComTypes.STATSTG pStatStg, int grfStatFlag);
}
[ComImport, Guid("0000000A-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface ILockBytes
{
void ReadAt([In, MarshalAs(UnmanagedType.U8)] long ulOffset, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, [In, MarshalAs(UnmanagedType.U4)] int cb, [Out, MarshalAs(UnmanagedType.LPArray)] int[] pcbRead);
void WriteAt([In, MarshalAs(UnmanagedType.U8)] long ulOffset, IntPtr pv, [In, MarshalAs(UnmanagedType.U4)] int cb, [Out, MarshalAs(UnmanagedType.LPArray)] int[] pcbWritten);
void Flush();
void SetSize([In, MarshalAs(UnmanagedType.U8)] long cb);
void LockRegion([In, MarshalAs(UnmanagedType.U8)] long libOffset, [In, MarshalAs(UnmanagedType.U8)] long cb, [In, MarshalAs(UnmanagedType.U4)] int dwLockType);
void UnlockRegion([In, MarshalAs(UnmanagedType.U8)] long libOffset, [In, MarshalAs(UnmanagedType.U8)] long cb, [In, MarshalAs(UnmanagedType.U4)] int dwLockType);
void Stat([Out]out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, [In, MarshalAs(UnmanagedType.U4)] int grfStatFlag);
}
[StructLayout(LayoutKind.Sequential)]
internal sealed class POINTL
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
internal sealed class SIZEL
{
public int cx;
public int cy;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal sealed class FILEGROUPDESCRIPTORA
{
public uint cItems;
public FILEDESCRIPTORA[] fgd;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal sealed class FILEDESCRIPTORA
{
public uint dwFlags;
public Guid clsid;
public SIZEL sizel;
public POINTL pointl;
public uint dwFileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
public uint nFileSizeHigh;
public uint nFileSizeLow;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string cFileName;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal sealed class FILEGROUPDESCRIPTORW
{
public uint cItems;
public FILEDESCRIPTORW[] fgd;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal sealed class FILEDESCRIPTORW
{
public uint dwFlags;
public Guid clsid;
public SIZEL sizel;
public POINTL pointl;
public uint dwFileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
public uint nFileSizeHigh;
public uint nFileSizeLow;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string cFileName;
}
}
#endregion
#region Property(s)
/// <summary>
/// Holds the <see cref="System.Windows.Forms.IDataObject"/> that this class is wrapping
/// </summary>
protected System.Windows.Forms.IDataObject underlyingDataObject;
/// <summary>
/// Holds the <see cref="System.Runtime.InteropServices.ComTypes.IDataObject"/> interface to the <see cref="System.Windows.Forms.IDataObject"/> that this class is wrapping.
/// </summary>
protected System.Runtime.InteropServices.ComTypes.IDataObject comUnderlyingDataObject;
/// <summary>
/// Holds the internal ole <see cref="System.Windows.Forms.IDataObject"/> to the <see cref="System.Windows.Forms.IDataObject"/> that this class is wrapping.
/// </summary>
protected System.Windows.Forms.IDataObject oleUnderlyingDataObject;
/// <summary>
/// Holds the <see cref="MethodInfo"/> of the "GetDataFromHGLOBLAL" method of the internal ole <see cref="System.Windows.Forms.IDataObject"/>.
/// </summary>
protected MethodInfo getDataFromHGLOBLALMethod;
#endregion
#region Constructor(s)
/// <summary>
/// Initializes a new instance of the <see cref="OutlookDataObject"/> class.
/// </summary>
/// <param name="underlyingDataObject">The underlying data object to wrap.</param>
public FileContentDataObjectBase(System.Windows.Forms.IDataObject underlyingDataObject)
{
//get the underlying dataobject and its ComType IDataObject interface to it
this.underlyingDataObject = underlyingDataObject;
this.comUnderlyingDataObject = (System.Runtime.InteropServices.ComTypes.IDataObject)this.underlyingDataObject;
//get the internal ole dataobject and its GetDataFromHGLOBLAL so it can be called later
FieldInfo innerDataField = this.underlyingDataObject.GetType().GetField("innerData", BindingFlags.NonPublic | BindingFlags.Instance);
this.oleUnderlyingDataObject = (System.Windows.Forms.IDataObject)innerDataField.GetValue(this.underlyingDataObject);
this.getDataFromHGLOBLALMethod = this.oleUnderlyingDataObject.GetType().GetMethod("GetDataFromHGLOBLAL", BindingFlags.NonPublic | BindingFlags.Instance);
}
#endregion
#region IDataObject Members
/// <summary>
/// Retrieves the data associated with the specified class type format.
/// </summary>
/// <param name="format">A <see cref="T:System.Type"></see> representing the format of the data to retrieve. See <see cref="T:System.Windows.Forms.DataFormats"></see> for predefined formats.</param>
/// <returns>
/// The data associated with the specified format, or null.
/// </returns>
public object GetData(Type format)
{
return this.GetData(format.FullName);
}
/// <summary>
/// Retrieves the data associated with the specified data format.
/// </summary>
/// <param name="format">The format of the data to retrieve. See <see cref="T:System.Windows.Forms.DataFormats"></see> for predefined formats.</param>
/// <returns>
/// The data associated with the specified format, or null.
/// </returns>
public object GetData(string format)
{
return this.GetData(format, true);
}
/// <summary>
/// Retrieves the data associated with the specified data format, using a Boolean to determine whether to convert the data to the format.
/// </summary>
/// <param name="format">The format of the data to retrieve. See <see cref="T:System.Windows.Forms.DataFormats"></see> for predefined formats.</param>
/// <param name="autoConvert">true to convert the data to the specified format; otherwise, false.</param>
/// <returns>
/// The data associated with the specified format, or null.
/// </returns>
public object GetData(string format, bool autoConvert)
{
return this.underlyingDataObject.GetData(format, autoConvert);
}
/// <summary>
/// Retrieves the data associated with the specified data format at the specified index.
/// </summary>
/// <param name="format">The format of the data to retrieve. See <see cref="T:System.Windows.Forms.DataFormats"></see> for predefined formats.</param>
/// <param name="index">The index of the data to retrieve.</param>
/// <returns>
/// A <see cref="MemoryStream"/> containing the raw data for the specified data format at the specified index.
/// </returns>
public MemoryStream GetData(string format, int index)
{
//create a FORMATETC struct to request the data with
FORMATETC formatetc = new FORMATETC();
formatetc.cfFormat = (short)DataFormats.GetFormat(format).Id;
formatetc.dwAspect = DVASPECT.DVASPECT_CONTENT;
formatetc.lindex = index;
formatetc.ptd = new IntPtr(0);
formatetc.tymed = TYMED.TYMED_ISTREAM | TYMED.TYMED_ISTORAGE | TYMED.TYMED_HGLOBAL;
//create STGMEDIUM to output request results into
STGMEDIUM medium = new STGMEDIUM();
//using the Com IDataObject interface get the data using the defined FORMATETC
this.comUnderlyingDataObject.GetData(ref formatetc, out medium);
//retrieve the data depending on the returned store type
switch (medium.tymed)
{
case TYMED.TYMED_ISTORAGE:
//to handle a IStorage it needs to be written into a second unmanaged
//memory mapped storage and then the data can be read from memory into
//a managed byte and returned as a MemoryStream
NativeMethods.IStorage iStorage = null;
NativeMethods.IStorage iStorage2 = null;
NativeMethods.ILockBytes iLockBytes = null;
System.Runtime.InteropServices.ComTypes.STATSTG iLockBytesStat;
try
{
//marshal the returned pointer to a IStorage object
iStorage = (NativeMethods.IStorage)Marshal.GetObjectForIUnknown(medium.unionmember);
Marshal.Release(medium.unionmember);
//create a ILockBytes (unmanaged byte array) and then create a IStorage using the byte array as a backing store
iLockBytes = NativeMethods.CreateILockBytesOnHGlobal(IntPtr.Zero, true);
iStorage2 = NativeMethods.StgCreateDocfileOnILockBytes(iLockBytes, 0x00001012, 0);
//copy the returned IStorage into the new IStorage
iStorage.CopyTo(0, null, IntPtr.Zero, iStorage2);
iLockBytes.Flush();
iStorage2.Commit(0);
//get the STATSTG of the ILockBytes to determine how many bytes were written to it
iLockBytesStat = new System.Runtime.InteropServices.ComTypes.STATSTG();
iLockBytes.Stat(out iLockBytesStat, 1);
int iLockBytesSize = (int)iLockBytesStat.cbSize;
//read the data from the ILockBytes (unmanaged byte array) into a managed byte array
byte[] iLockBytesContent = new byte[iLockBytesSize];
iLockBytes.ReadAt(0, iLockBytesContent, iLockBytesContent.Length, null);
//wrapped the managed byte array into a memory stream and return it
return new MemoryStream(iLockBytesContent);
}
finally
{
//release all unmanaged objects
Marshal.ReleaseComObject(iStorage2);
Marshal.ReleaseComObject(iLockBytes);
Marshal.ReleaseComObject(iStorage);
}
case TYMED.TYMED_ISTREAM:
//to handle a IStream it needs to be read into a managed byte and
//returned as a MemoryStream
IStream iStream = null;
System.Runtime.InteropServices.ComTypes.STATSTG iStreamStat;
try
{
//marshal the returned pointer to a IStream object
iStream = (IStream)Marshal.GetObjectForIUnknown(medium.unionmember);
Marshal.Release(medium.unionmember);
//get the STATSTG of the IStream to determine how many bytes are in it
iStreamStat = new System.Runtime.InteropServices.ComTypes.STATSTG();
iStream.Stat(out iStreamStat, 0);
int iStreamSize = (int)iStreamStat.cbSize;
//read the data from the IStream into a managed byte array
byte[] iStreamContent = new byte[iStreamSize];
iStream.Read(iStreamContent, iStreamContent.Length, IntPtr.Zero);
//wrapped the managed byte array into a memory stream and return it
return new MemoryStream(iStreamContent);
}
finally
{
//release all unmanaged objects
Marshal.ReleaseComObject(iStream);
}
case TYMED.TYMED_HGLOBAL:
//to handle a HGlobal the exisitng "GetDataFromHGLOBLAL" method is invoked via
//reflection
return (MemoryStream)this.getDataFromHGLOBLALMethod.Invoke(this.oleUnderlyingDataObject, new object[] { DataFormats.GetFormat((short)formatetc.cfFormat).Name, medium.unionmember });
}
return null;
}
/// <summary>
/// Returns a list of all formats that data stored in this instance is associated with or can be converted to.
/// </summary>
/// <returns>
/// An array of the names that represents a list of all formats that are supported by the data stored in this object.
/// </returns>
public string[] GetFormats()
{
return this.underlyingDataObject.GetFormats();
}
/// <summary>
/// Gets a list of all formats that data stored in this instance is associated with or can be converted to, using a Boolean value to determine whether to retrieve all formats that the data can be converted to or only native data formats.
/// </summary>
/// <param name="autoConvert">true to retrieve all formats that data stored in this instance is associated with or can be converted to; false to retrieve only native data formats.</param>
/// <returns>
/// An array of the names that represents a list of all formats that are supported by the data stored in this object.
/// </returns>
public string[] GetFormats(bool autoConvert)
{
return this.underlyingDataObject.GetFormats(autoConvert);
}
#endregion
#region methods
public static void SaveToFileSystem(string[] filenames, MemoryStream[] filestreams, string path)
{
for (int fileIndex = 0; fileIndex < filenames.Length; fileIndex++)
{
try
{
//use the fileindex to get the name and data stream
string filename = filenames[fileIndex];
MemoryStream filestream = filestreams[fileIndex];
//save the file stream using its name to the application path
FileStream outputStream = File.Create(Path.Combine(path, GetStrippedFileName(filename)));
filestream.WriteTo(outputStream);
outputStream.Close();
}
catch (IOException ex)
{
throw new Exception(ex.Message, ex);
}
catch (SecurityException ex)
{
throw new Exception(ex.Message, ex);
}
}
}
public static void SaveToFileSystem(FileContentDataObjectBase dataObject, string path)
{
dataObject.SaveToFileSystem(path);
}
public abstract void SaveToFileSystem(string path);
private static string GetStrippedFileName(string input)
{
foreach (var chr in Path.GetInvalidFileNameChars())
input = input.Replace(chr.ToString(), String.Empty);
return input;
}
#endregion
}
I would like to figure out why it happens and how I can fix the issue.
Any help or insights would be welcome.
Thanks in advance.
After searching for quite a while I finally found the problem thanks to this helpful comment.
As it turns out the FILEGROUPDESCRIPTORA and FILEGROUPDESCRIPTORW declarations in the original code we used are wrong which can cause an AccessViolation Exception in some cases.
So I did change the declarations like the commentor described from...
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public sealed class FILEGROUPDESCRIPTORA
{
public uint cItems;
public FILEDESCRIPTORA[] fgd;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public sealed class FILEGROUPDESCRIPTORW
{
public uint cItems;
public FILEDESCRIPTORW[] fgd;
}
To...
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public sealed class FILEGROUPDESCRIPTORA
{
public uint cItems;
public FILEDESCRIPTORA fgd;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public sealed class FILEGROUPDESCRIPTORW
{
public uint cItems;
public FILEDESCRIPTORW fgd;
}
Afterwards the AccessViolation Exceptions were gone and I only had to modify the original code I linked in my question a little more to make it work.
I had to add some additional checks during the handling of the Clipboard files 'FileContents' format to make sure the correct of the two FILEGROUPDESCRIPTORs was used each time, but now it works perfectly again.
There could still be an error in your code.
That first line below in the handler for 'FILEGROUPDESCRIPTORW' can/will fail on a Win8 machine.
replace
object fileGroupDescriptorObject = Marshal.PtrToStructure(fileGroupDescriptorWPointer, typeof(NativeMethods.FILEGROUPDESCRIPTORW));
NativeMethods.FILEGROUPDESCRIPTORW fgd = (NativeMethods.FILEGROUPDESCRIPTORW)fileGroupDescriptorObject;
files = new NativeMethods.FILEDESCRIPTOR[fgd.cItems];
pdata = (IntPtr)((int)pdata + Marshal.SizeOf(pdata));
for (int index = 0; index < fgd.cItems; index++)
with
int ITEMCOUNT = Marshal.ReadInt32(pdata);
files = new NativeMethods.FILEDESCRIPTOR[ITEMCOUNT];
// Set our pointer offset to the beginning of the FILEDESCRIPTOR* array
pdata = (IntPtr)((long)pdata + Marshal.SizeOf(pdata));
// Walk the array, converting each FILEDESCRIPTOR* to a FILEDESCRIPTOR
for (int index = 0; index < ITEMCOUNT; index++)
For more details, see:
Drag-and-Drop multiple Attached File From Outlook to C# Window Form
In one of my WPF applications, I have the need to be able to read bar code values using C#. I am not sure how to do this. Any help would be appreciated.
Thanks in advance.
The best way is to create a keyboard hook.
Below is a class that I have used in several projects.
You use it like this:
var hook = new KeyboardHook();
var availbleScanners = KeyboardHook.GetKeyboardDevices();
... // find out which scanner to use
hook.SetDeviceFilter(availableScanners.First());
hook.KeyPressed += OnBarcodeKey;
hook.AddHook(YourWPFMainView);
...
public void OnBarcodeKey(object sender, KeyPressedEventArgs e) {
Console.WriteLine("received " + e.Text);
}
I got the low level keyboard stuff from this article.
public class KeyPressedEventArgs : EventArgs
{
public KeyPressedEventArgs(string text) {
mText = text;
}
public string Text { get { return mText; } }
private readonly string mText;
}
public partial class KeyboardHook
: IDisposable
{
private static readonly Regex DeviceNamePattern = new Regex(#"#([^#]+)");
public event EventHandler<KeyPressedEventArgs> KeyPressed;
/// <summary>
/// Set the device to use in keyboard hook
/// </summary>
/// <param name="deviceId">Name of device</param>
/// <returns>true if device is found</returns>
public bool SetDeviceFilter(string deviceId) {
Dictionary<string, IntPtr> devices = FindAllKeyboardDevices();
return devices.TryGetValue(deviceId, out mHookDeviceId);
}
/// <summary>
/// Add this KeyboardHook to a window
/// </summary>
/// <param name="window">The window to add to</param>
public void AddHook(System.Windows.Window window) {
if (window == null)
throw new ArgumentNullException("window");
if (mHwndSource != null)
throw new InvalidOperationException("Hook already present");
IntPtr hwnd = new WindowInteropHelper(window).Handle;
mHwndSource = HwndSource.FromHwnd(hwnd);
if (mHwndSource == null)
throw new ApplicationException("Failed to receive window source");
mHwndSource.AddHook(WndProc);
RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[1];
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x06;
rid[0].dwFlags = RIDEV_INPUTSINK;
rid[0].hwndTarget = hwnd;
if (!RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0])))
throw new ApplicationException("Failed to register raw input device(s).");
}
/// <summary>
/// Remove this keyboard hook from window (if it is added)
/// </summary>
public void RemoveHook() {
if (mHwndSource == null)
return; // not an error
RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[1];
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x06;
rid[0].dwFlags = 0x00000001;
rid[0].hwndTarget = IntPtr.Zero;
RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0]));
mHwndSource.RemoveHook(WndProc);
mHwndSource.Dispose();
mHwndSource = null;
}
public void Dispose() {
RemoveHook();
}
private IntPtr mHookDeviceId;
private HwndSource mHwndSource;
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
switch (msg) {
case WM_INPUT:
if (ProcessInputCommand(mHookDeviceId, lParam)) {
MSG message;
PeekMessage(out message, IntPtr.Zero, WM_KEYDOWN, WM_KEYDOWN, PM_REMOVE);
}
break;
}
return IntPtr.Zero;
}
/// <summary>
/// Get a list of keyboard devices available
/// </summary>
/// <returns>Collection of devices available</returns>
public static ICollection<string> GetKeyboardDevices() {
return FindAllKeyboardDevices().Keys;
}
private static Dictionary<string, IntPtr> FindAllKeyboardDevices() {
Dictionary<string, IntPtr> deviceNames = new Dictionary<string, IntPtr>();
uint deviceCount = 0;
int dwSize = (Marshal.SizeOf(typeof(RAWINPUTDEVICELIST)));
if (GetRawInputDeviceList(IntPtr.Zero, ref deviceCount, (uint)dwSize) == 0) {
IntPtr pRawInputDeviceList = Marshal.AllocHGlobal((int)(dwSize*deviceCount));
try {
GetRawInputDeviceList(pRawInputDeviceList, ref deviceCount, (uint)dwSize);
for (int i = 0; i < deviceCount; i++) {
uint pcbSize = 0;
var rid = (RAWINPUTDEVICELIST)Marshal.PtrToStructure(
new IntPtr((pRawInputDeviceList.ToInt32() + (dwSize*i))),
typeof(RAWINPUTDEVICELIST));
GetRawInputDeviceInfo(rid.hDevice, RIDI_DEVICENAME, IntPtr.Zero, ref pcbSize);
if (pcbSize > 0) {
IntPtr pData = Marshal.AllocHGlobal((int)pcbSize);
try {
GetRawInputDeviceInfo(rid.hDevice, RIDI_DEVICENAME, pData, ref pcbSize);
string deviceName = Marshal.PtrToStringAnsi(pData);
// The list will include the "root" keyboard and mouse devices
// which appear to be the remote access devices used by Terminal
// Services or the Remote Desktop - we're not interested in these
// so the following code with drop into the next loop iteration
if (deviceName.ToUpper().Contains("ROOT"))
continue;
// If the device is identified as a keyboard or HID device,
// Check if it is the one we're looking for
if (rid.dwType == RIM_TYPEKEYBOARD || rid.dwType == RIM_TYPEHID) {
Match match = DeviceNamePattern.Match(deviceName);
if (match.Success)
deviceNames.Add(match.Groups[1].Value, rid.hDevice);
}
}
finally {
Marshal.FreeHGlobal(pData);
}
}
}
}
finally {
Marshal.FreeHGlobal(pRawInputDeviceList);
}
}
return deviceNames;
}
/// <summary>
/// Processes WM_INPUT messages to retrieve information about any
/// keyboard events that occur.
/// </summary>
/// <param name="deviceId">Device to process</param>
/// <param name="lParam">The WM_INPUT message to process.</param>
private bool ProcessInputCommand(IntPtr deviceId, IntPtr lParam) {
uint dwSize = 0;
try {
// First call to GetRawInputData sets the value of dwSize
// dwSize can then be used to allocate the appropriate amount of memory,
// storing the pointer in "buffer".
GetRawInputData(lParam, RID_INPUT, IntPtr.Zero,ref dwSize, (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER)));
IntPtr buffer = Marshal.AllocHGlobal((int)dwSize);
try {
// Check that buffer points to something, and if so,
// call GetRawInputData again to fill the allocated memory
// with information about the input
if (buffer != IntPtr.Zero &&
GetRawInputData(lParam, RID_INPUT, buffer, ref dwSize, (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER))) == dwSize) {
// Store the message information in "raw", then check
// that the input comes from a keyboard device before
// processing it to raise an appropriate KeyPressed event.
RAWINPUT raw = (RAWINPUT)Marshal.PtrToStructure(buffer, typeof(RAWINPUT));
if (raw.header.hDevice != deviceId)
return false;
if (raw.header.dwType != RIM_TYPEKEYBOARD)
return false;
if (raw.keyboard.Message != WM_KEYDOWN && raw.keyboard.Message != WM_SYSKEYDOWN)
return false;
// On most keyboards, "extended" keys such as the arrow or page
// keys return two codes - the key's own code, and an "extended key" flag, which
// translates to 255. This flag isn't useful to us, so it can be
// disregarded.
if (raw.keyboard.VKey > VK_LAST_KEY)
return false;
if (KeyPressed != null) {
string scannedText = null;
lock (mLocalBuffer) {
if (GetKeyboardState(mKeyboardState)) {
if (ToUnicode(raw.keyboard.VKey, raw.keyboard.MakeCode, mKeyboardState, mLocalBuffer, 64, 0) > 0) {
if (mLocalBuffer.Length > 0) {
scannedText = mLocalBuffer.ToString();
}
}
}
}
if (!string.IsNullOrEmpty(scannedText))
KeyPressed(this, new KeyPressedEventArgs(scannedText));
}
return true;
}
}
finally {
Marshal.FreeHGlobal(buffer);
}
}
catch (Exception err) {
Logger.LogError(err, "Scanner error");
}
return false;
}
private static readonly StringBuilder mLocalBuffer = new StringBuilder();
private static readonly byte[] mKeyboardState = new byte[256];
}
public partial class KeyboardHook
{
private const int RIDEV_INPUTSINK = 0x00000100;
private const int RIDEV_REMOVE = 0x00000001;
private const int RID_INPUT = 0x10000003;
private const int FAPPCOMMAND_MASK = 0xF000;
private const int FAPPCOMMAND_MOUSE = 0x8000;
private const int FAPPCOMMAND_OEM = 0x1000;
private const int RIM_TYPEMOUSE = 0;
private const int RIM_TYPEKEYBOARD = 1;
private const int RIM_TYPEHID = 2;
private const int RIDI_DEVICENAME = 0x20000007;
private const int WM_KEYDOWN = 0x0100;
private const int WM_SYSKEYDOWN = 0x0104;
private const int WM_INPUT = 0x00FF;
private const int VK_OEM_CLEAR = 0xFE;
private const int VK_LAST_KEY = VK_OEM_CLEAR; // this is a made up value used as a sentinal
private const int PM_REMOVE = 0x01;
[StructLayout(LayoutKind.Sequential)]
private struct RAWINPUTDEVICELIST
{
public IntPtr hDevice;
[MarshalAs(UnmanagedType.U4)]
public int dwType;
}
[StructLayout(LayoutKind.Explicit)]
private struct RAWINPUT
{
[FieldOffset(0)]
public RAWINPUTHEADER header;
[FieldOffset(16)]
public RAWMOUSE mouse;
[FieldOffset(16)]
public RAWKEYBOARD keyboard;
[FieldOffset(16)]
public RAWHID hid;
}
[StructLayout(LayoutKind.Sequential)]
private struct RAWINPUTHEADER
{
[MarshalAs(UnmanagedType.U4)]
public int dwType;
[MarshalAs(UnmanagedType.U4)]
public int dwSize;
public IntPtr hDevice;
[MarshalAs(UnmanagedType.U4)]
public int wParam;
}
[StructLayout(LayoutKind.Sequential)]
private struct RAWHID
{
[MarshalAs(UnmanagedType.U4)]
public int dwSizHid;
[MarshalAs(UnmanagedType.U4)]
public int dwCount;
}
[StructLayout(LayoutKind.Sequential)]
private struct BUTTONSSTR
{
[MarshalAs(UnmanagedType.U2)]
public ushort usButtonFlags;
[MarshalAs(UnmanagedType.U2)]
public ushort usButtonData;
}
[StructLayout(LayoutKind.Explicit)]
private struct RAWMOUSE
{
[MarshalAs(UnmanagedType.U2)]
[FieldOffset(0)]
public ushort usFlags;
[MarshalAs(UnmanagedType.U4)]
[FieldOffset(4)]
public uint ulButtons;
[FieldOffset(4)]
public BUTTONSSTR buttonsStr;
[MarshalAs(UnmanagedType.U4)]
[FieldOffset(8)]
public uint ulRawButtons;
[FieldOffset(12)]
public int lLastX;
[FieldOffset(16)]
public int lLastY;
[MarshalAs(UnmanagedType.U4)]
[FieldOffset(20)]
public uint ulExtraInformation;
}
[StructLayout(LayoutKind.Sequential)]
private struct RAWKEYBOARD
{
[MarshalAs(UnmanagedType.U2)]
public ushort MakeCode;
[MarshalAs(UnmanagedType.U2)]
public ushort Flags;
[MarshalAs(UnmanagedType.U2)]
public ushort Reserved;
[MarshalAs(UnmanagedType.U2)]
public ushort VKey;
[MarshalAs(UnmanagedType.U4)]
public uint Message;
[MarshalAs(UnmanagedType.U4)]
public uint ExtraInformation;
}
[StructLayout(LayoutKind.Sequential)]
private struct RAWINPUTDEVICE
{
[MarshalAs(UnmanagedType.U2)]
public ushort usUsagePage;
[MarshalAs(UnmanagedType.U2)]
public ushort usUsage;
[MarshalAs(UnmanagedType.U4)]
public int dwFlags;
public IntPtr hwndTarget;
}
[DllImport("User32.dll")]
private static extern uint GetRawInputDeviceList(IntPtr pRawInputDeviceList, ref uint uiNumDevices, uint cbSize);
[DllImport("User32.dll")]
private static extern uint GetRawInputDeviceInfo(IntPtr hDevice, uint uiCommand, IntPtr pData, ref uint pcbSize);
[DllImport("User32.dll")]
private static extern bool RegisterRawInputDevices(RAWINPUTDEVICE[] pRawInputDevice, uint uiNumDevices, uint cbSize);
[DllImport("User32.dll")]
private static extern uint GetRawInputData(IntPtr hRawInput, uint uiCommand, IntPtr pData, ref uint pcbSize, uint cbSizeHeader);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetKeyboardState(byte[] lpKeyState);
[DllImport("user32.dll")]
private static extern int ToUnicode(uint wVirtKey, uint wScanCode, byte[] lpKeyState, [Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)] StringBuilder pwszBuff,
int cchBuff, uint wFlags);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool PeekMessage(out MSG lpmsg, IntPtr hwnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
}
}
Most of the barcode readers I've seen emulate a keyboard and send a key-press stream followed by a carriage return.
If your barcode reader works this way then a regular edit box should work.
As seen in This SO question on getting icons for common file types, it's quite possible for a windows program to get the icons for a registered file type using the C++ Shell API. These icons may or may not exist on disk - for example, we wanted to make our own custom file browser and want to display the system-associated icon with the file.
Is there a native C# way to get the icons for various file types (and if so, how) or must it be done through PInvoke with shell API?
And as a follow up, if there is a native .NET way of doing it, is there a cross-platform way of doing it?
One of my old open source project include an Icon class that does exactly that, feel free to rip it, seeing the age I put this file in the public domain anyway it's just PInvoke for most part.
To get an icon you use for example :
Icon zipIcon = BlackFox.Win32.Icons.IconFromExtension(".zip", SystemIconSize.Small);
Full sample :
using System;
using System.Windows.Forms;
using BlackFox.Win32;
using System.Drawing;
class Program
{
static void Main(string[] args)
{
PictureBox pict = new PictureBox();
pict.Image = Icons.IconFromExtension(".zip", Icons.SystemIconSize.Large).ToBitmap();
pict.Dock = DockStyle.Fill;
pict.SizeMode = PictureBoxSizeMode.CenterImage;
Form form = new Form();
form.Controls.Add(pict);
Application.Run(form);
}
}
The library :
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using System.Reflection;
using System.Collections.Generic;
namespace BlackFox.Win32
{
public static class Icons
{
#region Custom exceptions class
public class IconNotFoundException : Exception
{
public IconNotFoundException(string fileName, int index)
: base(string.Format("Icon with Id = {0} wasn't found in file {1}", index, fileName))
{
}
}
public class UnableToExtractIconsException : Exception
{
public UnableToExtractIconsException(string fileName, int firstIconIndex, int iconCount)
: base(string.Format("Tryed to extract {2} icons starting from the one with id {1} from the \"{0}\" file but failed", fileName, firstIconIndex, iconCount))
{
}
}
#endregion
#region DllImports
/// <summary>
/// Contains information about a file object.
/// </summary>
struct SHFILEINFO
{
/// <summary>
/// Handle to the icon that represents the file. You are responsible for
/// destroying this handle with DestroyIcon when you no longer need it.
/// </summary>
public IntPtr hIcon;
/// <summary>
/// Index of the icon image within the system image list.
/// </summary>
public IntPtr iIcon;
/// <summary>
/// Array of values that indicates the attributes of the file object.
/// For information about these values, see the IShellFolder::GetAttributesOf
/// method.
/// </summary>
public uint dwAttributes;
/// <summary>
/// String that contains the name of the file as it appears in the Microsoft
/// Windows Shell, or the path and file name of the file that contains the
/// icon representing the file.
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
/// <summary>
/// String that describes the type of file.
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
};
[Flags]
enum FileInfoFlags : int
{
/// <summary>
/// Retrieve the handle to the icon that represents the file and the index
/// of the icon within the system image list. The handle is copied to the
/// hIcon member of the structure specified by psfi, and the index is copied
/// to the iIcon member.
/// </summary>
SHGFI_ICON = 0x000000100,
/// <summary>
/// Indicates that the function should not attempt to access the file
/// specified by pszPath. Rather, it should act as if the file specified by
/// pszPath exists with the file attributes passed in dwFileAttributes.
/// </summary>
SHGFI_USEFILEATTRIBUTES = 0x000000010
}
/// <summary>
/// Creates an array of handles to large or small icons extracted from
/// the specified executable file, dynamic-link library (DLL), or icon
/// file.
/// </summary>
/// <param name="lpszFile">
/// Name of an executable file, DLL, or icon file from which icons will
/// be extracted.
/// </param>
/// <param name="nIconIndex">
/// <para>
/// Specifies the zero-based index of the first icon to extract. For
/// example, if this value is zero, the function extracts the first
/// icon in the specified file.
/// </para>
/// <para>
/// If this value is �1 and <paramref name="phiconLarge"/> and
/// <paramref name="phiconSmall"/> are both NULL, the function returns
/// the total number of icons in the specified file. If the file is an
/// executable file or DLL, the return value is the number of
/// RT_GROUP_ICON resources. If the file is an .ico file, the return
/// value is 1.
/// </para>
/// <para>
/// Windows 95/98/Me, Windows NT 4.0 and later: If this value is a
/// negative number and either <paramref name="phiconLarge"/> or
/// <paramref name="phiconSmall"/> is not NULL, the function begins by
/// extracting the icon whose resource identifier is equal to the
/// absolute value of <paramref name="nIconIndex"/>. For example, use -3
/// to extract the icon whose resource identifier is 3.
/// </para>
/// </param>
/// <param name="phIconLarge">
/// An array of icon handles that receives handles to the large icons
/// extracted from the file. If this parameter is NULL, no large icons
/// are extracted from the file.
/// </param>
/// <param name="phIconSmall">
/// An array of icon handles that receives handles to the small icons
/// extracted from the file. If this parameter is NULL, no small icons
/// are extracted from the file.
/// </param>
/// <param name="nIcons">
/// Specifies the number of icons to extract from the file.
/// </param>
/// <returns>
/// If the <paramref name="nIconIndex"/> parameter is -1, the
/// <paramref name="phIconLarge"/> parameter is NULL, and the
/// <paramref name="phiconSmall"/> parameter is NULL, then the return
/// value is the number of icons contained in the specified file.
/// Otherwise, the return value is the number of icons successfully
/// extracted from the file.
/// </returns>
[DllImport("Shell32", CharSet = CharSet.Auto)]
extern static int ExtractIconEx(
[MarshalAs(UnmanagedType.LPTStr)]
string lpszFile,
int nIconIndex,
IntPtr[] phIconLarge,
IntPtr[] phIconSmall,
int nIcons);
[DllImport("Shell32", CharSet = CharSet.Auto)]
extern static IntPtr SHGetFileInfo(
string pszPath,
int dwFileAttributes,
out SHFILEINFO psfi,
int cbFileInfo,
FileInfoFlags uFlags);
#endregion
/// <summary>
/// Two constants extracted from the FileInfoFlags, the only that are
/// meaningfull for the user of this class.
/// </summary>
public enum SystemIconSize : int
{
Large = 0x000000000,
Small = 0x000000001
}
/// <summary>
/// Get the number of icons in the specified file.
/// </summary>
/// <param name="fileName">Full path of the file to look for.</param>
/// <returns></returns>
static int GetIconsCountInFile(string fileName)
{
return ExtractIconEx(fileName, -1, null, null, 0);
}
#region ExtractIcon-like functions
public static void ExtractEx(string fileName, List<Icon> largeIcons,
List<Icon> smallIcons, int firstIconIndex, int iconCount)
{
/*
* Memory allocations
*/
IntPtr[] smallIconsPtrs = null;
IntPtr[] largeIconsPtrs = null;
if (smallIcons != null)
{
smallIconsPtrs = new IntPtr[iconCount];
}
if (largeIcons != null)
{
largeIconsPtrs = new IntPtr[iconCount];
}
/*
* Call to native Win32 API
*/
int apiResult = ExtractIconEx(fileName, firstIconIndex, largeIconsPtrs, smallIconsPtrs, iconCount);
if (apiResult != iconCount)
{
throw new UnableToExtractIconsException(fileName, firstIconIndex, iconCount);
}
/*
* Fill lists
*/
if (smallIcons != null)
{
smallIcons.Clear();
foreach (IntPtr actualIconPtr in smallIconsPtrs)
{
smallIcons.Add(Icon.FromHandle(actualIconPtr));
}
}
if (largeIcons != null)
{
largeIcons.Clear();
foreach (IntPtr actualIconPtr in largeIconsPtrs)
{
largeIcons.Add(Icon.FromHandle(actualIconPtr));
}
}
}
public static List<Icon> ExtractEx(string fileName, SystemIconSize size,
int firstIconIndex, int iconCount)
{
List<Icon> iconList = new List<Icon>();
switch (size)
{
case SystemIconSize.Large:
ExtractEx(fileName, iconList, null, firstIconIndex, iconCount);
break;
case SystemIconSize.Small:
ExtractEx(fileName, null, iconList, firstIconIndex, iconCount);
break;
default:
throw new ArgumentOutOfRangeException("size");
}
return iconList;
}
public static void Extract(string fileName, List<Icon> largeIcons, List<Icon> smallIcons)
{
int iconCount = GetIconsCountInFile(fileName);
ExtractEx(fileName, largeIcons, smallIcons, 0, iconCount);
}
public static List<Icon> Extract(string fileName, SystemIconSize size)
{
int iconCount = GetIconsCountInFile(fileName);
return ExtractEx(fileName, size, 0, iconCount);
}
public static Icon ExtractOne(string fileName, int index, SystemIconSize size)
{
try
{
List<Icon> iconList = ExtractEx(fileName, size, index, 1);
return iconList[0];
}
catch (UnableToExtractIconsException)
{
throw new IconNotFoundException(fileName, index);
}
}
public static void ExtractOne(string fileName, int index,
out Icon largeIcon, out Icon smallIcon)
{
List<Icon> smallIconList = new List<Icon>();
List<Icon> largeIconList = new List<Icon>();
try
{
ExtractEx(fileName, largeIconList, smallIconList, index, 1);
largeIcon = largeIconList[0];
smallIcon = smallIconList[0];
}
catch (UnableToExtractIconsException)
{
throw new IconNotFoundException(fileName, index);
}
}
#endregion
//this will look throw the registry
//to find if the Extension have an icon.
public static Icon IconFromExtension(string extension,
SystemIconSize size)
{
// Add the '.' to the extension if needed
if (extension[0] != '.') extension = '.' + extension;
//opens the registry for the wanted key.
RegistryKey Root = Registry.ClassesRoot;
RegistryKey ExtensionKey = Root.OpenSubKey(extension);
ExtensionKey.GetValueNames();
RegistryKey ApplicationKey =
Root.OpenSubKey(ExtensionKey.GetValue("").ToString());
//gets the name of the file that have the icon.
string IconLocation =
ApplicationKey.OpenSubKey("DefaultIcon").GetValue("").ToString();
string[] IconPath = IconLocation.Split(',');
if (IconPath[1] == null) IconPath[1] = "0";
IntPtr[] Large = new IntPtr[1], Small = new IntPtr[1];
//extracts the icon from the file.
ExtractIconEx(IconPath[0],
Convert.ToInt16(IconPath[1]), Large, Small, 1);
return size == SystemIconSize.Large ?
Icon.FromHandle(Large[0]) : Icon.FromHandle(Small[0]);
}
public static Icon IconFromExtensionShell(string extension, SystemIconSize size)
{
//add '.' if nessesry
if (extension[0] != '.') extension = '.' + extension;
//temp struct for getting file shell info
SHFILEINFO fileInfo = new SHFILEINFO();
SHGetFileInfo(
extension,
0,
out fileInfo,
Marshal.SizeOf(fileInfo),
FileInfoFlags.SHGFI_ICON | FileInfoFlags.SHGFI_USEFILEATTRIBUTES | (FileInfoFlags)size);
return Icon.FromHandle(fileInfo.hIcon);
}
public static Icon IconFromResource(string resourceName)
{
Assembly assembly = Assembly.GetCallingAssembly();
return new Icon(assembly.GetManifestResourceStream(resourceName));
}
/// <summary>
/// Parse strings in registry who contains the name of the icon and
/// the index of the icon an return both parts.
/// </summary>
/// <param name="regString">The full string in the form "path,index" as found in registry.</param>
/// <param name="fileName">The "path" part of the string.</param>
/// <param name="index">The "index" part of the string.</param>
public static void ExtractInformationsFromRegistryString(
string regString, out string fileName, out int index)
{
if (regString == null)
{
throw new ArgumentNullException("regString");
}
if (regString.Length == 0)
{
throw new ArgumentException("The string should not be empty.", "regString");
}
index = 0;
string[] strArr = regString.Replace("\"", "").Split(',');
fileName = strArr[0].Trim();
if (strArr.Length > 1)
{
int.TryParse(strArr[1].Trim(), out index);
}
}
public static Icon ExtractFromRegistryString(string regString, SystemIconSize size)
{
string fileName;
int index;
ExtractInformationsFromRegistryString(regString, out fileName, out index);
return ExtractOne(fileName, index, size);
}
}
}
Take a look at: http://mvolo.com/display-pretty-file-icons-in-your-aspnet-applications-with-iconhandler/
It's not the cleanest solution but it works. Otherwise, try to get your hands on a library of Icons that's based on mime type or file extension.
I'm sure you have already found a solution for your problems but for the benefit of others i have made some modifications to VirtualBlackFox's solution.
Just replace the IconFromExtension method...
public static Icon IconFromExtension(string extension,
SystemIconSize size)
{
// Add the '.' to the extension if needed
if (extension[0] != '.') extension = '.' + extension;
//opens the registry for the wanted key.
RegistryKey Root = Registry.ClassesRoot;
RegistryKey ExtensionKey = Root.OpenSubKey(extension);
ExtensionKey.GetValueNames();
RegistryKey ApplicationKey =
Root.OpenSubKey(ExtensionKey.GetValue("").ToString());
RegistryKey CurrentVer = null;
try
{
CurrentVer = Root.OpenSubKey(ApplicationKey.OpenSubKey("CurVer").GetValue("").ToString());
}
catch (Exception ex)
{
//current version not found... carry on without it?
}
if (CurrentVer != null)
ApplicationKey = CurrentVer;
//gets the name of the file that have the icon.
string IconLocation =
ApplicationKey.OpenSubKey("DefaultIcon").GetValue("").ToString();
string[] IconPath = IconLocation.Split(',');
IntPtr[] Large = null;
IntPtr[] Small = null;
int iIconPathNumber = 0;
if (IconPath.Length > 1)
iIconPathNumber = 1;
else
iIconPathNumber = 0;
if (IconPath[iIconPathNumber] == null) IconPath[iIconPathNumber] = "0";
Large = new IntPtr[1];
Small = new IntPtr[1];
//extracts the icon from the file.
if (iIconPathNumber > 0)
{
ExtractIconEx(IconPath[0],
Convert.ToInt16(IconPath[iIconPathNumber]), Large, Small, 1);
}
else
{
ExtractIconEx(IconPath[0],
Convert.ToInt16(0), Large, Small, 1);
}
return size == SystemIconSize.Large ?
Icon.FromHandle(Large[0]) : Icon.FromHandle(Small[0]);
}
Icon.ExtractAssociatedIcon()
won't do the trick. As MSDN states it only extracts icons contained in the file. So creating dummy files won't help either. To my best knowledge you have to go the p/invoke way to get to these icons. A question related to this is this. MaLio seems to have a quite complete example of how to get the icons with p/invoke.
I don't know of a platform agnostic way to do this either (and I don't think there is one).
Sorry i couldn't provide you with better news!
Instead of browsing through registry, one may use the IQueryAssociations interface. This interface can also be used to get more information about registered file types (see i.e. ASSOCSTR type). Below I attach code which replaces IconFromExtension method from VirtualBlackFox's solution (parts of his code are left untouched):
public static Icon IconFromExtension(string extension, SystemIconSize size)
{
if (extension[0] != '.') extension = '.' + extension;
object obj;
shell.AssocCreate(shell.CLSID_QueryAssociations, ref shell.IID_IQueryAssociations, out obj);
var qa = (shell.IQueryAssociations)obj;
qa.Init(shell.ASSOCF.INIT_DEFAULTTOSTAR, Convert.ToString(extension), UIntPtr.Zero, IntPtr.Zero);
var bufSize = 0;
qa.GetString(shell.ASSOCF.NOTRUNCATE, shell.ASSOCSTR.DEFAULTICON, null, null, ref bufSize);
var sb = new StringBuilder(bufSize);
qa.GetString(shell.ASSOCF.NOTRUNCATE, shell.ASSOCSTR.DEFAULTICON, null, sb, ref bufSize);
if (!String.IsNullOrEmpty(sb.ToString()))
{
var iconLocation = sb.ToString();
var iconPath = iconLocation.Split(',');
var iIconPathNumber = iconPath.Length > 1 ? 1 : 0;
if (iconPath[iIconPathNumber] == null) iconPath[iIconPathNumber] = "0";
var large = new IntPtr[1];
var small = new IntPtr[1];
//extracts the icon from the file.
ExtractIconEx(iconPath[0],
iIconPathNumber > 0 ? Convert.ToInt16(iconPath[iIconPathNumber]) : Convert.ToInt16(0),
large,
small, 1);
return size == SystemIconSize.Large
? Icon.FromHandle(large[0])
: Icon.FromHandle(small[0]);
}
return IntPtr.Zero;
}
In addition to the above code one needs "shell" class - a wrapper of Shell API (below it is limited to AssocCreate and necessary types):
using System;
using System.Runtime.InteropServices;
using System.Text;
#pragma warning disable 1591
// ReSharper disable InconsistentNaming
namespace <put_your_appropriate_namespace_here>
{
public class shell
{
[DllImport("shlwapi.dll")]
public extern static int AssocCreate(
Guid clsid,
ref Guid riid,
[MarshalAs(UnmanagedType.Interface)] out object ppv);
[Flags]
public 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
}
public enum ASSOCSTR
{
COMMAND = 1,
EXECUTABLE,
FRIENDLYDOCNAME,
FRIENDLYAPPNAME,
NOOPEN,
SHELLNEWVALUE,
DDECOMMAND,
DDEIFEXEC,
DDEAPPLICATION,
DDETOPIC,
INFOTIP,
QUICKTIP,
TILEINFO,
CONTENTTYPE,
DEFAULTICON,
SHELLEXTENSION
}
public enum ASSOCKEY
{
SHELLEXECCLASS = 1,
APP,
CLASS,
BASECLASS
}
public enum ASSOCDATA
{
MSIDESCRIPTOR = 1,
NOACTIVATEHANDLER,
QUERYCLASSSTORE,
HASPERUSERASSOC,
EDITFLAGS,
VALUE
}
[Guid("c46ca590-3c3f-11d2-bee6-0000f805ca57"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public 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
}
public static Guid CLSID_QueryAssociations = new Guid("a07034fd-6caa-4954-ac3f-97a27216f98a");
public static Guid IID_IQueryAssociations = new Guid("c46ca590-3c3f-11d2-bee6-0000f805ca57");
}
}
For WPF users, who need ImageSource:
Replace the return type from Icon to ImageSource and the return clause to:
var iconPtr = size == SystemIconSize.Large ? large[0] : small[0];
if (iconPtr != IntPtr.Zero)
{
return Imaging.CreateBitmapSourceFromHIcon(
iconPtr,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
This class should do the job. Pass either a filename (with path) or folder name (with path).
public static class FileIcon
{
[DllImport("shell32.dll")]
private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);
[StructLayout(LayoutKind.Sequential)]
private 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;
};
private const uint SHGFI_ICON = 0x100;
private const uint SHGFI_LARGEICON = 0x0; // 'Large icon
private const uint SHGFI_SMALLICON = 0x1; // 'Small icon
public static System.Drawing.Icon GetLargeIcon(string file)
{
FileIcon.SHFILEINFO shinfo = new FileIcon.SHFILEINFO();
IntPtr hImgLarge = FileIcon.SHGetFileInfo(file, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), FileIcon.SHGFI_ICON | FileIcon.SHGFI_LARGEICON);
return System.Drawing.Icon.FromHandle(shinfo.hIcon);
}
public static System.Drawing.Icon GetSmallIcon(string file)
{
FileIcon.SHFILEINFO shinfo = new FileIcon.SHFILEINFO();
IntPtr hImgLarge = FileIcon.SHGetFileInfo(file, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), FileIcon.SHGFI_ICON | FileIcon.SHGFI_SMALLICON);
return System.Drawing.Icon.FromHandle(shinfo.hIcon);
}
}