Copy e-mail attachment into application, issue with IStream interface?

0

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.

c#
outlook
copy-paste
asked on Stack Overflow Dec 19, 2017 by Pino Carafa • edited Dec 20, 2017 by Pino Carafa

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0