I found variations on this theme in various places but I am experiencing an issue with it. For the original code, refer to:
Drag-and-Drop multiple Attached File From Outlook to C# Window Form
See the answer of 3 Jan 2012 (by V_B)
I implemented a VB.NET variant of that code, but I ran into a problem with the case TYMED.TYMED_ISTREAM branch.
The scenario is that the user opens an e-mail in outlook, right-clicks on an Attachment in that email and picks "Copy" from the context menu.
When they subsequently try to Paste into the application in the receiving application, containing the above code the code enters the case TYMED.TYMED_ISTREAM branch, opens the stream alright, but then fails on the iStream.Stat(out iStreamStat, 0) statement with a NullReferenceException.
Trying to read even a single byte from the stream gives me an Access Violation.
Trying to Clone the stream gives me an Access Violation
These are the Declarations I'm using:
<Runtime.InteropServices.StructLayout(Runtime.InteropServices.LayoutKind.Sequential)> _
Private Structure SIZEL
Public cx As Integer
Public cy As Integer
End Structure
<Runtime.InteropServices.StructLayout(Runtime.InteropServices.LayoutKind.Sequential)> _
Private Structure POINTL
Public x As Integer
Public y As Integer
End Structure
<Runtime.InteropServices.StructLayout(Runtime.InteropServices.LayoutKind.Sequential, Charset:=Runtime.InteropServices.CharSet.Ansi)> _
Private NotInheritable Class FILEDESCRIPTORA
Public dwFlags As UInt32
Public clsid As Guid
Public sizel As SIZEL
Public point As POINTL
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
<Runtime.InteropServices.MarshalAs(Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=260)> _
Public cFileName As String
End Class
<Runtime.InteropServices.StructLayout(Runtime.InteropServices.LayoutKind.Sequential, Charset:=Runtime.InteropServices.CharSet.Ansi)> _
Private NotInheritable Class FILEGROUPDESCRIPTORA
Public cItems As UInt32
Public fgd As FILEDESCRIPTORA
End Class
<Runtime.InteropServices.ComImportAttribute(), _
Runtime.InteropServices.Guid("0000000C-0000-0000-C000-000000000046"), _
Runtime.InteropServices.InterfaceType(Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown)> _
Private Interface IStream
Sub Clone(<Runtime.InteropServices.Out()> ByRef ppstm As IStream)
Sub Commit(ByVal grfCommitFlags As UInteger)
Sub CopyTo(ByVal pstm As IStream, ByVal cb As Long, ByVal dwLockType As Integer)
Sub Read(ByRef pv As Byte, ByVal cb As Integer, ByVal pcbRead As IntPtr)
Sub Revert()
Sub Seek(ByVal dlibMove As Long, ByVal dwOrigin As Integer, ByVal plibNewPosition As IntPtr)
Sub SetSize(ByVal libNewSize As Long)
Sub Stat(<Runtime.InteropServices.Out()> ByRef pstatstg As Runtime.InteropServices.ComTypes.STATSTG, ByVal grfCommitFlags As Integer)
Sub UnlockRegion(ByVal libOffset As Long, ByVal cb As Long, ByVal dwLockType As Integer)
Sub Write(ByRef pv As Byte, ByVal cb As Integer, ByVal pcbWritten As IntPtr)
End Interface
This is the procedure I wrote, with the irrelevant branches taken out.
Public Shared Function FilesFromClipboard() As List(Of String)
Dim bRaw As Byte()
Dim oRawStream As System.IO.MemoryStream = Nothing
Dim nRawPointer As IntPtr
Dim sFiles As Dictionary(Of Integer, FILEDESCRIPTORA) = New Dictionary(Of Integer, FILEDESCRIPTORA)
Try
oRawStream = TryCast(System.Windows.Clipboard.GetData("FileGroupDescriptor"), System.IO.MemoryStream)
Catch
End Try
If oRawStream Is Nothing Then
Return New List(Of String)
End If
Dim oResult As List(Of String) = New List(Of String)
ReDim bRaw(0 To oRawStream.Length - 1)
oRawStream.Read(bRaw, 0, bRaw.Length)
oRawStream.Close()
oRawStream.Dispose()
nRawPointer = Runtime.InteropServices.Marshal.AllocHGlobal(bRaw.Length)
Runtime.InteropServices.Marshal.Copy(bRaw, 0, nRawPointer, bRaw.Length)
Dim oFGD As FILEGROUPDESCRIPTORA = New FILEGROUPDESCRIPTORA
Runtime.InteropServices.Marshal.PtrToStructure(nRawPointer, oFGD)
Dim nCount As Integer
Dim nItem As Integer = 0
nCount = oFGD.cItems
Dim nNewPointer As IntPtr = IntPtr.Add(nRawPointer, Runtime.InteropServices.Marshal.SizeOf(oFGD.cItems))
While nCount > 0
Dim oFD As FILEDESCRIPTORA = New FILEDESCRIPTORA
Runtime.InteropServices.Marshal.PtrToStructure(nNewPointer, oFD)
sFiles.Add(nItem, oFD)
nItem = nItem + 1
nCount = nCount - 1
nNewPointer = IntPtr.Add(nNewPointer, Runtime.InteropServices.Marshal.SizeOf(oFD))
End While
System.Runtime.InteropServices.Marshal.Release(nRawPointer)
Dim oCOMDO As System.Runtime.InteropServices.ComTypes.IDataObject = Clipboard.GetDataObject
nCount = sFiles.Count
nItem = 0
While nItem < nCount
Dim oFD As FILEDESCRIPTORA
oFD = sFiles.Item(nItem)
'Dim oFETC As FORMATETC = New FORMATETC
Dim oFETC As Runtime.InteropServices.ComTypes.FORMATETC = New Runtime.InteropServices.ComTypes.FORMATETC
Dim nCFFormat As Integer
nCFFormat = System.Windows.Forms.DataFormats.GetFormat("FileContents").Id
If nCFFormat > 32767 Then
nCFFormat = nCFFormat - 65536
oFETC.cfFormat = nCFFormat
Else
oFETC.cfFormat = nCFFormat
End If
oFETC.dwAspect = Runtime.InteropServices.ComTypes.DVASPECT.DVASPECT_CONTENT
oFETC.lindex = nItem
oFETC.ptd = IntPtr.Zero
oFETC.tymed = (Runtime.InteropServices.ComTypes.TYMED.TYMED_ISTREAM Or Runtime.InteropServices.ComTypes.TYMED.TYMED_ISTORAGE Or Runtime.InteropServices.ComTypes.TYMED.TYMED_HGLOBAL)
Dim oSM As Runtime.InteropServices.ComTypes.STGMEDIUM = New Runtime.InteropServices.ComTypes.STGMEDIUM()
oCOMDO.GetData(oFETC, oSM)
If oSM.tymed = Runtime.InteropServices.ComTypes.TYMED.TYMED_ISTREAM Then
Dim bData() As Byte
Dim oIStream As IStream = Nothing
Dim oStreamStat As System.Runtime.InteropServices.ComTypes.STATSTG
Dim nStreamSize As Integer
Try
oIStream = Runtime.InteropServices.Marshal.GetObjectForIUnknown(oSM.unionmember)
Catch
End Try
If Not oIStream Is Nothing Then
Try
oStreamStat = New Runtime.InteropServices.ComTypes.STATSTG()
oIStream.Stat(oStreamStat, 0)
nStreamSize = oStreamStat.cbSize
Catch ex As Exception
MsgBox("Error! " + ex.Message)
End Try
End If
Try
Runtime.InteropServices.Marshal.Release(oSM.unionmember)
Catch
End Try
'snipped out some irrelevant code
Try
System.Runtime.InteropServices.Marshal.ReleaseComObject(oIStream)
Catch
End Try
oIStream = Nothing
End If
oSM = Nothing
nItem = nItem + 1
End While
oCOMDO = Nothing
GC.Collect()
End Function
This is the code snippet in which the problem occurs:
oIStream.Stat(oStreamStat, 0)
This gives a NullReferenceException even though both oIStream and oStreamStat are set. I then tried debugging the code, and stepping through it I skipped the offending Try/Catch, and below it (in the 'snipped out irrelevant code) bit I first tried reading a single byte
oIStream.Read(bData(0), nStreamSize, IntPtr.Zero)
as well as cloning the stream. Both trying to read a single byte and cloning the stream lead to Access Violations.
To summarise: the problem is the NullReferenceException that occurs when it tries to execute the oIStream.Stat statement, and as a result it cannot establish the stream size so the code then cannot proceed.
Hm.... Minor observation. What USED to throw a NullReferenceException now appears to be throwing a different exception: Unable to perform requested operation. (Exception from HRESULT: 0x80030001 (STG_E_INVALIDFUNCTION)). I am not sure what I changed.
User contributions licensed under CC BY-SA 3.0