Asked by:
Drag'n'drop to explorer fails for large files

-
Hello.
I have very strange problem with D'n'D. In two words, when I drag item from my application (.NET 3.5 Visual Studio 2012) to explorer it fails with message 'Not enough storage is available to complete operation' if file size is more then approximately 21 MB.
D'n'd is implemented using own class that is inherited from DataObject.
Imports System.Runtime.InteropServices Imports System.Runtime.InteropServices.ComTypes Imports System.IO Imports System.Security.Permissions Imports System.Windows.Forms ''' <summary> ''' DataObject capable of create files before drop to windows explorer ''' </summary> ''' <remarks></remarks> Public Class ShellDataObjectBase Inherits DataObject Implements System.Runtime.InteropServices.ComTypes.IDataObject Public FileContentMemoryStream As MemoryStream = Nothing Public FileDescriptorMemoryStream As MemoryStream Public FileName As String #Region "Types of media" ''' <summary> ''' Allowed types of media ''' </summary> ''' <remarks></remarks> Private Shared ReadOnly ALLOWED_TYMEDS As TYMED() = New TYMED() {TYMED.TYMED_ENHMF, TYMED.TYMED_GDI, TYMED.TYMED_FILE, TYMED.TYMED_HGLOBAL, TYMED.TYMED_ISTREAM, TYMED.TYMED_MFPICT} ''' <summary> ''' Is type of media allowed? ''' </summary> ''' <param name="tymed__1"></param> ''' <returns></returns> ''' <remarks></remarks> Private Shared Function GetTymedUseable(ByVal tymed__1 As TYMED) As [Boolean] For i As Int32 = 0 To ALLOWED_TYMEDS.Length - 1 If (tymed__1 And ALLOWED_TYMEDS(i)) <> TYMED.TYMED_NULL Then Return True End If Next Return False End Function #End Region #Region "Structures" <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _ Private Structure FILEDESCRIPTOR Public dwFlags As UInt32 Public clsid As Guid Public sizel As System.Drawing.Size Public pointl As System.Drawing.Point Public dwFileAttributes As UInt32 Public ftCreationTime As System.Runtime.InteropServices.ComTypes.FILETIME Public ftLastAccessTime As System.Runtime.InteropServices.ComTypes.FILETIME Public ftLastWriteTime As System.Runtime.InteropServices.ComTypes.FILETIME Public nFileSizeHigh As UInt32 Public nFileSizeLow As UInt32 <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)> _ Public cFileName As [String] End Structure 'Public Structure SelectedItem ' Public FileName As [String] ' Public WriteTime As DateTime ' Public FileSize As Int64 'End Structure #End Region #Region "Properties" #End Region #Region "Constructors" Public Sub New() Me.SetData(NativeMethods.CFSTR_FILEDESCRIPTORW, Nothing) Me.SetData(NativeMethods.CFSTR_FILECONTENTS, Nothing) Me.SetData(NativeMethods.CFSTR_PERFORMEDDROPEFFECT, Nothing) End Sub #End Region #Region "File methods" Private _Lindex As Int32 Private Function GetFileDescriptor() As MemoryStream FileDescriptorMemoryStream = New MemoryStream() ' Write out the FILEGROUPDESCRIPTOR.cItems value FileDescriptorMemoryStream.Write(BitConverter.GetBytes(1), 0, 4) Dim FileDescriptor As New FILEDESCRIPTOR() 'Dim FileName As String = "image_with_barcodes.tif" FileDescriptor.cFileName = FileName Dim FileWriteTimeUtc As Int64 = 0 Dim Cancel As Boolean = False Dim Key As String = String.Empty Dim FileSize As Long = New IO.FileInfo(FileName).Length FileDescriptor.nFileSizeHigh = CUInt(FileSize >> 32) FileDescriptor.nFileSizeLow = CUInt(FileSize And &HFFFFFFFFUI) FileDescriptor.ftLastWriteTime.dwHighDateTime = CInt(FileWriteTimeUtc >> 32) FileDescriptor.ftLastWriteTime.dwLowDateTime = CInt(FileWriteTimeUtc And &HFFFFFFFFUI) FileDescriptor.dwFlags = NativeMethods.FD_WRITESTIME Or NativeMethods.FD_FILESIZE Or NativeMethods.FD_PROGRESSUI ' Marshal the FileDescriptor structure into a ' byte array and write it to the MemoryStream. Dim FileDescriptorSize As Int32 = Marshal.SizeOf(FileDescriptor) Dim FileDescriptorPointer As IntPtr = Marshal.AllocHGlobal(FileDescriptorSize) Marshal.StructureToPtr(FileDescriptor, FileDescriptorPointer, True) Dim FileDescriptorByteArray As [Byte]() = New [Byte](FileDescriptorSize - 1) {} Marshal.Copy(FileDescriptorPointer, FileDescriptorByteArray, 0, FileDescriptorSize) Marshal.FreeHGlobal(FileDescriptorPointer) FileDescriptorMemoryStream.Write(FileDescriptorByteArray, 0, FileDescriptorByteArray.Length) Return FileDescriptorMemoryStream End Function Private Function GetFileContents() As MemoryStream If _Lindex < 1 Then If Not File.Exists(FileName) Then Return Nothing Dim bBuffer As [Byte]() = File.ReadAllBytes(FileName) FileContentMemoryStream = New MemoryStream(bBuffer.Length) 'Must send at least one byte for a zero length file to prevent stoppages. If bBuffer.Length = 0 Then bBuffer = New [Byte](0) {} End If FileContentMemoryStream.Write(bBuffer, 0, bBuffer.Length) End If Return FileContentMemoryStream End Function #End Region #Region "DataObject methods" Public Overloads Overrides Function GetData(ByVal format As String, ByVal autoConvert As Boolean) As Object Console.WriteLine(format) If [String].Compare(format, NativeMethods.CFSTR_FILEDESCRIPTORW, StringComparison.OrdinalIgnoreCase) = 0 Then MyBase.SetData(NativeMethods.CFSTR_FILEDESCRIPTORW, GetFileDescriptor()) ElseIf [String].Compare(format, NativeMethods.CFSTR_FILECONTENTS, StringComparison.OrdinalIgnoreCase) = 0 Then Dim Stream As MemoryStream = GetFileContents() If Not Stream Is Nothing Then MyBase.SetData(NativeMethods.CFSTR_FILECONTENTS, Stream) 'TODO: Cleanup routines after paste has been performed ElseIf [String].Compare(format, NativeMethods.CFSTR_PERFORMEDDROPEFFECT, StringComparison.OrdinalIgnoreCase) = 0 Then End If Return MyBase.GetData(format, autoConvert) End Function #End Region End Class
And I have small form with the next code:
Public Class Form1 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load ListBox1.Items.Add("Small image. Works fine") ListBox1.Items.Add("Large image. error occures") End Sub Private Sub ListBox1_MouseDown(sender As Object, e As MouseEventArgs) Handles ListBox1.MouseDown Dim DataObject As ShellDataObjectBase = New ShellDataObjectBase() If ListBox1.SelectedIndex = 0 Then DataObject.FileName = "Small.bmp" End If If ListBox1.SelectedIndex = 1 Then DataObject.FileName = "Large.bmp" End If ListBox1.DoDragDrop(DataObject, DragDropEffects.All) End Sub End Class
It works fine for 'Small.bmp' (wich is around 250kB) but fails for 'Large.bmp' which is 35 MB.
I don't see any exception in Visual Studio, so I guess that error is raised somewhere in kernel libraries. Maybe there is some problem with .NET memory management/memory allocation, but I don't know how to deal with this error. Any ideas what's going wrong here?
P.S. Can't add link to my test project: get message that my account is not verified. Do I need to perform any action to verify my account? Thanks.
- Changed type Maksim Sterekhov Friday, April 7, 2017 1:45 PM
Question
All replies
-
-
I'm not sure, but here's where I would first look:
Is the unmanaged code involved? Comment out everything that uses unmanaged code and repeat the test so that only the file content is present (ignore the file descriptor for now). Does the error still occur?
If so, I would factor out the memory streams in the data object. All that is necessary are the actual bytes. Copy the stream contents into an array, place the byte array on the data object, and close the memory stream. Does the error still occur?
Those would be my first two steps, personally.
Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
-
Let Explorer do the work for you. Get an IShellItem interface for your file. Then obtain an IDataObject interface by calling IShellItem::BindToHandler with BHID_DataObject. Of course you would use the managed code equivalents.
- Edited by RLWA32 Friday, April 7, 2017 1:37 PM
-
-
A discussion can be between regulars in this forum about how a problem the best can be solved.
However, then there is in fact seldom code involved.
A question is if something does not do as what persons expect and wants help with that.
But I see a lot of code, but not something like. I use A but think I can also use B, what do you think?
It seems to me simply a question to solve a running code problem.
Success
Cor- Edited by Cor Ligthert Friday, April 7, 2017 1:44 PM
-
-
-
-
-
I want to discuss what's going wrong with my code or what is the source of error. In my opinion it's a little bit more then just questions. But maybe I didn't get basic idea of 'questions' and 'discussions'.
As an aside, for future reference:
The thread should be a question when it could have a definitive answer. "What's wrong with my code?" is an example of a question which likely has one or more definitive answers.
The thread should be a discussion when it could not have a single definitive answer, or otherwise does not pose a question of any kind. "How could I achieve some goal" or "How to do a thing" are examples of threads which would likely be open-ended discussions.
Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
-
I'll preface this by saying I'm not a VB guy so the code may not be perfect, but the following had no problem dragging and dropping a 52 MB file
The form had a listbox that contained 1 file name
Imports System.Runtime.InteropServices Imports System.Runtime.InteropServices.ComTypes Public Class Form1 Dim IShellItemGUID As New Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe") Dim IDataObjectGUID As New Guid("0000010e-0000-0000-C000-000000000046") Dim BHID_DataObject As New Guid("B8C0BD9F-ED24-455c-83E6-D5390C4FE8C4") Public Enum SIGDN As UInteger NORMALDISPLAY = 0 PARENTRELATIVEPARSING = &H80018001UI PARENTRELATIVEFORADDRESSBAR = &H8001C001UI DESKTOPABSOLUTEPARSING = &H80028000UI PARENTRELATIVEEDITING = &H80031001UI DESKTOPABSOLUTEEDITING = &H8004C000UI FILESYSPATH = &H80058000UI URL = &H80068000UI End Enum <ComImport()> <Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")> <InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> Public Interface IShellItem <PreserveSig> Function BindToHandler(ByVal pbc As IntPtr, <MarshalAs(UnmanagedType.LPStruct)> ByVal bhid As Guid, <MarshalAs(UnmanagedType.LPStruct)> ByVal riid As Guid, <Out(), MarshalAs(UnmanagedType.Interface, IidParameterIndex:=2)> ByRef ppv As IDataObject) As <MarshalAs(UnmanagedType.Error)> Integer <PreserveSig> Function GetParent(ByRef ppsi As IShellItem) As <MarshalAs(UnmanagedType.Error)> Integer <PreserveSig> Function GetDisplayName(ByVal sigdnName As SIGDN, ByRef ppszName As IntPtr) As <MarshalAs(UnmanagedType.Error)> Integer <PreserveSig> Function GetAttributes(ByVal sfgaoMask As UInt32, ByRef psfgaoAttribs As UInt32) As <MarshalAs(UnmanagedType.Error)> Integer <PreserveSig> Function Compare(ByVal psi As IShellItem, ByVal hint As UInt32, ByRef piOrder As Integer) As <MarshalAs(UnmanagedType.Error)> Integer End Interface <DllImport("shell32.dll", CharSet:=CharSet.Unicode, PreserveSig:=False)> Public Shared Sub SHCreateItemFromParsingName( <MarshalAs(UnmanagedType.LPWStr)> ByVal pszPath As String, ByVal pbc As IntPtr, <MarshalAs(UnmanagedType.LPStruct)> ByVal riid As Guid, <MarshalAs(UnmanagedType.Interface, IidParameterIndex:=2)> ByRef ppv As IShellItem) End Sub Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load ListBox1.Items.Add("C:\Users\RLWA32\Downloads\windows6.1-kb3197867-x86.msu") End Sub Private Sub ListBox1_MouseDown(sender As Object, e As MouseEventArgs) Handles ListBox1.MouseDown Dim fileName As String Dim pbc As IntPtr Dim hRes As Integer Dim shellItem As IShellItem Dim dataObj As IDataObject fileName = ListBox1.Items(0) pbc = Nothing SHCreateItemFromParsingName(fileName, pbc, IShellItemGUID, shellItem) hRes = shellItem.BindToHandler(pbc, BHID_DataObject, IDataObjectGUID, dataObj) ListBox1.DoDragDrop(dataObj, DragDropEffects.All) End Sub End Class
- Edited by RLWA32 Friday, April 7, 2017 4:20 PM
-
New version of VB code. I changed it to use Exceptions instead of returning HRESULTS and also improved the way that interfaces were returned from the SHCreateItemFromParsingName and IShellItem::BindToHandler. BTW, in this version the sample file was 285 MB. :)
Imports System.Runtime.InteropServices Imports System.Runtime.InteropServices.ComTypes Public Class Form1 Dim IShellItemGUID As New Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe") Dim IDataObjectGUID As New Guid("0000010e-0000-0000-C000-000000000046") Dim BHID_DataObject As New Guid("B8C0BD9F-ED24-455c-83E6-D5390C4FE8C4") Public Enum SIGDN As UInteger NORMALDISPLAY = 0 PARENTRELATIVEPARSING = &H80018001UI PARENTRELATIVEFORADDRESSBAR = &H8001C001UI DESKTOPABSOLUTEPARSING = &H80028000UI PARENTRELATIVEEDITING = &H80031001UI DESKTOPABSOLUTEEDITING = &H8004C000UI FILESYSPATH = &H80058000UI URL = &H80068000UI End Enum <ComImport()> <Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")> <InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> Public Interface IShellItem Function BindToHandler(ByVal pbc As IntPtr, <MarshalAs(UnmanagedType.LPStruct)> ByVal bhid As Guid, <MarshalAs(UnmanagedType.LPStruct)> ByVal riid As Guid, <Out(), MarshalAs(UnmanagedType.Interface, IidParameterIndex:=2)> ByRef ppv As Object) Function GetParent(ByRef ppsi As IShellItem) As <MarshalAs(UnmanagedType.Error)> Integer Function GetDisplayName(ByVal sigdnName As SIGDN, ByRef ppszName As IntPtr) Function GetAttributes(ByVal sfgaoMask As UInt32, ByRef psfgaoAttribs As UInt32) Function Compare(ByVal psi As IShellItem, ByVal hint As UInt32, ByRef piOrder As Integer) End Interface <DllImport("shell32.dll", CharSet:=CharSet.Unicode, PreserveSig:=False)> Public Shared Sub SHCreateItemFromParsingName( <MarshalAs(UnmanagedType.LPWStr)> ByVal pszPath As String, ByVal pbc As IntPtr, <MarshalAs(UnmanagedType.LPStruct)> ByVal riid As Guid, <Out(), MarshalAs(UnmanagedType.Interface, IidParameterIndex:=2)> ByRef ppv As Object) End Sub Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load ListBox1.Items.Add("C:\Users\RLWA32\Downloads\VS2013 Image Library.zip") End Sub Private Sub ListBox1_MouseDown(sender As Object, e As MouseEventArgs) Handles ListBox1.MouseDown Dim fileName As String Dim pbc As IntPtr Dim hRes As Integer Dim shellItem As IShellItem Dim dataObj As IDataObject fileName = ListBox1.Items(0) pbc = Nothing shellItem = Nothing dataObj = Nothing Try SHCreateItemFromParsingName(fileName, pbc, IShellItemGUID, shellItem) shellItem.BindToHandler(pbc, BHID_DataObject, IDataObjectGUID, dataObj) ListBox1.DoDragDrop(dataObj, DragDropEffects.All) hRes = Marshal.ReleaseComObject(shellItem) hRes = Marshal.ReleaseComObject(dataObj) Catch ex As Exception MsgBox(ex.Message) End Try End Sub End Class
-
New version of VB code. I changed it to use Exceptions instead of returning HRESULTS and also improved the way that interfaces were returned from the SHCreateItemFromParsingName and IShellItem::BindToHandler. BTW, in this version the sample file was 285 MB. :)
Interesting approach, but I'm not sure that it's suitable for me. In my "real application" when user starts dragging usually I don't have file on disk, I have only it's description. I load file via webservice only in case user drops item to explorer. This is why I use so complicated d'n'd code.
-
New version of VB code. I changed it to use Exceptions instead of returning HRESULTS and also improved the way that interfaces were returned from the SHCreateItemFromParsingName and IShellItem::BindToHandler. BTW, in this version the sample file was 285 MB. :)
Interesting approach, but I'm not sure that it's suitable for me. In my "real application" when user starts dragging usually I don't have file on disk, I have only it's description. I load file via webservice only in case user drops item to explorer. This is why I use so complicated d'n'd code.