|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThe firm I work for sells a product that implements its own file server and runtime environment. The code base is old and two years ago a project was initiated to create a new interface using C# and .NET 2.0. One of the features on the wish list was the ability to copy/cut/drag/drop files between the proprietary file server environment and Windows Explorer. Bringing files into the proprietary environment was easy through the use of the Using the CodeThis code implements a class called For this project, the following using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Security.Permissions;
The first class that needs to be defined is a public class NativeMethods
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr GlobalAlloc(int uFlags, int dwBytes);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr GlobalFree(HandleRef handle);
// Clipboard formats used for cut/copy/drag operations
public const string CFSTR_PREFERREDDROPEFFECT = "Preferred DropEffect";
public const string CFSTR_PERFORMEDDROPEFFECT = "Performed DropEffect";
public const string CFSTR_FILEDESCRIPTORW = "FileGroupDescriptorW";
public const string CFSTR_FILECONTENTS = "FileContents";
// File Descriptor Flags
public const Int32 FD_CLSID = 0x00000001;
public const Int32 FD_SIZEPOINT = 0x00000002;
public const Int32 FD_ATTRIBUTES = 0x00000004;
public const Int32 FD_CREATETIME = 0x00000008;
public const Int32 FD_ACCESSTIME = 0x00000010;
public const Int32 FD_WRITESTIME = 0x00000020;
public const Int32 FD_FILESIZE = 0x00000040;
public const Int32 FD_PROGRESSUI = 0x00004000;
public const Int32 FD_LINKUI = 0x00008000;
// Global Memory Flags
public const Int32 GMEM_MOVEABLE = 0x0002;
public const Int32 GMEM_ZEROINIT = 0x0040;
public const Int32 GHND = (GMEM_MOVEABLE | GMEM_ZEROINIT);
public const Int32 GMEM_DDESHARE = 0x2000;
// IDataObject constants
public const Int32 DV_E_TYMED = unchecked((Int32)0x80040069);
}
With this class in place, we need to define the namespace MyData.Extensions
{
public class DataObjectEx :
DataObject, System.Runtime.InteropServices.ComTypes.IDataObject
{
private static readonly TYMED[] ALLOWED_TYMEDS =
new TYMED[] {
TYMED.TYMED_ENHMF,
TYMED.TYMED_GDI,
TYMED.TYMED_HGLOBAL,
TYMED.TYMED_ISTREAM,
TYMED.TYMED_MFPICT};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct FILEDESCRIPTOR
{
public UInt32 dwFlags;
public Guid clsid;
public System.Drawing.Size sizel;
public System.Drawing.Point pointl;
public UInt32 dwFileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
public UInt32 nFileSizeHigh;
public UInt32 nFileSizeLow;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public String cFileName;
}
public struct SelectedItem
{
public String FileName;
public DateTime WriteTime;
public Int64 FileSize;
}
private SelectedItem[] m_SelectedItems;
private Int32 m_lindex;
public DataObjectEx(SelectedItem[] selectedItems)
{
m_SelectedItems = selectedItems;
}
The public structure The next method we need to implement is an override of public override object GetData(string format, bool autoConvert)
{
if (String.Compare(format, NativeMethods.CFSTR_FILEDESCRIPTORW,
StringComparison.OrdinalIgnoreCase) == 0 && m_SelectedItems != null)
{
base.SetData(NativeMethods.CFSTR_FILEDESCRIPTORW,
GetFileDescriptor(m_SelectedItems));
}
else if (String.Compare(format, NativeMethods.CFSTR_FILECONTENTS,
StringComparison.OrdinalIgnoreCase) == 0)
{
base.SetData(NativeMethods.CFSTR_FILECONTENTS,
GetFileContents(m_SelectedItems, m_lindex));
}
else if (String.Compare(format, NativeMethods.CFSTR_PERFORMEDDROPEFFECT,
StringComparison.OrdinalIgnoreCase) == 0)
{
//TODO: Cleanup routines after paste has been performed
}
return base.GetData(format, autoConvert);
}
This next method returns a It is in this method where the data in the array of Once the file descriptor structure has been populated, it is necessary to write it to the memory stream. This involves some marshaling to convert the structure to a byte array that can then be written to the memory stream. After all of the virtual file descriptors have been created, the method returns the memory stream to the caller: private MemoryStream GetFileDescriptor(SelectedItem[] SelectedItems)
{
MemoryStream FileDescriptorMemoryStream = new MemoryStream();
// Write out the FILEGROUPDESCRIPTOR.cItems value
FileDescriptorMemoryStream.Write
(BitConverter.GetBytes(SelectedItems.Length), 0, sizeof(UInt32));
FILEDESCRIPTOR FileDescriptor = new FILEDESCRIPTOR();
foreach (SelectedItem si in SelectedItems)
{
FileDescriptor.cFileName = si.FileName;
Int64 FileWriteTimeUtc = si.WriteTime.ToFileTimeUtc();
FileDescriptor.ftLastWriteTime.dwHighDateTime =
(Int32)(FileWriteTimeUtc >> 32);
FileDescriptor.ftLastWriteTime.dwLowDateTime =
(Int32)(FileWriteTimeUtc & 0xFFFFFFFF);
FileDescriptor.nFileSizeHigh = (UInt32)(si.FileSize >> 32);
FileDescriptor.nFileSizeLow = (UInt32)(si.FileSize & 0xFFFFFFFF);
FileDescriptor.dwFlags = NativeMethods.FD_WRITESTIME |
NativeMethods.FD_FILESIZE | NativeMethods.FD_PROGRESSUI;
// Marshal the FileDescriptor structure into a
// byte array and write it to the MemoryStream.
Int32 FileDescriptorSize = Marshal.SizeOf(FileDescriptor);
IntPtr FileDescriptorPointer = Marshal.AllocHGlobal(FileDescriptorSize);
Marshal.StructureToPtr(FileDescriptor, FileDescriptorPointer, true);
Byte[] FileDescriptorByteArray = new Byte[FileDescriptorSize];
Marshal.Copy(FileDescriptorPointer,
FileDescriptorByteArray, 0, FileDescriptorSize);
Marshal.FreeHGlobal(FileDescriptorPointer);
FileDescriptorMemoryStream.Write
(FileDescriptorByteArray, 0, FileDescriptorByteArray.Length);
}
return FileDescriptorMemoryStream;
}
This next method returns a memory stream containing the file contents for the file number being requested by Windows Explorer through the private MemoryStream GetFileContents(SelectedItem[] SelectedItems, Int32 FileNumber)
{
MemoryStream FileContentMemoryStream = null;
if (SelectedItems != null && FileNumber < SelectedItems.Length)
{
FileContentMemoryStream = new MemoryStream();
SelectedItem si = SelectedItems[FileNumber];
// ******************************************************************
// TODO: Get the virtual file contents and place
// the contents in the byte array bBuffer.
// If the contents are zero length then a single byte
// must be supplied to Windows
// Explorer otherwise the transfer will fail.
// If this is part of a multi-file transfer,
// the entire transfer will fail at this point
// if the buffer is zero length.
// ******************************************************************
Byte[] bBuffer;
// Must send at least one byte for a zero length file to prevent stoppages.
if (bBuffer.Length == 0)
bBuffer = new Byte[1];
FileContentMemoryStream.Write(bBuffer, 0, bBuffer.Length);
}
return FileContentMemoryStream;
}
These last two methods are used to get a copy of the [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
void System.Runtime.InteropServices.ComTypes.IDataObject.GetData
(ref System.Runtime.InteropServices.ComTypes.FORMATETC formatetc,
out System.Runtime.InteropServices.ComTypes.STGMEDIUM medium)
{
if (formatetc.cfFormat == (Int16)DataFormats.GetFormat
(NativeMethods.CFSTR_FILECONTENTS).Id)
m_lindex = formatetc.lindex;
medium = new System.Runtime.InteropServices.ComTypes.STGMEDIUM();
if (GetTymedUseable(formatetc.tymed))
{
if ((formatetc.tymed & TYMED.TYMED_HGLOBAL) != TYMED.TYMED_NULL)
{
medium.tymed = TYMED.TYMED_HGLOBAL;
medium.unionmember = NativeMethods.GlobalAlloc
(NativeMethods.GHND | NativeMethods.GMEM_DDESHARE, 1);
if (medium.unionmember == IntPtr.Zero)
{
throw new OutOfMemoryException();
}
try
{
((System.Runtime.InteropServices.ComTypes.IDataObject)this).
GetDataHere(ref formatetc, ref medium);
return;
}
catch
{
NativeMethods.GlobalFree(new HandleRef((STGMEDIUM)medium,
medium.unionmember));
medium.unionmember = IntPtr.Zero;
throw;
}
}
medium.tymed = formatetc.tymed;
((System.Runtime.InteropServices.ComTypes.IDataObject)this).
GetDataHere(ref formatetc, ref medium);
}
else
{
Marshal.ThrowExceptionForHR(NativeMethods.DV_E_TYMED);
}
}
private static Boolean GetTymedUseable(TYMED tymed)
{
for (Int32 i = 0; i < ALLOWED_TYMEDS.Length; i++)
{
if ((tymed & ALLOWED_TYMEDS[i]) != TYMED.TYMED_NULL)
{
return true;
}
}
return false;
}
Finally, the following code snippet shows a sample implementation using Int32 NumItems = 3;
DataObjectEx.SelectedItem[] SelectedItems =
new DataObjectEx.SelectedItem[NumItems];
for (Int32 ItemCount = 0; ItemCount < SelectedItems.Length; ItemCount++)
{
// TODO: Get virtual file name
SelectedItems[ItemCount].FileName = "My Virtual File";
// TODO: Get virtual file date
SelectedItems[ItemCount].WriteTime = new DateTime(2008, 1, 1);
// TODO: Get virtual file size
SelectedItems[ItemCount].FileSize = 0;
}
DataObjectEx dataObject = new DataObjectEx(SelectedItems);
dataObject.SetData(NativeMethods.CFSTR_FILEDESCRIPTORW, null);
dataObject.SetData(NativeMethods.CFSTR_FILECONTENTS, null);
dataObject.SetData(NativeMethods.CFSTR_PERFORMEDDROPEFFECT, null);
Clipboard.SetDataObject(dataObject);
Points of InterestThis was a very interesting project to work on. While working on it, I was not even sure it would be possible to do what I wanted to do. With no examples available anywhere on using these formats, it was truly a "roll your own" scenario. I completed most of the class very quickly but became hung up on getting access to the History
| ||||||||||||||||||||