Wanted to have a wrapper around the drag and drop outlook attachments for the File Select Control, but that turned somewhat harder than expected. Saving one attachment was easy enough. It could be done completely managed (found some other examples that do the same on the net), but saving multiple wasn't to be found in .net (at least not as far as I could find :-/) and even though the File Select control doesn't support multiple files (yet), the wrapper certainly should :p
The problem is that with the windows forms DataObject created when dropping, you can only get data on the default index. The only solution seemed to be using the unmanaged wrappers.Luckily the first step was easy: the DataObject explicitely implements the ComTypes.IDataObject interface and thus could be parsed. Getting the proper formatter and all seemed to be a bigger headache. To make a long story (and more than half a day of trial and error frustration to create the entire wrapper) short, a combination of using the .net DataObject and the unmanaged wrappers lead to the solution (see below in this post to read just that part).
The entire wrapper: (for an example, see the File Select Control mentioned above)
namespace Subro.Outlook
{
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.IO;
//the interopservices are needed to get to unmanaged code to get the streams
//for attachments if there are more. (for one, it can be done problemless with managed code)
using System.Runtime.InteropServices;
using ct =System.Runtime.InteropServices.ComTypes;
using System.Runtime.InteropServices.ComTypes;
///
/// Wrapper around the drop data from attachments from outlook
///
public class OutlookAttachmentDropCatcher{
public
static string[] GetFiles(
DragEventArgs e)
{
return new OutlookAttachmentDropCatcher(e).GetFiles();
}
///
/// The name to identify a file group when dragging outlook files
///
public const string GroupIdentifier = "FileGroupDescriptor";
///
/// The name to identify the raw data of the attachments in the clipboard
///
DataFormats.Format ContentsIdentifier = DataFormats.GetFormat("FileContents");
///
/// The drop data with which this OutlookAttachment object was created
///
public readonly DataObject DropData;
public OutlookAttachmentDropCatcher(
DragEventArgs e)
: this((DataObject)e.Data)
{
}
public OutlookAttachmentDropCatcher(
DataObject Data)
{
//Source: http://blogs.vbcity.com/hotdog/archive/2006/03/16/5896.aspx
//check there for updates....
DropData = Data;
MemoryStream s = (MemoryStream)Data.GetData(GroupIdentifier);
ReadHeaders(s);
s.Close();
}
#region COM structs
struct FILETIME
{
int dwLowDateTime;
int dwHighDateTime;
public FILETIME(
BinaryReader br)
{
dwLowDateTime = br.ReadInt32();
dwHighDateTime = br.ReadInt32();
}
}
struct
CLSID{
byte[] ID;
public CLSID(
BinaryReader br)
{
ID = br.ReadBytes(16);
}
}
struct SizeL
{
public
int X, Y;
public SizeL(
BinaryReader br)
{
X = br.ReadInt32();
Y = br.ReadInt32();
}
}
struct FILEDESCRIPTOR
{
int dwFlags;
CLSID clsid;
SizeL sizel;
SizeL pointl;
int dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
int nFileSizeHigh;
int nFileSizeLow;
public string FileName;
public FILEDESCRIPTOR(
BinaryReader br)
{
//these flags aren't really filled when coming
//from outlook, but who knows....someday it might be used :p
dwFlags = br.ReadInt32();
clsid = new CLSID(br);
sizel = new SizeL(br);
pointl = new SizeL(br);
dwFileAttributes = br.ReadInt32();
ftCreationTime = new FILETIME(br);
ftLastAccessTime = new FILETIME(br);
ftLastWriteTime = new FILETIME(br);
nFileSizeHigh = br.ReadInt32();
nFileSizeLow = br.ReadInt32();
const int MAX_PATH = 256;
byte[] bytes = br.ReadBytes(MAX_PATH);
int i = 0;
for (; i < MAX_PATH && bytes[i] != 0; i++)
{ }
FileName = Encoding.ASCII.GetString(bytes, 0, i);
br.ReadInt32(); //not entirely sure what this entry does...
}
}
struct
FILEGROUPDESCRIPTOR{
uint cItems;
FILEDESCRIPTOR[] fgd;
public readonly string[] Names;
public FILEGROUPDESCRIPTOR(
MemoryStream s)
{
BinaryReader br =
new BinaryReader(s);
cItems = br.ReadUInt32();
fgd =
new FILEDESCRIPTOR[cItems];
Names =
new string[cItems];
for (
int i = 0; i < cItems; i++)
{
fgd[i] = new FILEDESCRIPTOR(br);
Names[i] = fgd[i].FileName;
}
}
}
#endregion
FILEGROUPDESCRIPTOR descriptor;
void ReadHeaders(
MemoryStream s)
{
descriptor = new FILEGROUPDESCRIPTOR(s);
}
string tempfolder =
Environment.GetFolderPath(
Environment.
SpecialFolder.InternetCache) +
@"\OLK3D2\";
///
/// The folder where temporary files are stored (when dragging files from outlook)
///
string TempFolder
{
get
{
return tempfolder;
}
set
{
if (value == null) throw new ArgumentNullException();
tempfolder = value;
if (!tempfolder.EndsWith(@"\")) tempfolder += @"\";
} }
///
/// Gets the stream of the specified attachment (without saving)
/// NB: Name must be one of the attachment names (see )
///
///
///
public MemoryStream GetStream(
string Name)
{
return GetStream(IndexOf(Name));
}
public
int IndexOf(
string AttachmentName)
{
return Array.IndexOf<string>(descriptor.Names, AttachmentName);
}
///
/// Gets the stream of the FIRST attachment
///
///
public MemoryStream GetStream()
{
return GetStream(0);
}
///
/// Gets the stream at Index of the available attachments
///
///
///
public MemoryStream GetStream(
int Index)
{
if (Index == 0)
{
//the first is easy: just copy the formatted .net data
MemoryStream ms = (MemoryStream)DropData.GetData(ContentsIdentifier.Name);
return ms;
}
return GetHigherAttachment(Index); }
#region unmanaged
MemoryStream GetHigherAttachment(
int Index)
{
//this is where it gets ugly :p
ct.IDataObject data = (ct.IDataObject)DropData;
STGMEDIUM medium;
#region possible errors (not used)
/*
unchecked
{
const int
S_OK = 0,
DV_E_LINDEX = (int)0x80040068,
DV_E_FORMATETC = (int)0x80040064,
DV_E_TYMED = (int)0x80040069,
//DV_E_DVASPECT = -2147221397,
E_INVALIDARG = (int)0x80070057,
E_UNEXPECTED = (int)0x8000FFFF,
OLE_E_NOTRUNNING = (int)0x80040005,
E_OUTOFMEMORY = (int)0x8007000E;
}*/
#endregion
//find the proper format
IEnumFORMATETC formats = data.EnumFormatEtc(DATADIR.DATADIR_GET);
int cnt = DropData.GetFormats().Length;
FORMATETC[] resF = new FORMATETC[cnt];
int[] resI = new int[cnt];
formats.Next(cnt, resF, resI);
int index = Array.IndexOf<string>(DropData.GetFormats(), ContentsIdentifier.Name);
FORMATETC format = resF[index];
//set the index to get
format.lindex = Index;
//get the istream object
data.GetData(ref format, out medium);
IStream stream = (IStream)Marshal.GetObjectForIUnknown(medium.unionmember);
//read the istream into a memorystream (saving could have been done directly, but this is the more general approach)
MemoryStream ms =
new MemoryStream();
const int Block_Length = 1024;
byte[] bytes =
new byte[Block_Length];
ulong count = 0;
IntPtr countptr =
Marshal.AllocHGlobal(8);
Marshal.StructureToPtr(count, countptr,
true);
try{
for (; ; )
{
stream.Read(bytes, Block_Length, countptr);
count = (uint)Marshal.PtrToStructure(countptr, typeof(uint));
if (count == 0) break;
ms.Write(bytes, 0, (int)count);
}
}
finally
{
Marshal.FreeHGlobal(countptr);
}
return ms;
}
#endregion
///
/// The number of dropped attachments
///
public int AttachmentCount
{
get { return descriptor.Names.Length; }
}
///
/// Returns the attachment names
///
///
public string[] GetNames()
{
return descriptor.Names;
}
///
/// Saves the file to the temp directory and returns the full location.
/// NB: Name must be one of the attachments (see )
///
///
///
public string GetFile(
string Name)
{
return GetFile(IndexOf(Name));
}
///
/// Saves the FIRST file to the temp directory and returns its url. You can use this function
/// if you are sure there's only one attachment
///
///
public string GetFile()
{
return GetFile(0);
}
///
/// Saves the files at the Index position of the available attachments
///
///
///
public string GetFile(
int Index)
{
string Name = descriptor.Names[Index];
string url = tempfolder + Name;
return SaveAttachment(Name, url);
}
private
bool alwaysoverwrite =
true;
///
/// Indicates if (temporary) files may always be overwritten when saving
///
public bool AlwaysOverWrite
{
get { return alwaysoverwrite; }
set { alwaysoverwrite = value; }
}
///
/// Saves the Attachment to the specified location
/// NB: Name must be one of the attachments (see )
///
///
///
public string SaveAttachment(
string AttachmentName,
string Path)
{
return SaveAttachment(AttachmentName, Path, alwaysoverwrite);
}
public
string SaveAttachment(
int AttachmentIndex,
string Path)
{
return SaveAttachment(AttachmentIndex, Path, alwaysoverwrite);
}
public string SaveAttachment(string AttachmentName, string Path, bool AlwaysOverWrite)
{
return SaveAttachment(IndexOf(AttachmentName), Path, AlwaysOverWrite);
}
public string SaveAttachment(int AttachmentIndex, string Path, bool AlwaysOverWrite)
{
if (!AlwaysOverWrite &&
File.Exists(Path))
{
switch (
MessageBox.Show(
"Temporary file for " + descriptor.Names[AttachmentIndex] +
" allready exists. Overwrite?",
"file exists",
MessageBoxButtons.YesNoCancel))
{
case
DialogResult.No:
return Path;
case DialogResult.Cancel:
return null;
} }
File.WriteAllBytes(Path, GetStream(AttachmentIndex).ToArray());
return Path;
}
///
/// saves all files to the temporary directory and returns the url's
///
///
public string[] GetFiles()
{
string[] files =
new string[descriptor.Names.Length];
int i = 0;
foreach (
string name
in descriptor.Names)
{
files[i++] = GetFile(name);
}
return files;
}
}
}
. . .
The specific multiple attachments code:
#region unmanaged
MemoryStream GetHigherAttachment(
int Index)
{
//this is where it gets ugly :p
ct.IDataObject data = (ct.IDataObject)DropData;
STGMEDIUM medium;
#region possible errors (not used)
/*
unchecked
{
const int
S_OK = 0,
DV_E_LINDEX = (int)0x80040068,
DV_E_FORMATETC = (int)0x80040064,
DV_E_TYMED = (int)0x80040069,
//DV_E_DVASPECT = -2147221397,
E_INVALIDARG = (int)0x80070057,
E_UNEXPECTED = (int)0x8000FFFF,
OLE_E_NOTRUNNING = (int)0x80040005,
E_OUTOFMEMORY = (int)0x8007000E;
}*/
#endregion
//find the proper format
IEnumFORMATETC formats = data.EnumFormatEtc(DATADIR.DATADIR_GET);
int cnt = DropData.GetFormats().Length;
FORMATETC[] resF = new FORMATETC[cnt];
int[] resI = new int[cnt];
formats.Next(cnt, resF, resI);
int index = Array.IndexOf<string>(DropData.GetFormats(), ContentsIdentifier.Name);
FORMATETC format = resF[index];
//set the index to get
format.lindex = Index;
//get the istream object
data.GetData(ref format, out medium);
IStream stream = (IStream)Marshal.GetObjectForIUnknown(medium.unionmember);
//read the istream into a memorystream (saving could have been done directly, but this is the more general approach)
MemoryStream ms =
new MemoryStream();
const int Block_Length = 1024;
byte[] bytes =
new byte[Block_Length];
ulong count = 0;
IntPtr countptr =
Marshal.AllocHGlobal(8);
Marshal.StructureToPtr(count, countptr,
true);
try{
for (; ; )
{
stream.Read(bytes, Block_Length, countptr);
count = (uint)Marshal.PtrToStructure(countptr, typeof(uint));
if (count == 0) break;
ms.Write(bytes, 0, (int)count);
}
}
finally
{
Marshal.FreeHGlobal(countptr);
}
return ms;
}
#endregion
. . .